# DiagramInspector

What the reader just picked, written out beside the picture.

- Group: Diagrams
- Import: `import { DiagramInspector } from '@misoto22/folio/diagrams'`
- Page: https://ui.misoto22.com/components/diagram-inspector/
- Related: card, diagram-canvas

## When to reach for it

A node holds about eight words before it stops being a node. Everything past those — the port, the owner, the six relationships — belongs here.

## Anatomy

- **Region** (required) — A <section> named "<title> details" with aria-live="polite", on a panel plate. A region and not a dialog: nothing traps focus and nothing demands dismissal, because the reader clicked a node rather than opening a window.
- **Heading** (required) — The eyebrow over the title — what KIND of thing this is, then what it is called. The eyebrow is where the plate’s own kicker word belongs, so the panel and the picture say the same thing about the same node.
- **Facts** — A definition list, one term and value per row, the value settable in mono for an id, a path, a port. A list rather than a grid of divs, because a grid tells a screen reader nothing about which value belongs to which label.
- **Relationships** — One row per edge the node takes part in, each becoming a real button when it carries onSelect. The arrow glyph is hidden and the direction spelled out beside it, so "to" and "from" are heard rather than guessed.
- **Close** — A small control named for what it does — clearing the selection, not closing a window. It is the only thing that can empty the panel.
- **Actions** — A wrap of caller-supplied buttons under the facts: copy the id, open the source the node was read out of.

## Best practices

### Do

- Pair it with the figure’s onSelectNode. That is what turns the hidden summary list into real buttons, and without it the panel can only ever be filled by a pointer — the plates in the picture are presentational by construction.
- Give every relationship an onSelect. The rows become buttons and the graph becomes walkable peer by peer, which is how a keyboard reader gets from a node to the node it is connected to without going back through the list.
- Move the long facts here and leave about eight words on the plate. A box holding a port, an owning team and a file path grows to fit all three rather than clipping them, and a figure of boxes that size is a document with lines drawn on it.

### Don’t

- Two facts sharing a label are two rows, and neither says which is which. They are keyed by position, so both render — and a panel with "Source" twice leaves the reader deducing from the values what the labels should have said. Name them apart when they mean different things.
- floating pins the panel over the surface, so it covers that corner of the diagram for as long as it is mounted. It has no dismissal contract of its own — passing no onClose leaves the reader nothing to press and the corner hidden until the selection changes.

## Accessibility

- A labelled region with aria-live="polite", not a dialog: the reader clicked a node, they did not open anything, so focus is never trapped or demanded.
- Relationships are real buttons when they carry onSelect, which is how the graph becomes walkable peer by peer from the keyboard.

## DiagramInspector

What the reader just picked, written out. A diagram can hold about eight words per node before it stops being a diagram and starts being a document with lines drawn on it. Everything past those eight — the port, the owning team, the file it was read out of, the six relationships it takes part in — belongs beside the picture rather than inside it, and this is that place. NOT A DIALOG, and that is deliberate. An inspector is a REGION that changes with the selection, not a modal. Giving it `role="dialog"` would trap focus and demand dismissal for something the reader never opened — they clicked a node, and the panel followed. So it is a labelled region with `aria-live="polite"`, which is what makes a screen reader announce the new selection without stealing the cursor from whatever the reader was doing. The relationships are real buttons when they carry `onSelect`, which is how the diagram becomes navigable from the keyboard: a reader can walk the graph peer by peer without ever touching the picture.

### Props

- `eyebrow` — `string`. The kicker over the title — what KIND of thing this panel is showing.
- `title` (required) — `string`.
- `description` — `ReactNode`.
- `facts` — `InspectorFact[]` default `[]`.
- `links` — `InspectorLink[]` default `[]`.
- `actions` — `ReactNode`. Buttons under the facts — copy a link, open the source.
- `onClose` — `() => void`.
- `className` — `string`.
- `floating` — `boolean`. Renders as a floating panel over the surface rather than in the flow.

## Example — default

```tsx
import { DiagramInspector } from '@misoto22/folio/diagrams'

<DiagramInspector
  eyebrow="Service"
  title="API"
  description="FastAPI, behind the load balancer. Owns every read the cache misses."
  facts={[
    { label: 'Port', value: '8000', mono: true },
    { label: 'Region', value: 'ap-southeast-2', mono: true },
    { label: 'Id', value: 'api', mono: true },
  ]}
  links={[
    { direction: 'in', label: 'HTTPS', peer: 'CloudFront' },
    { direction: 'out', label: 'read-through', peer: 'Redis' },
    { direction: 'out', label: 'SQL', peer: 'Postgres' },
  ]}
  onClose={() => {}}
/>
```

## Example — nothing selected

```tsx
import { Button } from '@misoto22/folio'
import { DiagramInspector } from '@misoto22/folio/diagrams'

<div className="flex flex-col gap-3">
  <div className="flex flex-wrap items-center gap-2">
    {NODES.map((entry) => (
      <Button key={entry.id} size="sm" variant="secondary" onClick={() => setSelected(entry.id)}>
        {entry.title}
      </Button>
    ))}
  </div>
  {node && peer ? (
    <DiagramInspector
      eyebrow={node.eyebrow}
      title={node.title}
      description={node.description}
      facts={[
        { label: 'Port', value: node.port, mono: true },
        { label: 'Id', value: node.id, mono: true },
      ]}
      links={[
        {
          direction: node.id === 'worker' ? 'out' : 'in',
          label: 'read-through',
          peer: peer.title,
          onSelect: () => setSelected(peer.id),
        },
      ]}
      onClose={() => setSelected(null)}
    />
  ) : (
    <p className="m-0 rounded-(--radius-lg) border border-dashed border-(--rule-2) p-4 text-[13px] leading-[1.55] text-(--ink-3-aa)">
      Pick a component to see what it is and what reaches it.
    </p>
  )}
</div>
```

## Example — pinned over the figure

```tsx
import { Button } from '@misoto22/folio'
import { ArchitectureFigure, DiagramInspector } from '@misoto22/folio/diagrams'

<div className="relative">
  <ArchitectureFigure spec={SPEC} heading={false} legend="hidden" cards={false} activeIds={['db']} />
  <DiagramInspector
    floating
    eyebrow="Datastore"
    title="Postgres"
    description="The primary, and the end of the request path above it."
    facts={[
      { label: 'Id', value: 'db', mono: true },
      { label: 'Links', value: '1' },
    ]}
    actions={
      <Button
        size="sm"
        variant="secondary"
        onClick={() => {
          void navigator.clipboard?.writeText('#db')
          setCopied(true)
        }}
      >
        {copied ? 'Copied' : 'Copy link'}
      </Button>
    }
  />
</div>
```
