(props: {
tools: readonly ToolSummary[];
selectedToolId: string | null;
onSelect: (toolId: string) => void;
onSetPolicy?: (pattern: string, action: ToolPolicyAction) => void;
onClearPolicy?: (pattern: string) => void;
patternForDisplay: (displayPattern: string) => string;
exactPatterns: ReadonlyMap<string, ToolPolicyAction>;
search: string;
terms: readonly string[];
selectedRowRef: React.Ref<HTMLButtonElement>;
})
| 448 | // --------------------------------------------------------------------------- |
| 449 | |
| 450 | function ToolTreeBody(props: { |
| 451 | tools: readonly ToolSummary[]; |
| 452 | selectedToolId: string | null; |
| 453 | onSelect: (toolId: string) => void; |
| 454 | onSetPolicy?: (pattern: string, action: ToolPolicyAction) => void; |
| 455 | onClearPolicy?: (pattern: string) => void; |
| 456 | patternForDisplay: (displayPattern: string) => string; |
| 457 | exactPatterns: ReadonlyMap<string, ToolPolicyAction>; |
| 458 | search: string; |
| 459 | terms: readonly string[]; |
| 460 | selectedRowRef: React.Ref<HTMLButtonElement>; |
| 461 | }) { |
| 462 | const { |
| 463 | tools, |
| 464 | selectedToolId, |
| 465 | onSelect, |
| 466 | onSetPolicy, |
| 467 | onClearPolicy, |
| 468 | patternForDisplay, |
| 469 | exactPatterns, |
| 470 | search, |
| 471 | terms, |
| 472 | selectedRowRef, |
| 473 | } = props; |
| 474 | const [manualOpen, setManualOpen] = useState<Set<string>>(() => new Set()); |
| 475 | |
| 476 | const tree = useMemo(() => buildTree(tools), [tools]); |
| 477 | |
| 478 | // When searching, expand everything so matches are visible. |
| 479 | // Also auto-expand groups that contain the selected tool. |
| 480 | const openSet = useMemo(() => { |
| 481 | if (terms.length > 0) { |
| 482 | const all = new Set<string>(); |
| 483 | collectGroupPaths(tree, all); |
| 484 | return all; |
| 485 | } |
| 486 | const set = new Set(manualOpen); |
| 487 | if (selectedToolId) { |
| 488 | const parts = selectedToolId.split("."); |
| 489 | // Progressively add ancestor paths (best-effort, based on dotted name). |
| 490 | let acc = ""; |
| 491 | for (let i = 0; i < parts.length - 1; i++) { |
| 492 | acc = acc ? `${acc}.${parts[i]}` : parts[i]!; |
| 493 | set.add(acc); |
| 494 | } |
| 495 | } |
| 496 | return set; |
| 497 | }, [tree, manualOpen, selectedToolId, terms.length]); |
| 498 | |
| 499 | const rows = useMemo(() => { |
| 500 | const acc: Row[] = []; |
| 501 | flattenTree(tree, 0, openSet, acc); |
| 502 | return acc; |
| 503 | }, [tree, openSet]); |
| 504 | |
| 505 | const toggleGroup = (path: string) => { |
| 506 | setManualOpen((prev) => { |
| 507 | const next = new Set(prev); |
nothing calls this directly
no test coverage detected