File size: 3,406 Bytes
7c5ed63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { useState } from 'react';

import type { AgentRecord, WireEntry } from '../../types';
import { CopyButton } from '../shared/CopyButton';
import { JsonViewer } from '../shared/JsonViewer';
import { GenericDetail } from './parts';
import { rendererFor } from './renderers';

interface WireRowDetailProps {
  entry: WireEntry;
  /** Scroll to + expand a given line. */
  onJumpTo?: (lineNo: number) => void;
}

type JsonView = 'none' | 'raw' | 'projected';

export function WireRowDetail({ entry }: WireRowDetailProps) {
  const [view, setView] = useState<JsonView>('none');
  // Only offer the dual view when migration actually changed something.
  // For records on the current protocol, `raw` and `data` are identical
  // and the toggle would just be visual noise.
  const migrated = !sameJson(entry.raw, entry.data);

  return (
    <div className="pl-[120px] pr-2 py-1 font-mono text-[12px]">
      {renderFriendly(entry.data)}
      <div className="mt-2 flex items-center justify-end gap-3">
        <CopyButton
          value={JSON.stringify(entry.raw, null, 2)}
          label="copy raw"
        />
        {migrated ? (
          <CopyButton
            value={JSON.stringify(entry.data, null, 2)}
            label="copy projected"
          />
        ) : null}
        <button
          onClick={() => {
            setView((v) => (v === 'raw' ? 'none' : 'raw'));
          }}
          className={`font-mono text-[10px] ${
            view === 'raw' ? 'text-fg-0' : 'text-fg-3 hover:text-fg-1'
          }`}
          title="What this line looks like on disk (no vis-side transforms)"
        >
          {view === 'raw' ? '[ hide raw ]' : '[ {…} raw ]'}
        </button>
        {migrated ? (
          <button
            onClick={() => {
              setView((v) => (v === 'projected' ? 'none' : 'projected'));
            }}
            className={`font-mono text-[10px] ${
              view === 'projected' ? 'text-fg-0' : 'text-fg-3 hover:text-fg-1'
            }`}
            title="Same line after vis applied the engine's wire migration chain"
          >
            {view === 'projected' ? '[ hide projected ]' : '[ {…} projected ]'}
          </button>
        ) : null}
      </div>
      {view !== 'none' ? (
        <div className="mt-2 border border-border bg-surface-0 p-2">
          <div className="mb-1 font-mono text-[10px] uppercase tracking-[0.08em] text-fg-3">
            {view === 'raw' ? 'as written on disk' : 'after vis migration'}
            {migrated && view === 'raw' ? (
              <span className="ml-2 text-[var(--color-sev-warning)]">
                — differs from projected
              </span>
            ) : null}
          </div>
          <JsonViewer
            value={view === 'raw' ? entry.raw : entry.data}
            defaultOpenDepth={2}
          />
        </div>
      ) : null}
    </div>
  );
}

function sameJson(a: unknown, b: unknown): boolean {
  try {
    return JSON.stringify(a) === JSON.stringify(b);
  } catch {
    return false;
  }
}

/** Render the expanded detail for a wire record. Thin dispatch to the per-kind
 *  registry's `detail`; kinds without one fall back to a structured JSON dump. */
function renderFriendly(record: AgentRecord) {
  const renderer = rendererFor(record.type);
  if (renderer?.detail !== undefined) return renderer.detail(record);
  return <GenericDetail value={record} />;
}