code-graph-cli 3.0.3

Code intelligence engine for TypeScript/JavaScript/Rust/Python/Go — query the dependency graph instead of reading source files.
<script lang="ts">
  // Node kind -> color mappings for symbol granularity (17 types matching backend node_color())
  const symbolKinds = [
    { label: 'Function', color: '#7c5cfc' },
    { label: 'Class', color: '#4f8ef7' },
    { label: 'Struct', color: '#5ba3f5' },
    { label: 'Interface', color: '#2dba8c' },
    { label: 'Trait', color: '#26a87e' },
    { label: 'Method', color: '#9b7fe8' },
    { label: 'Enum', color: '#c4853a' },
    { label: 'Component', color: '#c0537a' },
    { label: 'Type', color: '#7a9fd4' },
    { label: 'Property', color: '#8897a8' },
    { label: 'Variable', color: '#8aa0b0' },
    { label: 'Const', color: '#96a8b8' },
    { label: 'Static', color: '#7d8fa0' },
    { label: 'Macro', color: '#a07ab0' },
    { label: 'Module', color: '#5e8bc0' },
    { label: 'File', color: '#6b6090' },
  ] as const;

  // Language -> color mappings for file granularity (matches backend language_color)
  const languageColors = [
    { label: 'TypeScript', color: '#3178C6' },
    { label: 'JavaScript', color: '#E8D44D' },
    { label: 'Rust', color: '#DEA584' },
    { label: 'Python', color: '#3572A5' },
    { label: 'Go', color: '#00ADD8' },
    { label: 'Svelte', color: '#FF3E00' },
    { label: 'HTML', color: '#E34C26' },
    { label: 'CSS', color: '#563D7C' },
    { label: 'Java', color: '#B07219' },
    { label: 'Other', color: '#6B7280' },
  ] as const;

  // Structural node types for file granularity (folder synthesis from Plan 01)
  const fileNodeTypes = [
    { label: 'Folder', color: '#6366f1' },
    { label: 'Module', color: '#5e8bc0' },
  ] as const;

  // Edge type -> color mappings (muted palette matching Plan 02 backend)
  const fileEdgeTypes = [
    { label: 'Imports', color: '#1d4ed8' },
    { label: 'Contains', color: '#2d5a3d' },
    { label: 'Circular', color: '#EF4444' },
  ] as const;

  const symbolEdgeTypes = [
    { label: 'Imports', color: '#1d4ed8' },
    { label: 'Calls', color: '#7c3aed' },
    { label: 'Extends', color: '#c2410c' },
    { label: 'Contains', color: '#2d5a3d' },
    { label: 'Implements', color: '#be185d' },
    { label: 'Circular', color: '#EF4444' },
  ] as const;

  interface Props {
    granularity?: 'file' | 'symbol' | 'package';
    visibleEdgeTypes?: Set<string>;
    onToggleEdgeType?: (type: string) => void;
  }
  let { granularity = 'file', visibleEdgeTypes, onToggleEdgeType }: Props = $props();

  let edgeItems = $derived(granularity === 'file' ? fileEdgeTypes : symbolEdgeTypes);
</script>

<div class="legend">
  <div class="legend-separator"></div>

  {#if granularity === 'file'}
    <div class="legend-section">
      <div class="legend-title">Languages</div>
      {#each languageColors as item}
        <div class="legend-row">
          <span class="legend-dot" style="background: {item.color};"></span>
          <span class="legend-label">{item.label}</span>
        </div>
      {/each}
    </div>
    <div class="legend-section">
      <div class="legend-title">Node Types</div>
      {#each fileNodeTypes as item}
        <div class="legend-row">
          <span class="legend-dot" style="background: {item.color};"></span>
          <span class="legend-label">{item.label}</span>
        </div>
      {/each}
    </div>
  {:else}
    <div class="legend-section">
      <div class="legend-title">Nodes</div>
      {#each symbolKinds as item}
        <div class="legend-row">
          <span class="legend-dot" style="background: {item.color};"></span>
          <span class="legend-label">{item.label}</span>
        </div>
      {/each}
    </div>
  {/if}

  <div class="legend-section">
    <div class="legend-title">Edges</div>
    {#each edgeItems as item}
      {@const isVisible = !visibleEdgeTypes || visibleEdgeTypes.has(item.label)}
      <div
        class="legend-row legend-row-edge {isVisible ? '' : 'legend-row-disabled'}"
        onclick={() => onToggleEdgeType?.(item.label)}
        role="button"
        tabindex="0"
        onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') onToggleEdgeType?.(item.label); }}
        aria-pressed={isVisible}
        aria-label="Toggle {item.label} edges"
        title="{isVisible ? 'Hide' : 'Show'} {item.label} edges"
      >
        <span class="legend-line" style="background: {isVisible ? item.color : '#555'};"></span>
        <span class="legend-label" style="color: {isVisible ? '' : '#555'};">{item.label}</span>
      </div>
    {/each}
  </div>
</div>

<style>
  .legend {
    padding-bottom: 12px;
  }

  .legend-separator {
    height: 1px;
    background: var(--color-border);
    margin: 0 0 10px;
  }

  .legend-section {
    padding: 0 12px;
    margin-bottom: 10px;
  }

  .legend-title {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-text-muted);
    margin-bottom: 6px;
  }

  .legend-row {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 2px 0;
  }

  .legend-row-edge {
    cursor: pointer;
    border-radius: 3px;
    padding: 2px 4px;
    margin: 0 -4px;
    transition: background 100ms ease, opacity 100ms ease;
  }

  .legend-row-edge:hover {
    background: rgba(255, 255, 255, 0.06);
  }

  .legend-row-disabled {
    opacity: 0.35;
  }

  .legend-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
  }

  .legend-line {
    width: 16px;
    height: 2px;
    border-radius: 1px;
    flex-shrink: 0;
  }

  .legend-label {
    font-size: 11px;
    color: var(--color-text-muted);
  }
</style>