diff --git a/datajunction-ui/src/app/pages/AddEditNodePage/NodeQueryField.jsx b/datajunction-ui/src/app/pages/AddEditNodePage/NodeQueryField.jsx
index 0309682d7..7c83f03be 100644
--- a/datajunction-ui/src/app/pages/AddEditNodePage/NodeQueryField.jsx
+++ b/datajunction-ui/src/app/pages/AddEditNodePage/NodeQueryField.jsx
@@ -232,8 +232,9 @@ export const NodeQueryField = ({ djClient, value }) => {
djNodeHoverTooltip({
getStatus: key => tableStatusRef.current[key],
getKnownCatalogs: () => knownCatalogsRef.current,
+ fetchNodeDetails: name => djClient.node(name).catch(() => null),
}),
- [],
+ [djClient],
);
return (
diff --git a/datajunction-ui/src/app/pages/AddEditNodePage/djNodeBadges.js b/datajunction-ui/src/app/pages/AddEditNodePage/djNodeBadges.js
index a01daa95e..b0e9b93fa 100644
--- a/datajunction-ui/src/app/pages/AddEditNodePage/djNodeBadges.js
+++ b/datajunction-ui/src/app/pages/AddEditNodePage/djNodeBadges.js
@@ -274,11 +274,23 @@ export function renderTooltipDom(status, refKey) {
return wrap;
}
+// Cache of node-detail fetches so re-hovering the same chip doesn't refetch.
+// Lives at module scope (one editor + dom = one cache) — small enough that
+// not bothering with an LRU.
+const nodeDetailsCache = new Map();
+
/**
* Hover tooltip extension. Pairs with djNodeBadges so the same
- * `getStatus` source of truth drives the popover content.
+ * `getStatus` source of truth drives the popover content. Additionally
+ * lazy-fetches the full node (columns, description, mode, version) on
+ * hover via `fetchNodeDetails`, and re-renders the tooltip in place
+ * when the promise resolves.
*/
-export function djNodeHoverTooltip({ getStatus, getKnownCatalogs }) {
+export function djNodeHoverTooltip({
+ getStatus,
+ getKnownCatalogs,
+ fetchNodeDetails,
+}) {
return hoverTooltip(
(view, pos) => {
const hit = refAtPos(view, pos);
@@ -299,9 +311,46 @@ export function djNodeHoverTooltip({ getStatus, getKnownCatalogs }) {
pos: hit.from,
end: hit.to,
above: true,
- create: () => ({
- dom: renderTooltipDom(status || { refType: 'source' }, hit.key),
- }),
+ create: () => {
+ const baseStatus = status || { refType: 'source' };
+ const dom = renderTooltipDom(baseStatus, hit.key);
+
+ // Lazy-fetch the full node so we can show columns + description.
+ // The validateNode dep object only carries {name, type, status}.
+ if (
+ fetchNodeDetails &&
+ baseStatus.kind !== 'invalid' &&
+ baseStatus.kind !== 'registering'
+ ) {
+ const cached = nodeDetailsCache.get(hit.key);
+ const promise = cached || fetchNodeDetails(hit.key);
+ if (!cached) nodeDetailsCache.set(hit.key, promise);
+
+ Promise.resolve(promise)
+ .then(full => {
+ if (!full) return;
+ const richDom = renderTooltipDom(
+ {
+ ...baseStatus,
+ node: { ...(baseStatus.node || {}), ...full },
+ },
+ hit.key,
+ );
+ if (dom.parentNode) {
+ dom.parentNode.replaceChild(richDom, dom);
+ } else {
+ // Tooltip already detached — swap children so future refs
+ // see the rich content.
+ dom.replaceChildren(...richDom.childNodes);
+ }
+ })
+ .catch(() => {
+ // Soft-fail — leave the bare header tooltip in place.
+ });
+ }
+
+ return { dom };
+ },
};
},
{ hoverTime: 150 },
diff --git a/datajunction-ui/src/app/pages/AddEditNodePage/index.jsx b/datajunction-ui/src/app/pages/AddEditNodePage/index.jsx
index e8a81e2ae..7fa46cfe4 100644
--- a/datajunction-ui/src/app/pages/AddEditNodePage/index.jsx
+++ b/datajunction-ui/src/app/pages/AddEditNodePage/index.jsx
@@ -544,28 +544,6 @@ export function AddEditNodePage({ extensions = {} }) {
) : (