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:
Colby McHenry
2026-08-27 07:14:55 -05:00
parent c15413f200
commit 2a0c6dc58f
21 changed files with 2100 additions and 28 deletions
+6 -1
View File
@@ -20,6 +20,7 @@
import Connectors from '../components/symbol/Connectors.svelte';
import BlastStrip from '../components/symbol/BlastStrip.svelte';
import MembersOutline from '../components/symbol/MembersOutline.svelte';
import TypeHierarchy from '../components/symbol/TypeHierarchy.svelte';
import SourceBlock from '../components/symbol/SourceBlock.svelte';
import SymbolHeader from '../components/symbol/SymbolHeader.svelte';
import DriftBanner from '../components/DriftBanner.svelte';
@@ -560,7 +561,7 @@
<Connectors {connectors} width={overlay.width} height={overlay.height} />
<section class="center" bind:this={centerEl}>
<SymbolHeader {payload} onopen={open} />
<SymbolHeader {payload} onopen={open} relationChips={!payload.hierarchy} />
{#if payload.drift}
<div class="banner">
@@ -578,6 +579,10 @@
</div>
{/if}
{#if payload.hierarchy}
<TypeHierarchy hierarchy={payload.hierarchy} focus={payload.node} onopen={open} />
{/if}
{#if codeBlock}
<SourceBlock
block={codeBlock}