summaryrefslogtreecommitdiffhomepage
path: root/scripts/probe-tab.ts
blob: 16ad4237cf992ef560510549e7c791322c35d8be (plain)
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/**
 * Focused probe: load a selected conversation through the FE's REAL pure modules
 * and see whether the tab's render path would throw. Not part of the test suite.
 *
 *   bun scripts/probe-tab.ts <conversationId>
 */
import "fake-indexeddb/auto";
import type {
  ConversationHistoryResponse,
  ConversationMetricsResponse,
} from "@dispatch/transport-contract";
import {
  applyHistory,
  groupRenderedChunks,
  initialState,
  selectChunks,
  selectMessages,
} from "../src/core/chunks/index.ts";
import {
  applyDurableMetrics,
  initialMetricsState,
  selectOrderedTurnMetrics,
} from "../src/core/metrics/index.ts";

const HTTP_BASE = process.env.PROBE_HTTP ?? "http://localhost:24991";
const CONV = process.argv[2];
if (!CONV) {
  console.error("Usage: bun scripts/probe-tab.ts <conversationId>");
  process.exit(1);
}

async function historySync(id: string, sinceSeq: number): Promise<ConversationHistoryResponse> {
  const url = `${HTTP_BASE}/conversations/${encodeURIComponent(id)}?sinceSeq=${sinceSeq}`;
  const res = await fetch(url, { headers: { Origin: "http://localhost:24204" } });
  if (!res.ok) throw new Error(`history fetch ${res.status} for ${url}`);
  return (await res.json()) as ConversationHistoryResponse;
}

async function metricsSync(id: string): Promise<ConversationMetricsResponse> {
  const res = await fetch(`${HTTP_BASE}/conversations/${encodeURIComponent(id)}/metrics`, {
    headers: { Origin: "http://localhost:24204" },
  });
  if (!res.ok) throw new Error(`metrics fetch ${res.status}`);
  return (await res.json()) as ConversationMetricsResponse;
}

let failures = 0;
const record = (name: string, ok: boolean, detail?: string) => {
  if (!ok) failures += 1;
  console.log(`  ${ok ? "OK" : "FAIL"} ${name}${detail ? ` — ${detail}` : ""}`);
};

console.log(`Probing conversation ${CONV} via ${HTTP_BASE}`);

// 1. applyHistory (the tab's cold-load path)
try {
  const hist = await historySync(CONV, 0);
  const chunks = hist.chunks ?? [];
  console.log(`  history: ${chunks.length} chunks, latestSeq=${hist.latestSeq}`);
  let state = initialState();
  try {
    state = applyHistory(state, chunks as never);
    record(
      `applyHistory(${chunks.length} chunks)`,
      true,
      `state=${JSON.stringify(state).length} bytes`,
    );
  } catch (e) {
    record("applyHistory", false, String(e));
  }

  // 2. selectMessages / selectChunks — what the transcript renders
  try {
    const messages = selectMessages(state);
    record("selectMessages", true, `${messages.length} messages`);
  } catch (e) {
    record("selectMessages", false, String(e));
  }

  // 3. groupRenderedChunks — the render grouping
  try {
    const grouped = groupRenderedChunks(selectChunks(state));
    record("groupRenderedChunks", true, `${grouped.length} groups`);
  } catch (e) {
    record("groupRenderedChunks", false, String(e));
  }
} catch (e) {
  record("history load", false, String(e));
}

// 4. metrics path
try {
  const m = await metricsSync(CONV);
  let ms = initialMetricsState();
  const turns = (m as { turns?: unknown[] }).turns ?? [];
  try {
    ms = applyDurableMetrics(ms, turns as never);
  } catch (e) {
    failures += 1;
    console.log(`  FAIL applyDurableMetrics — ${String(e)}`);
  }
  const ordered = selectOrderedTurnMetrics(ms);
  record(
    "metrics fold+apply",
    true,
    `${ordered.length} ordered turn metrics (${turns.length} turns)`,
  );
} catch (e) {
  record("metrics load", false, String(e));
}

console.log(failures === 0 ? "\nALL OK — tab render path is clean." : `\n${failures} FAILURE(S).`);
process.exit(failures === 0 ? 0 : 1);