fix(web): address four more PR review comments

Edge visibility (useSigma.ts): HAS_METHOD / HAS_PROPERTY edges were hidden
when any edge-type filter was active because those types are not in the EdgeType
union. Normalize HAS_METHOD → DEFINES and HAS_PROPERTY → CONTAINS before the
visibleTypes.includes() guard so Kotlin/Java hierarchy edges follow the same
filter logic as their semantic equivalents.

Force-mode edge styles (graph-adapter.ts): HAS_METHOD / HAS_PROPERTY fell back
to the default gray color in the force-graph adapter because EDGE_STYLES had no
entries for them. Added explicit entries using the same hues as DEFINES/CONTAINS
so force mode renders Kotlin/Java hierarchy edges consistently with tree/circles.

Accessibility (GraphCanvas.tsx, locales): the layout-mode switcher (Force /
Tree / Circles) had no ARIA semantics. Added role="tablist" on the container
and role="tab" + aria-selected on each button. Added the viewModes.label i18n
key (used as aria-label on the tablist) to en and zh-CN locale files.

Flaky test (graph-adapter.test.ts): replaced the hard 2 s wall-clock assertion
with a structural check (node count + edge count) that is deterministic across
CI hardware. Timing tests are inherently flaky and provide no correctness signal.

Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-5
This commit is contained in:
Hugo Gu 2026-05-26 20:35:04 +08:00
parent 743a69c5ff
commit 4e5df3d3c8
6 changed files with 38 additions and 9 deletions

View file

@ -272,8 +272,14 @@ export const GraphCanvas = forwardRef<GraphCanvasHandle>((_, ref) => {
</div>
{/* View Mode Tabs */}
<div className="absolute top-4 left-1/2 z-20 flex -translate-x-1/2 gap-1 rounded-lg border border-border-subtle bg-elevated/90 p-1 backdrop-blur-sm">
<div
role="tablist"
aria-label={t('canvas.viewModes.label')}
className="absolute top-4 left-1/2 z-20 flex -translate-x-1/2 gap-1 rounded-lg border border-border-subtle bg-elevated/90 p-1 backdrop-blur-sm"
>
<button
role="tab"
aria-selected={graphViewMode === 'force'}
onClick={() => handleViewModeChange('force')}
className={`flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-all ${
graphViewMode === 'force'
@ -285,6 +291,8 @@ export const GraphCanvas = forwardRef<GraphCanvasHandle>((_, ref) => {
{t('canvas.viewModes.force')}
</button>
<button
role="tab"
aria-selected={graphViewMode === 'tree'}
onClick={() => handleViewModeChange('tree')}
className={`flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-all ${
graphViewMode === 'tree'
@ -296,6 +304,8 @@ export const GraphCanvas = forwardRef<GraphCanvasHandle>((_, ref) => {
{t('canvas.viewModes.tree')}
</button>
<button
role="tab"
aria-selected={graphViewMode === 'circles'}
onClick={() => handleViewModeChange('circles')}
className={`flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-all ${
graphViewMode === 'circles'

View file

@ -586,10 +586,19 @@ export const useSigma = (options: UseSigmaOptions = {}): UseSigmaReturn => {
edgeReducer: (edge, data) => {
const res = { ...data };
// Check edge type visibility first
// Check edge type visibility first.
// HAS_METHOD / HAS_PROPERTY are Kotlin/Java hierarchy edges not in the
// EdgeType union — normalize them so they follow DEFINES / CONTAINS
// visibility instead of being silently hidden.
const visibleTypes = visibleEdgeTypesRef.current;
if (visibleTypes && data.relationType) {
if (!visibleTypes.includes(data.relationType as EdgeType)) {
const normalizedType =
data.relationType === 'HAS_METHOD'
? 'DEFINES'
: data.relationType === 'HAS_PROPERTY'
? 'CONTAINS'
: data.relationType;
if (!visibleTypes.includes(normalizedType as EdgeType)) {
res.hidden = true;
return res;
}

View file

@ -98,8 +98,9 @@ describe('knowledgeGraphToTreeGraphology', () => {
});
});
it('should complete within an acceptable time budget for a medium-sized graph', () => {
it('should handle a medium-sized graph without dropping nodes or edges', () => {
// 2000 nodes + 4000 edges — exercises the adaptive spring iteration path (14 iters).
// Structural assertion only: wall-clock timing is too variable across CI machines.
const nodes: GraphNode[] = Array.from({ length: 2000 }, (_, i) =>
makeNode(`n${i}`, i % 4 === 0 ? 'Folder' : i % 4 === 1 ? 'File' : 'Function', `node${i}`),
);
@ -111,12 +112,14 @@ describe('knowledgeGraphToTreeGraphology', () => {
}));
const graph: KnowledgeGraph = { nodes, relationships };
const start = performance.now();
knowledgeGraphToTreeGraphology(graph);
const elapsed = performance.now() - start;
const sigmaGraph = knowledgeGraphToTreeGraphology(graph);
// Should complete comfortably within 2 seconds even on slow CI machines.
expect(elapsed).toBeLessThan(2000);
// All nodes that have a tree-layout position must be present in the output.
expect(sigmaGraph.order).toBe(2000);
// Every relationship whose source and target both exist should produce an edge.
// Self-loops (sourceId === targetId) are excluded — the adapter skips them.
const selfLoops = relationships.filter((r) => r.sourceId === r.targetId).length;
expect(sigmaGraph.size).toBe(relationships.length - selfLoops);
});
});

View file

@ -308,6 +308,11 @@ export const knowledgeGraphToGraphology = (
// TYPE RELATIONSHIPS - Warm colors (OOP)
EXTENDS: { color: '#c2410c', sizeMultiplier: 1.0 }, // Orange - extension
IMPLEMENTS: { color: '#be185d', sizeMultiplier: 0.9 }, // Pink - interface implementation
// KOTLIN/JAVA HIERARCHY — same hues as their logical equivalents so force
// mode renders these consistently with tree/circles view.
HAS_METHOD: { color: EDGE_INFO.DEFINES.color, sizeMultiplier: 0.4 }, // Class→Method (≈ DEFINES)
HAS_PROPERTY: { color: EDGE_INFO.CONTAINS.color, sizeMultiplier: 0.35 }, // Class→Property (≈ CONTAINS)
};
// Two-pass insertion so hierarchy/DEFINES edges are drawn first (behind)

View file

@ -113,6 +113,7 @@
},
"canvas": {
"viewModes": {
"label": "Graph view mode",
"force": "Force Graph",
"tree": "Sequential Layout",
"circles": "Radial Layout"

View file

@ -113,6 +113,7 @@
},
"canvas": {
"viewModes": {
"label": "图形视图模式",
"force": "力导向图",
"tree": "顺序布局",
"circles": "径向布局"