Skip to content

feat: add target node alias search#64

Open
aledefra wants to merge 1 commit intodevelopfrom
codex/target-node-alias-search
Open

feat: add target node alias search#64
aledefra wants to merge 1 commit intodevelopfrom
codex/target-node-alias-search

Conversation

@aledefra
Copy link
Collaborator

Summary

  • add getActiveNodes wrapper for /active_nodes_list with optional alias_pattern
  • add alias-based search dropdown in target nodes input
  • allow selecting a node by alias while persisting the selected node address

Verification

  • npm run lint
  • npm run build

Copilot AI review requested due to automatic review settings February 24, 2026 15:15
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds alias-based search functionality for target nodes in job deployment. Users can now search for nodes by typing an alias (instead of only using node addresses), with a dropdown showing matching active nodes that can be selected.

Changes:

  • Added getActiveNodes API wrapper to fetch active nodes with optional alias pattern filtering
  • Implemented autocomplete dropdown with debounced search when users type node aliases
  • Enhanced UX with loading states, error handling, and proper cleanup of async operations

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
src/lib/api/oracles.tsx Added getActiveNodes function and type definitions for fetching active nodes with alias pattern filtering
src/shared/jobs/target-nodes/TargetNodesSection.tsx Implemented alias search UI with dropdown, state management, debouncing, and integration with existing node validation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +67 to +97
export const getActiveNodes = async (
page: number = 1,
pageSize: number = 10,
aliasPattern?: string,
options?: RequestOptions,
): Promise<ActiveNodesResult> => {
const searchParams = new URLSearchParams({
items_per_page: String(pageSize),
page: String(page),
});

const normalizedAliasPattern = aliasPattern?.trim();
if (normalizedAliasPattern) {
searchParams.set('alias_pattern', normalizedAliasPattern);
}

const response = await fetch(`${oraclesUrl}/active_nodes_list?${searchParams.toString()}`, {
signal: options?.signal,
});

if (!response.ok) {
throw new Error('Failed to fetch active nodes.');
}

const payload: { result: ActiveNodesResult } = await response.json();
if (payload?.result?.error) {
throw new Error(payload.result.error);
}

return payload.result;
};
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getActiveNodes function uses raw fetch while other API functions in this file use axios through _doGet helper. This creates inconsistency in error handling (no toApiError wrapper) and request configuration. Consider using axios with the axiosInstance to maintain consistency with other API functions in this file, which would provide automatic error transformation through the interceptor.

Copilot uses AI. Check for mistakes.
Comment on lines +272 to +332
{showAliasSearchDropdown && (
<div className="absolute top-full right-0 left-0 z-20 mt-1 rounded-lg border border-slate-200 bg-white shadow-md">
<div className="max-h-52 overflow-y-auto py-1">
{isAliasSearchLoading && (
<div className="px-3 py-2 text-sm text-slate-500">
Searching nodes...
</div>
)}

{!isAliasSearchLoading && aliasSuggestions.length === 0 && (
<div className="px-3 py-2 text-sm text-slate-500">
No active nodes matched this alias.
</div>
)}

{!isAliasSearchLoading &&
aliasSuggestions.map((suggestion) => (
<button
key={suggestion.address}
type="button"
className="w-full cursor-pointer px-3 py-2 text-left hover:bg-slate-50"
onMouseDown={async (event) => {
event.preventDefault();
clearAliasSearchCloseTimeout();
field.onChange(suggestion.address);
setValue(
`deployment.targetNodes.${index}.address`,
suggestion.address,
);
setNodeInfoToIdle(suggestion.address);
setActiveAliasSearchIndex(null);
setAliasSuggestions([]);

await trigger('deployment.targetNodes');
await fetchNodeInfoForAddress(
suggestion.address,
);
}}
>
<div className="flex items-center justify-between gap-2">
<div className="truncate text-sm font-medium text-slate-700">
{suggestion.alias || suggestion.address}
</div>
<div
className={`h-2 w-2 rounded-full ${
suggestion.isOnline === null
? 'bg-slate-300'
: suggestion.isOnline
? 'bg-green-500'
: 'bg-yellow-500'
}`}
/>
</div>
<div className="truncate text-xs text-slate-500">
{suggestion.address}
</div>
</button>
))}
</div>
</div>
}
/>
)}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alias search dropdown lacks keyboard navigation support. Users cannot navigate through suggestions using arrow keys or select with Enter/Tab keys, which is a standard UX pattern for autocomplete dropdowns. Consider adding keyboard event handlers for arrow keys (up/down to navigate), Enter (to select), and Escape (to close) to improve accessibility and user experience.

Copilot uses AI. Check for mistakes.
Comment on lines +211 to +270
<StyledInput
placeholder="0xai_ or node alias"
value={value}
onFocus={() => {
clearAliasSearchCloseTimeout();
setActiveAliasSearchIndex(index);
}}
onChange={(e) => {
const nextValue = e.target.value;
field.onChange(nextValue);
setNodeInfoToIdle(nextValue);
setActiveAliasSearchIndex(index);
}}
onBlur={async () => {
field.onBlur();
scheduleAliasSearchClose(index);
await trigger('deployment.targetNodes');
await fetchNodeInfoForAddress(value);
}}
isInvalid={hasError}
errorMessage={specificError || fieldError || rootError}
endContent={
<div className="flex items-center gap-2">
<NodeInfoStatusPopover
nodeInfoState={nodeInfoState}
normalizedValue={normalizedValue}
ariaLabel="Show node info"
/>

<button
type="button"
className="cursor-pointer hover:opacity-60"
aria-label="Paste target node address from clipboard"
onClick={async () => {
try {
const clipboardText = await navigator.clipboard.readText();
field.onChange(clipboardText);

setValue(
`deployment.targetNodes.${index}.address`,
clipboardText,
);
setNodeInfoToIdle(clipboardText);
setActiveAliasSearchIndex(null);
setAliasSuggestions([]);

await fetchNodeInfoForAddress(clipboardText);
} catch (error) {
console.error('Failed to read clipboard:', error);
toast.error(
'Unable to read from clipboard. Please paste the address manually.',
);
}
}}
>
<RiClipboardLine className="text-lg text-slate-600" />
</button>
</div>
}
/>
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alias search dropdown is missing ARIA attributes for proper screen reader support. The input should have aria-expanded, aria-autocomplete="list", aria-controls pointing to the dropdown ID, and aria-activedescendant for the focused suggestion. The dropdown should have role="listbox" and each suggestion should have role="option". These attributes are essential for users relying on assistive technologies.

Copilot uses AI. Check for mistakes.
setActiveAliasSearchIndex((previousIndex) => (previousIndex === index ? null : previousIndex));
setAliasSuggestions([]);
setIsAliasSearchLoading(false);
}, 120);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The close timeout of 120ms seems very short and might cause the dropdown to close before users can click on a suggestion, especially on slower devices or for users with reduced dexterity. Consider increasing this to at least 200-300ms to provide a better user experience. Alternatively, you could use onMouseEnter/onMouseLeave on the dropdown container to manage visibility more reliably.

Suggested change
}, 120);
}, 250);

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +30
const isAbortError = (error: unknown) => {
if (!error || typeof error !== 'object') {
return false;
}

const errorObj = error as {
name?: string;
code?: string;
};

return errorObj.name === 'AbortError' || errorObj.code === 'ERR_CANCELED';
};
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isAbortError function is duplicated from nodeInfo.ts. Consider importing it from nodeInfo.ts instead of duplicating the code to reduce code duplication and improve maintainability. You would need to export it from nodeInfo.ts first.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants