You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
VehicleDetails.tsx currently hydrates its edit form from VehicleGQLShaped — the projection row used by the list — via hydrateFromRow() (src/data/vehicles/vehicleFormDefaults.ts). That row only carries the few columns the list needs (registrationNumber, operationalNumber, transportType.{id,name,transportMode}, version). It is missing the bulk of the NeTEx Vehicle shape — Name, Description, ShortName, BuildDate, ChassisNumber, RegistrationDate, keyList, ValidBetween, VehicleModelRef, etc. — so the form can never round-trip a real Vehicle.
The Sobek single-vehicle export endpoint is in place:
GET /services/vehicles/netex/vehicles/{id} → PublicationDelivery XML (CompositeFrame → ResourceFrame → vehicles → <Vehicle>), with Accept: application/xml. Unknown id returns an empty ResourceFrame.
This issue switches details hydration to that endpoint. Save path already round-trips via useVehiclePairSave → buildVehiclePairXml → import — this aligns the read side with it.
Scope (Hathor only)
Add fetchVehicleNetexXml(applicationImportBaseUrl, id, token) under src/data/vehicles/xml/ — GET ${base}/vehicles/${encodeURIComponent(id)}, Accept: application/xml, returns raw string.
Add parseVehicleXml(xml): Partial<Vehicle> — uses fast-xml-parser's XMLParser (already a dep, see src/data/vehicle-imports/xmlUtils.ts:106) to drill PublicationDelivery → dataObjects → (CompositeFrame.frames?.ResourceFrame ?? ResourceFrame) → vehicles → Vehicle[0]. Reuse findResourceFrame + toArray from xmlUtils.ts. Map the parsed shape into the Vehicle interface declared in src/data/vehicles/xml/Vehicle.ts.
VehicleDetails.tsx: replace the hydrateFromRow(vehicle) path with the new hook. Keep the GQL row only for the header context strip (transport-type chip + mode chip) until issue Vehicle + VehicleModel combined details form (phase 2) #69 reshapes that too. The edit form (VehicleEditForm) gets fed the XML-shaped data directly.
Delete vehicleFormDefaults.ts:hydrateFromRow once nothing else references it. BLANK_FORM stays — "New Vehicle" flow still needs it.
Open design question (handled in PR)
fast-xml-parser's default shape does not match xml/Vehicle.ts:
concern
xml/Vehicle.ts
fast-xml-parser default
attributes
\$xxx (e.g. \$id)
@_xxx (e.g. @_id)
text node
value
#text
ref shape
string (flattened, e.g. TransportTypeRef: 'NMR:VT:1')
{ '@_ref': 'NMR:VT:1' }
arrays
always T[] (e.g. Name: TextType[])
object when 1, array when N (or use isArray)
Two acceptable approaches:
(a) Parser adapter — keep xml/Vehicle.ts as the truth; write a thin xmlToVehicle() that renames keys (@_ → \$), unwraps { '@_ref' } to string, and forces arrays via XMLParser's isArray callback on the known list fields. This is the inverse of the existing vehicleToXmlShape() write mapper.
(b) Adopt the parser shape — change xml/Vehicle.ts (and VehicleEditForm's readers like firstText) to consume @_ / #text / { '@_ref' } directly, then update Vehicle-mapping.ts to be identity-shaped on the parsed half. Bigger blast radius but eliminates two transforms.
Decision happens during PR: build a small fixture from the export-seed (vehicle-export-seed.xml in Sobek), parse with XMLParser, diff against Vehicle interface, then pick. If the gap turns out to be wide enough that neither option is clean, retrofit Vehicle.ts / EditForm to the XML shape per the user's plan ("the details form will be retrofitted to align with the xml shaped vehicle").
Changes to VehicleEditForm's field set — that follows once the parsed shape is wired in and we can see what populates vs. what's empty.
Verification
npm run local against Sobek :37999. Click a vehicle in the list → details slider opens → form fields populated from the NeTEx fetch (not just registration number + op number). Edit a field → save → re-open → field persists.
Network tab shows the GET /netex/vehicles/{id} round-trip.
Unknown-id route still renders the existing "Vehicle not found" empty state (empty vehicles array from the parser).
Problem
VehicleDetails.tsxcurrently hydrates its edit form fromVehicleGQLShaped— the projection row used by the list — viahydrateFromRow()(src/data/vehicles/vehicleFormDefaults.ts). That row only carries the few columns the list needs (registrationNumber,operationalNumber,transportType.{id,name,transportMode},version). It is missing the bulk of the NeTEx Vehicle shape —Name,Description,ShortName,BuildDate,ChassisNumber,RegistrationDate,keyList,ValidBetween,VehicleModelRef, etc. — so the form can never round-trip a real Vehicle.The Sobek single-vehicle export endpoint is in place:
GET /services/vehicles/netex/vehicles/{id}→PublicationDeliveryXML (CompositeFrame → ResourceFrame → vehicles →<Vehicle>), withAccept: application/xml. Unknown id returns an empty ResourceFrame.This issue switches details hydration to that endpoint. Save path already round-trips via
useVehiclePairSave→buildVehiclePairXml→ import — this aligns the read side with it.Scope (Hathor only)
fetchVehicleNetexXml(applicationImportBaseUrl, id, token)undersrc/data/vehicles/xml/—GET ${base}/vehicles/${encodeURIComponent(id)},Accept: application/xml, returns raw string.parseVehicleXml(xml): Partial<Vehicle>— usesfast-xml-parser'sXMLParser(already a dep, seesrc/data/vehicle-imports/xmlUtils.ts:106) to drillPublicationDelivery → dataObjects → (CompositeFrame.frames?.ResourceFrame ?? ResourceFrame) → vehicles → Vehicle[0]. ReusefindResourceFrame+toArrayfromxmlUtils.ts. Map the parsed shape into theVehicleinterface declared insrc/data/vehicles/xml/Vehicle.ts.useVehicle(id)hook — mirrorsuseVehicles()lifecycle (fetch on id/baseUrl change,loading/error/refetch), returns{ data: Partial<Vehicle> | null, loading, error, refetch }.VehicleDetails.tsx: replace thehydrateFromRow(vehicle)path with the new hook. Keep the GQL row only for the header context strip (transport-type chip + mode chip) until issue Vehicle + VehicleModel combined details form (phase 2) #69 reshapes that too. The edit form (VehicleEditForm) gets fed the XML-shaped data directly.vehicleFormDefaults.ts:hydrateFromRowonce nothing else references it.BLANK_FORMstays — "New Vehicle" flow still needs it.Open design question (handled in PR)
fast-xml-parser's default shape does not matchxml/Vehicle.ts:\$xxx(e.g.\$id)@_xxx(e.g.@_id)value#textstring(flattened, e.g.TransportTypeRef: 'NMR:VT:1'){ '@_ref': 'NMR:VT:1' }T[](e.g.Name: TextType[])isArray)Two acceptable approaches:
xml/Vehicle.tsas the truth; write a thinxmlToVehicle()that renames keys (@_→\$), unwraps{ '@_ref' }to string, and forces arrays viaXMLParser'sisArraycallback on the known list fields. This is the inverse of the existingvehicleToXmlShape()write mapper.xml/Vehicle.ts(andVehicleEditForm's readers likefirstText) to consume@_/#text/{ '@_ref' }directly, then updateVehicle-mapping.tsto be identity-shaped on the parsed half. Bigger blast radius but eliminates two transforms.Decision happens during PR: build a small fixture from the export-seed (
vehicle-export-seed.xmlin Sobek), parse withXMLParser, diff againstVehicleinterface, then pick. If the gap turns out to be wide enough that neither option is clean, retrofitVehicle.ts/EditFormto the XML shape per the user's plan ("the details form will be retrofitted to align with the xml shaped vehicle").Out of scope
VehicleDetails.tsxaway from its sidebar form (issue Vehicle + VehicleModel combined details form (phase 2) #69 — phase-2 combined vehicle+model form).vehicle(id:)fetch on the list-row click path — list still needs the projection row for sort/filter; the row also seeds the header strip until Vehicle + VehicleModel combined details form (phase 2) #69 lands.VehicleEditForm's field set — that follows once the parsed shape is wired in and we can see what populates vs. what's empty.Verification
npm run localagainst Sobek :37999. Click a vehicle in the list → details slider opens → form fields populated from the NeTEx fetch (not just registration number + op number). Edit a field → save → re-open → field persists.GET /netex/vehicles/{id}round-trip.vehiclesarray from the parser).