fix: harden Data tab against sibling transform crashes and entity races#68
fix: harden Data tab against sibling transform crashes and entity races#68
Conversation
Three independent failure modes that all surface as "No data available for this component." even when the data endpoint returns items: 1. transformFault now accepts status as either a string or an object with an aggregatedStatus field, accepts code/fault_name as fallbacks for fault_code/description, and tolerates an undefined severity. Without these guards a single malformed fault payload threw inside a sibling transform and rejected the whole Promise.all in prefetchResourceCounts. 2. prefetchResourceCounts now wraps each per-resource transform in its own try/catch via a small safeCount helper. One bad resource can no longer wipe out the others. 3. The EntityDetailPanel resource-counts effect now sets a cancelled flag in its cleanup so a late Promise.all from a previously-selected entity cannot overwrite the new entity's topicsData. Also adds a typed access field to ComponentTopic, plumbed from x-medkit.access. DataPanel uses it to hide the write section for access==='read' and to label the section "Write Value" instead of "Publish Message" for scalar writes. closes #67
There was a problem hiding this comment.
Pull request overview
Hardens the Data tab/resource prefetch path so malformed sibling payloads (notably faults) and fast entity navigation no longer cause the Data tab to appear empty despite /<entity>/data returning items; also adds per-topic access metadata to drive write UI behavior.
Changes:
- Make
transformFaultand related transforms more defensive against payload drift; addComponentTopic.accessderived fromx-medkit.access. - Isolate per-resource count transforms in
prefetchResourceCountsso one bad payload can’t reject the whole counts prefetch. - Prevent stale async results from overwriting new entity state in
EntityDetailPanel; adjustDataPanelwrite/publish UI based onaccess.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/types.ts | Adds typed ComponentTopic.access to represent write capability from vendor extension metadata. |
| src/lib/transforms.ts | Hardens fault transform and plumbs x-medkit.access into ComponentTopic during data transform. |
| src/lib/transforms.test.ts | Adds regression/unit tests for new defensive fault handling and access passthrough behavior. |
| src/lib/store.ts | Wraps individual resource count transforms so failures don’t break sibling counts. |
| src/components/EntityDetailPanel.tsx | Guards against selection-race overwrites by ignoring late async results after entity change. |
| src/components/DataPanel.tsx | Hides write UI for access === 'read' and updates label for scalar writes. |
- Guard `xm?.access` with a `typeof === 'string'` check before lowercasing so non-string vendor payloads no longer throw inside the data transform. - Abort in-flight resource-count and entity-data fetches in the EntityDetailPanel cleanup via AbortController, so rapid entity navigation no longer piles up wasted requests. Threaded `signal` through prefetchResourceCounts to its three underlying GETs. - Replace `as never` casts in transformFault alias tests with `as unknown as RawFaultItem` to keep the cast intent typed.
There was a problem hiding this comment.
Pull request overview
Hardens the Entity Detail “Data” tab against sibling resource transform failures and rapid navigation races, while adding support for a typed per-item access mode to hide/adjust the write UI when appropriate.
Changes:
- Make
transformFaulttolerant of schema drift (status as object, field aliases, undefined severity). - Prevent
prefetchResourceCountsfrom failing the whole counts fetch when one resource transform throws; add AbortSignal support through counts/data fetch to avoid entity-selection races. - Add
ComponentTopic.access(fromx-medkit.access) and use it inDataPanelto hide the write section for read-only items and adjust copy.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/types.ts | Adds optional ComponentTopic.access union type for read/write/readwrite modes. |
| src/lib/transforms.ts | Hardens fault transform and plumbs x-medkit.access into ComponentTopic. |
| src/lib/transforms.test.ts | Adds regression tests for fault schema drift and access parsing. |
| src/lib/store.ts | Threads AbortSignal into prefetchResourceCounts and isolates per-resource transforms with try/catch. |
| src/components/EntityDetailPanel.tsx | Adds AbortController + cancellation guard to prevent stale async results overwriting new entity state. |
| src/components/DataPanel.tsx | Uses topic.access to hide write UI for read-only items and adjusts section label. |
- transformFault: guard severity_label with typeof before lowercasing so non-string payloads do not throw. - transformFault: when both fault_code and code are missing, generate a synthetic per-call id instead of the literal 'unknown'. A shared literal collapses store dedup (keyed by code+entity_id), duplicates React keys in the faults lists, and causes the expand-state Sets to toggle unrelated rows together. - DataPanel: split the write-form condition so an explicit access='write'/'readwrite' always enables the form and the fallback typed-topic heuristic checks data presence rather than truthiness. Fixes the write section disappearing when the last observed value is a falsy scalar (0, false, '') and no type hint is reported. - Add regression tests for synthetic fault codes and for the DataPanel canWrite logic across access modes.
There was a problem hiding this comment.
Pull request overview
Hardens the entity Data tab and related resource prefetching against malformed sibling payloads and fast-navigation races, while also adding explicit per-topic access control to hide or relabel write UI appropriately.
Changes:
- Makes fault/data transforms more defensive (schema drift tolerant) and threads
AbortSignalthroughprefetchResourceCounts+fetchEntityData. - Isolates per-resource count transforms so one malformed payload doesn’t poison parallel fetches.
- Adds
ComponentTopic.accessfromx-medkit.accessand updates DataPanel write-section behavior and labeling; adds/extends Vitest coverage.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/types.ts | Adds ComponentTopic.access union type for per-item access control. |
| src/lib/transforms.ts | Hardens transformFault and plumbs x-medkit.access into transformed data topics. |
| src/lib/transforms.test.ts | Adds regression tests for fault schema drift + synthetic codes + access passthrough. |
| src/lib/store.ts | Extends prefetchResourceCounts to accept AbortSignal and isolates transform failures per resource. |
| src/components/EntityDetailPanel.tsx | Prevents stale async results from overwriting current entity state via abort + cancelled guard. |
| src/components/DataPanel.tsx | Uses access to hide write UI for read-only items and relabels write section. |
| src/components/DataPanel.test.tsx | Adds unit tests covering canWrite behavior for access and falsy scalar values. |
Comments suppressed due to low confidence (1)
src/components/DataPanel.tsx:224
handleCopyFromLastgates the deep-copy onif (topic.data), which will no-op for legitimate falsy scalar readings like0,false, or''(even thoughhasDatawill show the "Copy to Publish" button for those values). Use a null/undefined presence check instead of truthiness so scalar values can be copied reliably.
topic.access === 'write' || topic.access === 'readwrite' ? 'Write Value' : 'Publish Message';
const handleCopyFromLast = () => {
if (topic.data) {
setPublishValue(JSON.parse(JSON.stringify(topic.data)));
- transformFault: accept an optional synthetic-id suffix and use the response array index (passed from transformFaultsResponse) instead of a timestamp+random token. The previous random suffix changed on every poll, so the store's code-based dedup saw the same code-less fault as new data on every refresh, churning React keys and resetting expand state. A per-index suffix keeps code-less faults distinct inside a response while remaining stable across polls. - DataPanel: switch publishValue initialization from `||` to `??` so a gateway-reported scalar of 0 / false / '' is preserved instead of collapsing to an empty object, and replace the truthy guard in handleCopyFromLast with an explicit null/undefined presence check so "Copy to Publish" works for legitimate falsy scalar readings. - Drop the truncated license header from DataPanel.test.tsx to match the header convention of the other component test files. - Extend DataPanel and transforms tests with the corresponding regression cases (scalar-zero initial value, scalar-zero copy, stable deterministic synthetic codes across repeated transform calls).
There was a problem hiding this comment.
Pull request overview
Hardens the entity Data tab loading path so that malformed sibling resource payloads (faults/configs/ops) and fast entity navigation can no longer cause the Data tab to incorrectly fall back to an empty state, while also plumbing a new per-topic access mode to hide/rename the write UI appropriately.
Changes:
- Made
transformFaultresilient to schema drift (status object/null, alias fields, missing severity) and stabilized synthetic fault codes for code-less items. - Updated
prefetchResourceCountsto accept anAbortSignaland isolate per-resource transform failures; added effect cleanup guarding + request aborting inEntityDetailPanel. - Added
ComponentTopic.accessderived fromx-medkit.access, updatedDataPanelwrite gating/labeling, and added targeted Vitest coverage.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/lib/types.ts | Adds ComponentTopic.access typing for read/write/readwrite. |
| src/lib/transforms.ts | Hardens fault + data transforms; threads access through data transform. |
| src/lib/transforms.test.ts | Adds regression tests for fault status/aliases/synthetic codes and access passthrough. |
| src/lib/store.ts | Extends prefetchResourceCounts with optional abort signal and per-transform safety. |
| src/components/EntityDetailPanel.tsx | Prevents stale async results from overwriting current entity state via abort + cancelled guard. |
| src/components/DataPanel.tsx | Uses access to hide write form for read-only topics, fixes falsy scalar handling, updates label. |
| src/components/DataPanel.test.tsx | Adds regression tests for access gating and falsy scalar publish initialization/copy. |
The `(data.items || []).map(...)` fallback only catches nullish values,
so any truthy non-array payload (e.g. `{ items: {} }` or `{ items: 'x' }`)
would crash the transform with "map is not a function". Switch to
`Array.isArray` so malformed list payloads fall through to an empty
result instead of breaking the Data tab for sibling resources.
Covered by a new regression test that feeds a selection of non-array
truthy values into the transform and asserts an empty, crash-free result.
Summary
Fixes #67. Three independent regressions surfaced as the same symptom (Data tab shows "No data available" even when
/<entity>/datareturns items):transformFaultis now defensive: acceptsstatusas string or object (aggregatedStatus), acceptscode/fault_nameas fallbacks forfault_code/description, and tolerates undefinedseverity. Without this any malformed fault payload threw inside a sibling transform and rejected the wholePromise.allinprefetchResourceCounts.prefetchResourceCountswraps each per-resource transform in its owntry/catch. One bad resource can no longer wipe out the others.EntityDetailPanelresource-counts effect sets acancelledflag in cleanup, so a late callback from a previously selected entity cannot overwrite the new entity'stopicsData(caught a real race when navigating fast).accessfield onComponentTopic, plumbed fromx-medkit.access.DataPanelhides the write section foraccess==='read'and labels itWrite Valueinstead ofPublish Messagefor scalar writes.Issue
Type
Testing