From 64c19166452d9a19e50c5f7fae08ab992f1a55ec Mon Sep 17 00:00:00 2001 From: Bernhard Jackiewicz Date: Sat, 3 Oct 2026 20:02:52 +0200 Subject: [PATCH] fix(ui): name a Behavior start that calls itself instead of counting it A start operation that calls itself was counted as one more direct callee but drawn once, as the start. The heading then named one call more than the pages, and the page counter read the gap as a filter nobody had set. The start is no longer a callee of itself; the heading says "also calls itself", and "matching the filter" appears only while a filter is set. Signed-off-by: Bernhard Jackiewicz --- graph-ui/src/architecture/BehaviorJourney.test.tsx | 11 +++++++++++ graph-ui/src/architecture/BehaviorJourney.tsx | 4 ++-- .../architecture/behavior-journey-model.test.ts | 12 ++++++++++++ .../src/architecture/behavior-journey-model.ts | 14 +++++++++----- graph-ui/src/architecture/strings.ts | 2 ++ 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/graph-ui/src/architecture/BehaviorJourney.test.tsx b/graph-ui/src/architecture/BehaviorJourney.test.tsx index 4c36d762e..04d7c6740 100644 --- a/graph-ui/src/architecture/BehaviorJourney.test.tsx +++ b/graph-ui/src/architecture/BehaviorJourney.test.tsx @@ -122,6 +122,17 @@ describe('behavior journeys', () => { // Every page draws its calls and the start beside them. expect(container.querySelectorAll('[data-node]')).toHaveLength(3); }); + it('names a start operation that calls itself instead of counting it as a callee or claiming a filter', async () => { + const data = fixture(); + const callees = Array.from({ length: 6 }, (_, index) => symbol(index + 20)); + data.paths = [...callees.map((target, index) => ({ entrypoint_id: 1, nodes: [data.entrypoints[0], target], edges: [{ id: 500 + index, source_id: 1, target_id: target.id, type: 'CALLS' }] })), + { entrypoint_id: 1, nodes: [data.entrypoints[0], data.entrypoints[0]], edges: [{ id: 599, source_id: 1, target_id: 1, type: 'CALLS' }] }]; + await render(data); + expect(container.querySelector('.behavior-navigation span')?.textContent).toBe('6 direct callees with returned evidence · also calls itself'); + const pages = container.querySelector('[aria-label="Direct call pages"]')?.textContent ?? ''; + expect(pages).toContain('Calls 1 to 4 of 6'); + expect(pages).not.toContain('matching the filter'); + }); it('shows the operation the journey opened with in the Start field, not "Choose an operation"', async () => { // Without a requested entry the journey falls back to the first entry point (Django: main in manage.py-tpl). await render(fixture(), { entryId: undefined }); diff --git a/graph-ui/src/architecture/BehaviorJourney.tsx b/graph-ui/src/architecture/BehaviorJourney.tsx index 4b2f048df..8c88f72f6 100644 --- a/graph-ui/src/architecture/BehaviorJourney.tsx +++ b/graph-ui/src/architecture/BehaviorJourney.tsx @@ -145,7 +145,7 @@ export default function BehaviorJourney({ project, generation, data, entries, ta
{targetId !== undefined && } {pending ? 'Updating this journey…' : path ? `${path.edges.length} call-chain hops · ${new Set(path.nodes.map(item => item.component_id)).size} components` - : `${journey?.counts.directChoices ?? 0} direct callees with returned evidence`} + : text.directCallees(journey?.counts.directChoices ?? 0, journey?.counts.selfCalls ?? false)} Static evidence
{error &&

{error}

} @@ -171,7 +171,7 @@ export default function BehaviorJourney({ project, generation, data, entries, ta {activeStep + 1} / {path.nodes.length}} {!path && journey.scene.nodes.length > 5 && }