Spotted during /code-review on #85's branch (table-detail loading spinner work), but the underlying race predates that PR — it exists in the single-await version of openNodeDetail too, not something #85 introduced.
Where: src/ui/app.js openNodeDetail(node, targetDoc) calls openDetailPane(app, node, detail, targetDoc) (src/ui/schema-detail.js:23), which unconditionally removes whatever .schema-detail pane is currently mounted and replaces it, with no check that node is still the most-recently-requested node.
Failure scenario: user clicks table A in the fullscreen schema graph (starts ch.loadTableDetail for A), then quickly clicks table B before A's fetch resolves (starts a separate fetch for B, mounts B's pane/ring). If A's fetch resolves after B's, A's openDetailPane call fires last and silently replaces B's pane + selection ring with A's stale data — last-resolved-wins instead of last-clicked-wins. No crash, but the displayed detail can mismatch what's highlighted as selected.
Why deferred: out of scope for #85 (UI polish issue); fixing requires a request-identity guard (e.g. stash a request token/node id and check it before the final openDetailPane call), which is a small but separate behavioral change deserving its own test coverage.
Suggested fix shape: track a per-overlay "latest requested node id" (or a monotonic request counter) in openNodeDetail; before the final openDetailPane call, check the request is still current and no-op otherwise.
Spotted during /code-review on #85's branch (table-detail loading spinner work), but the underlying race predates that PR — it exists in the single-await version of
openNodeDetailtoo, not something #85 introduced.Where:
src/ui/app.jsopenNodeDetail(node, targetDoc)callsopenDetailPane(app, node, detail, targetDoc)(src/ui/schema-detail.js:23), which unconditionally removes whatever.schema-detailpane is currently mounted and replaces it, with no check thatnodeis still the most-recently-requested node.Failure scenario: user clicks table A in the fullscreen schema graph (starts
ch.loadTableDetailfor A), then quickly clicks table B before A's fetch resolves (starts a separate fetch for B, mounts B's pane/ring). If A's fetch resolves after B's, A'sopenDetailPanecall fires last and silently replaces B's pane + selection ring with A's stale data — last-resolved-wins instead of last-clicked-wins. No crash, but the displayed detail can mismatch what's highlighted as selected.Why deferred: out of scope for #85 (UI polish issue); fixing requires a request-identity guard (e.g. stash a request token/node id and check it before the final
openDetailPanecall), which is a small but separate behavioral change deserving its own test coverage.Suggested fix shape: track a per-overlay "latest requested node id" (or a monotonic request counter) in
openNodeDetail; before the finalopenDetailPanecall, check the request is still current and no-op otherwise.