feat(ui): the type hierarchy — what a type is built on, and what dispatches through it (CG-58)
A vertical tree above the members outline for classes, interfaces, structs, traits, protocols, enums, unions and type aliases: ancestors above (the whole chain, not just the direct parent), the focus in accent, subtypes below indented per level. `extends` draws solid, `implements` dashed; a synthesized edge — Go's implicit interface satisfaction — draws dashed wider and carries the site it was wired at, so a relation the resolver inferred never reads like one the source wrote down. For an interface the fan below IS the set of runtime targets a call can land on, and a type with eight or more implementers leads with that in a sentence. Members that redeclare an ancestor's are marked in the outline. The walk lives in `src/graph/type-hierarchy.ts`, following CG-50/CG-51: shared computation in `src/graph/`, presentation in the caller. Its `countImplementers` is now also what `ToolHandler.buildPolymorphicBoundaries` counts with, so "N types implement X" is the same N whether an agent reads it or a person does. `/api/node` carries the block as `hierarchy` rather than a second endpoint — it is part of the Symbol view's first paint, and gated to types, so a function costs one kind test. Layout is arithmetic (24px rows, 22px indent, orthogonal connectors computed from the two): no ResizeObserver, same payload → same picture. The header's `extends X` / `implemented by …` chips are suppressed while the tree is on screen — two renderings of one relation in one column is how a reader ends up trusting neither. `TypeHierarchy` is exported from `@colbymchenry/codegraph-ui` and takes its data as a prop, so a host holding a `WireSymbolPayload` renders it without a second read.
This commit is contained in:
@@ -56,6 +56,48 @@ export interface WireMember extends WireNodeRef {
|
||||
depth: number;
|
||||
fanIn: number;
|
||||
fanOut: number;
|
||||
/** This member redeclares one an ancestor type declares. */
|
||||
overrides?: WireOverride;
|
||||
}
|
||||
|
||||
/** How a subtype is tied to the type above it. */
|
||||
export type WireHierarchyRelation = 'extends' | 'implements';
|
||||
|
||||
/** A member that redeclares an ancestor's — a name match inside a linked chain. */
|
||||
export interface WireOverride {
|
||||
baseId: string;
|
||||
baseTypeId: string;
|
||||
baseTypeName: string;
|
||||
relation: WireHierarchyRelation;
|
||||
}
|
||||
|
||||
/** One type in the hierarchy tree, and the single edge that puts it there. */
|
||||
export interface WireHierarchyNode extends WireNodeRef {
|
||||
/** Steps from the focus, in whichever direction the row sits. 1 = direct. */
|
||||
depth: number;
|
||||
/** The row this one hangs off — the focus's id at depth 1. */
|
||||
parentId: string;
|
||||
relation: WireHierarchyRelation;
|
||||
/** Synthesized rather than parsed (Go's implicit interface satisfaction). */
|
||||
synthesized: boolean;
|
||||
via?: string;
|
||||
registeredAt?: string;
|
||||
/** Direct subtypes of this row that are NOT in the payload. */
|
||||
hiddenSubtypes: number;
|
||||
}
|
||||
|
||||
/** Ancestors up, subtypes down, and the fan an interface call dispatches into. */
|
||||
export interface WireHierarchy {
|
||||
ancestors: WireList<WireHierarchyNode>;
|
||||
descendants: WireList<WireHierarchyNode>;
|
||||
/** True number of DIRECT subtypes, whatever `descendants` was capped to. */
|
||||
direct: number;
|
||||
/** Of `direct`, the ones tied by `implements`. */
|
||||
implementers: number;
|
||||
/** Subtypes exist below what the walk returned. */
|
||||
bounded: boolean;
|
||||
/** A call through this type dispatches at runtime rather than to one target. */
|
||||
polymorphic: boolean;
|
||||
}
|
||||
|
||||
export interface WireEdge {
|
||||
@@ -124,6 +166,8 @@ export interface WireSymbolPayload {
|
||||
/** Outermost first: file, then module/class, then the symbol's own parent. */
|
||||
ancestors: WireNodeRef[];
|
||||
members: WireList<WireMember>;
|
||||
/** The type-hierarchy block. `null` for anything that is not a type, and for a type with none. */
|
||||
hierarchy: WireHierarchy | null;
|
||||
incoming: WireList<WireRelation>;
|
||||
outgoing: WireList<WireRelation>;
|
||||
typesUsed: WireRelation[];
|
||||
|
||||
Reference in New Issue
Block a user