PageSourceSearch

https://embed-goodfire-attr-graph.netlify.app/attribution_graph/util-cg.js

js embed-goodfire-attr-graph.netlify.app collected 2026-10-03 10:41:43 UTC 21,215 bytes, 627 lines download raw bytes

1window.utilCg = (function(){
2  function clerpUUID(d){
3    return '🤖' + d.featureIndex
4  }
5
6  function parseClerpUUID(str){
7    var [featureIndex] = str.split('🤖')
8    return {featureIndex}
9  }
10
11  function loadDatapath(urlStr){
12    try {
13      var url = new URL(urlStr)
14      urlStr = url.searchParams.get('datapath') ?? urlStr
15    } catch {}
16    urlStr = urlStr?.replace('index.html', 'data.json').split('?')[0] || 'data.json'
17
18    try {
19      return util.getFile(urlStr)  // seems unused - don't worry about it
20    } catch (exc) {
21      d3.select('body')
22        .html(`Couldn't load data from <code>${urlStr}</code>: ${exc}. Maybe you need to specify a <code>?datapath=</code> argument?`)
23        .st({color: '#c00', fontSize: '150%', padding: '1em', whiteSpace: 'pre-wrap'})
24      throw exc
25    }
26  }
27
28  function saveHClerpsToLocalStorage(hClerps) {
29    const key = 'local-clerp'
30    const hClerpArray = Array.from(hClerps.entries()).filter(d => d[1])
31    localStorage.setItem(key, JSON.stringify(hClerpArray));
32  }
33
34  function getHClerpsFromLocalStorage() {
35    const key = 'local-clerp'
36    // We want to set on load here so that any page load will fix the key.
37    if (localStorage.getItem(key) === null) localStorage.setItem(key, '[]')
38    const hClerpArray = JSON.parse(localStorage.getItem(key))
39      .filter(d => d[0] != clerpUUID({}))
40    return new Map(hClerpArray)
41  }
42
43  async function deDupHClerps() {
44    const remoteClerps = []
45    let remoteMap = new Map(remoteClerps.map(d => {
46      let key = clerpUUID(d);
47      let clerp = d.clerp;
48      return [key, clerp];
49    }));
50
51    let localClerps = getHClerpsFromLocalStorage()
52    let featureLookup = {}
53    data.features.forEach(d => featureLookup[clerpUUID(d)] = d)
54
55    // update feature data with current spreadsheet
56    // (why is this behind the "copy" button?)
57    Array.from(remoteMap).forEach(([key, value]) => {
58      if (featureLookup[key]) featureLookup[key].remoteClerp = value
59    })
60
61    const deDupArray = Array.from(localClerps)
62      .filter(([key, localClerp]) => {
63        let remote = remoteMap.get(key);
64
65        // keep only local clerps that are different from remote
66        if (!remote) return true
67        // gdoc to local storage mangles quotes, don't force strict equality
68        function slugify(d){ return d ? d.replace(/['"]/g, '').trim() : ''}
69        if (slugify(remote) != slugify(localClerp)) return true
70
71        // if local changes are on remote, delete localClerp and set remoteClerp
72        localClerps.delete(key)
73        if (featureLookup[key]) featureLookup[key].localClerp = ''
74      })
75
76    // copy feature.remoteClerp to node.remoteClerp
77    data.nodes?.forEach(node => {
78      var feature = data.features.idToFeature[node.featureId]
79      node.remoteClerp = feature.remoteClerp
80      node.localClerp = feature.localClerp
81    })
82
83    saveHClerpsToLocalStorage(new Map(deDupArray))
84    return new Map(deDupArray);
85  }
86
87  function tabifyHClerps(hClerps) {
88    return []
89  }
90
91  function hClerpUpdateFn(params, data){
92    const localClerps = getHClerpsFromLocalStorage();
93    if (params) {
94      const [node, hClerp] = params;
95      localClerps.set(clerpUUID(node), hClerp)
96      saveHClerpsToLocalStorage(localClerps);
97    }
98
99    data.features.forEach(node => {
100      node.localClerp = localClerps.get(clerpUUID(node))
101      node.ppClerp = node.localClerp || node.remoteClerp || node.clerp;
102    })
103
104    data.nodes?.forEach(node => {
105      var feature = data.features.idToFeature[node.featureId]
106      if (!feature) return
107      node.localClerp = feature.localClerp
108      node.ppClerp = feature.ppClerp
109    })
110  }
111
112  // Adds virtual logit node showing A-B logit difference based on url param logitDiff=⍽tokenA⍽__vs__⍽tokenB⍽
113  function addVirtualDiff(data){
114    // Filter out any previous virtual nodes/links
115    var nodes = data.nodes.filter(d => !d.isJsVirtual)
116    var links = data.links.filter(d => !d.isJsVirtual)
117    nodes.forEach(d => d.logitToken = d.clerp?.split(`"`)[1]?.split(`" k(p=`)[0])
118
119    var [logitAStr, logitBStr] = util.params.get('logitDiff')?.split('__vs__') || []
120    if (!logitAStr || !logitBStr) return {nodes, links}
121    var logitANode = nodes.find(d => d.logitToken == logitAStr)
122    var logitBNode = nodes.find(d => d.logitToken == logitBStr)
123    if (!logitANode || !logitBNode) return {nodes, links}
124
125    var virtualId = `virtual-diff-${logitAStr}
125-vs-${logitBStr}`
126    var diffNode = {
127      ...logitANode,
128      node_id: virtualId,
129      jsNodeId: virtualId,
130      feature: virtualId,
131      isJsVirtual: true,
132      logitToken: `${logitAStr} - ${logitBStr}`,
133      clerp: `Logit diff: ${logitAStr} - ${logitBStr}`,
134    }
135    nodes.push(diffNode)
136
137    var targetLinks = links.filter(d => d.target == logitANode.node_id || d.target == logitBNode.node_id)
138    d3.nestBy(targetLinks, d => d.source).map(sourceLinks => {
139      var linkA = sourceLinks.find(d => d.target == logitANode.node_id)
140      var linkB = sourceLinks.find(d => d.target == logitBNode.node_id)
141
142      links.push({
143        source: sourceLinks[0].source,
144        target: diffNode.node_id,
145        weight: (linkA?.weight || 0) - (linkB?.weight || 0),
146        isJsVirtual: true
147      })
148    })
149
150    return {nodes, links}
151  }
152
153  // Decorates and mutates data.json
154  // - Adds pointers between node and links
155  // - Deletes very common features
156  // - Adds data.features and data.byStream
157  async function formatData(data, visState){
158    var {metadata} = data
159    var {nodes, links} = addVirtualDiff(data)
160
161    var py_node_id_to_node = {}
162    var idToNode = {}
163    var maxLayer = d3.max(nodes.filter(d => d.feature_type != 'logit'), d => +d.layer)
164    nodes.forEach((d, i) => {
165      // To make hover state work across prompts, drop ctx from node id
166      d.featureId = `${d.layer}_${d.feature}`
167
168      d.active_feature_idx = d.feature
169      d.nodeIndex = i
170
171      if (d.feature_type == 'logit') d.layer = maxLayer + 1
172      
173      // TODO: does this handle error nodes correctly?
174      if (d.feature_type == 'unexplored node' && !d.layer != 'E'){
175        d.feature_type = 'cross layer transcoder'
176      }
177      
178      // count from end to align last token on diff prompts
179      d.ctx_from_end = data.metadata.prompt_tokens.length - d.ctx_idx
180
181      // add clerp to embed and error nodes
182      if (d.feature_type.includes('error')){
183        d.isError = true
184
185        if (!d.featureId.includes('__err_idx_')) d.featureId = d.featureId + '__err_idx_' + d.ctx_from_end
186
187        if (d.feature_type == 'mlp reconstruction error'){
188          d.clerp = `Err: mlp “${util.ppToken(data.metadata.prompt_tokens[d.ctx_idx])}"`
189        }
190      } else if (d.feature_type == 'embedding'){
191        d.clerp = `Emb: “${util.ppToken(data.metadata.prompt_tokens[d.ctx_idx])}"`
192      }
193
194      d.url = d.vis_link
195      d.isFeature = true
196
197      d.targetLinks = []
198      d.sourceLinks = []
199
200      // TODO: switch to featureIndex in graphgen
201      d.featureIndex = d.feature
202
203      d.nodeId = d.jsNodeId
204      if (d.feature_type == 'logit' && d.clerp) d.logitPct= +d.clerp.split('(p=')[1].split(')')[0]
205      idToNode[d.nodeId] = d
206      py_node_id_to_node[d.node_id] = d
207
208      if (d.feature_type == 'cross layer transcoder') d.clerp = "Feature " + d.featureIndex // override clerp for now
209    })
210
211    // delete features that occur in than 2/3 of tokens
212    // TODO: more principled way of filtering them out — maybe by feature density?
213    var deletedFeatures = []
214    var byFeatureId = d3.nestBy(nodes, d => d.featureId)
215    byFeatureId.forEach(feature => {
216      if (feature.length > metadata.prompt_tokens.length*2/3){
217        deletedFeatures.push(feature)
218        feature.forEach(d => {
219          delete idToNode[d.nodeId]
220          delete py_node_id_to_node[d.node_id]
221        })
222      }
223    })
224    if (deletedFeatures.length) console.log({deletedFeatures})
225    nodes = nodes.filter(d => idToNode[d.nodeId])
226    nodes = d3.sort(nodes, d => +d.layer)
227
228    links = links.filter(d => py_node_id_to_node[d.source] && py_node_id_to_node[d.target])
229
230    // connect links to nodes
231    links.forEach(link => {
232      link.sourceNode = py_node_id_to_node[link.source]
233      link.targetNode = py_node_id_to_node[link.target]
234
235      link.linkId = link.sourceNode.nodeId + '__' + link.targetNode.nodeId
236
237      link.sourceNode.targetLinks.push(link)
238      link.targetNode.sourceLinks.push(link)
239      link.absWeight = Math.abs(link.weight)
240    })
241    links = d3.sort(links, d => d.absWeight) 
242    
243
244    nodes.forEach(d => {
245      d.inputAbsSum = d3.sum(d.sourceLinks, e => Math.abs(e.weight))
246      d.sourceLinks.forEach(e => e.pctInput = e.weight/d.inputAbsSum)
247      d.inputError = d3.sum(d.sourceLinks.filter(e => e.sourceNode.isError), e => Math.abs(e.weight))
248      d.pctInputError = d.inputError/d.inputAbsSum
249    })
250
251    // convert layer/probe_location_idx to a streamIdx used to position nodes
252    var byStream = d3.nestBy(nodes, d => [d.layer, d.probe_location_idx] + '')
253    byStream = d3.sort(byStream, d => d[0].probe_location_idx)
254    byStream = d3.sort(byStream, d => d[0].layer == 'E' ? -1 : +d[0].layer)
255    byStream.forEach((stream, streamIdx) => {
256      stream.forEach(d => {
257        d.streamIdx = streamIdx
258        d.layerLocationLabel = layerLocationLabel(d.layer, d.probe_location_idx)
259        
260        if (!visState.isHideLayer) d.streamIdx = isFinite(d.layer) ? +d.layer : 0
261      })
262    })
263
264    // add target_logit_effect__ columns for each logit
265    var logitNodeMap = new Map(nodes.filter(d => d.isLogit).map(d => [d.node_id, d.logitToken]))
266    nodes.forEach(node => {
267      node.targetLinks.forEach(link => {
268        if (!logitNodeMap.has(link.target)) return
269        node[`target_logit_effect__${logitNodeMap.get(link.target)}`] = link.weight
270      })
271    })
272
273    // add ppClerp
274    await Promise.all(nodes.map(async d => {
275      if (!d.clerp) d.clerp = ''
276      d.remoteClerp = ''
277    }))
278
279    // condense nodes into features, using last occurence of feature if necessary to point to a node
280    var features = d3.nestBy(nodes.filter(d => d.isFeature), d => d.featureId)
281      .map(d => ({
282        featureId: d[0].featureId,
283        feature_type: d[0].feature_type,
284        clerp: d[0].clerp,
285        remoteClerp: d[0].remoteClerp,
286        layer: d[0].layer,
287        streamIdx: d[0].streamIdx,
288        probe_location_idx: d[0].probe_location_idx,
289        featureIndex: d[0].featureIndex,
290        top_logit_effects: d[0].top_logit_effects,
291        bottom_logit_effects: d[0].bottom_logit_effects,
292        top_embedding_effects: d[0].top_embedding_effects,
293        bottom_embedding_effects: d[0].bottom_embedding_effects,
294        url: d[0].url,
295        lastNodeId: d.at(-1).nodeId,
296        isLogit: d[0].isLogit,
297        isError: d[0].isError,
298        feature_type: d[0].feature_type,
299      }))
300
301    nodes.idToNode = idToNode
302    features.idToFeature = Object.fromEntries(features.map(d => [d.featureId, d]))
303    links.idToLink = Object.fromEntries(links.map(d => [d.linkId, d]))
304
305    Object.assign(data, {nodes, features, links, byStream})
306  }
307
308  function initBcSync({visState, renderAll}){
309    var bcStateSync = window.bcSync = window.bcSync ||  new BroadcastChannel('state-sync')
310
311    function broadcastState(){
312      if (!visState.isSyncEnabled) return
313      bcStateSync.postMessage({
314        pinnedIds: visState.pinnedIds,
315        hiddenIds: visState.hiddenIds,
316        clickedId: visState.clickedId,
317        hoveredId: visState.hoveredId,
318        pageUUID: visState.pageUUID,
319        isSyncEnabled: visState.isSyncEnabled,
320      })
321    }
322
323    renderAll.pinnedIds.fns.push(ev => { if (!ev?.skipBroadcast) broadcastState() })
324    renderAll.hiddenIds.fns.push(ev => { if (!ev?.skipBroadcast) broadcastState() })
325    renderAll.clickedId.fns.push(ev => { if (!ev?.skipBroadcast) broadcastState() })
326    renderAll.hoveredId.fns.push(ev => { if (!ev?.skipBroadcast) broadcastState() })
327
328    bcStateSync.onmessage = ev => {
329      if (!visState.isSyncEnabled) return
330      if (visState.isSyncEnabled != ev.data.isSyncEnabled) return
331      if (ev.data.pageUUID == visState.pageUUID) return
332
333      if (JSON.stringify(visState.pinnedIds) != JSON.stringify(ev.data.pinnedIds)){
334        visState.pinnedIds = ev.data.pinnedIds
335        renderAll.pinnedIds({skipBroadcast: true})
336      }
337
338      if (JSON.stringify(visState.hiddenIds) != JSON.stringify(ev.data.hiddenIds)){
339        visState.hiddenIds = ev.data.hiddenIds
340        renderAll.hiddenIds({skipBroadcast: true})
341      }
342
343      if (visState.clickedId != ev.data.clickedId){
344        visState.clickedId = ev.data.clickedId
345        renderAll.clickedId({skipBroadcast: true})
346      }
347
348      if (visState.hoveredId != ev.data.hoveredId){
349        visState.hoveredId = ev.data.hoveredId
350        renderAll.hoveredId({skipBroadcast: true})
351      }
352    }
353  }
354
355  function addFeatureEvents(visState, renderAll) {
356    return function(selection) {
357      selection
358        .on('mouseover', (ev, d) => {
359          if (ev.shiftKey) return
360          if (visState.subgraph?.activeGrouping.isActive) return
361          ev.preventDefault()
362          hoverFeature(visState, renderAll, d)
363        })
364        .on('mouseout', (ev, d) => {
365          if (ev.shiftKey) return
366          if (visState.subgraph?.activeGrouping.isActive) return
367          ev.preventDefault()
368          unHoverFeature(visState, renderAll)
369        })
370        .on('click', (ev, d) => {
371          if (visState.subgraph?.activeGrouping.isActive) return
372          clickFeature(visState, renderAll, d, ev.metaKey || ev.ctrlKey)
373        })
374    }
375  }
376
377  function hoverFeature(visState, renderAll, d) {
378    if (d.nodeId.includes('supernode-')) return
379
380    if (visState.hoveredId != d.featureId) {
381      visState.hoveredId = d.featureId
382      visState.hoveredCtxIdx = d.ctx_idx
383      renderAll.hoveredId()
384    }
385  }
386
387  function unHoverFeature(visState, renderAll) {
388    if (visState.hoveredId) {
389      visState.hoveredId = null
390      visState.hoveredCtxIdx = null
391      setTimeout(() => {
392        if (!visState.hoveredId) renderAll.hoveredId()
393      })
394    }
395  }
396  function togglePinned(visState, renderAll, d) {
397    var index = visState.pinnedIds.indexOf(d.nodeId)
398    if (index == -1) {
399      visState.pinnedIds.push(d.nodeId)
400    } else {
401      visState.pinnedIds.splice(index, 1)
402    }
403    renderAll.pinnedIds()
404  }
405
406  function clickFeature(visState, renderAll, d, metaKey){
407    console.log(d)
408    if (d.nodeId.includes('supernode-')) return
409    
410    if (metaKey && visState.isEditMode) {
411      togglePinned(visState, renderAll, d) 
412    } else {
413      if (visState.clickedId == d.nodeId) {
414        visState.clickedId = null
415        visState.clickedCtxIdx = null
416      } else {
417        visState.clickedId = d.nodeId
418        visState.clickedCtxIdx = d.ctx_idx
419      }
420      visState.hoveredId = null
421      visState.hoveredCtxIdx = null
422      renderAll.clickedId()
423    }
424  }
425
426  function showTooltip(ev, d) {
427    let tooltipSel = d3.select('.tooltip'),
428        x = ev.clientX,
429        y = ev.clientY,
430        bb = tooltipSel.node().getBoundingClientRect(),
431        left = d3.clamp(20, (x-bb.width/2), window.innerWidth - bb.width - 20),
432        top = innerHeight > y + 20 + bb.height ? y + 20 : y - bb.height - 20;
433
434    let tooltipHtml = !ev.metaKey ? (d.ppClerp || `F#${d.feature}`) : Object.keys(d)
435      .filter(str => typeof d[str] != 'object' && typeof d[str] != 'function' && !keysToSkip.has(str))
436      .map(str => {
437        var val = d[str]
438        if (typeof val == 'number' && !Number.isInteger(val)) val = val.toFixed(6)
439        return `<div>${str}: <b>${val}</b></div>`
440      })
441      .join('')
442
443    tooltipSel
444      .style('left', left +'px')
445      .style('top', top + 'px')
446      .html(tooltipHtml)
447      .classed('tooltip-hidden', false)
448  }
449
450  function addFeatureTooltip(selection){
451    selection
452      .call(d3.attachTooltip, d3.select('.tooltip'), [])
453      .on('mouseover.tt', (ev, d) => {
454        var tooltipHtml = !ev.metaKey ? d.ppClerp : Object.keys(d)
455          .filter(str => typeof d[str] != 'object' && typeof d[str] != 'function' && !keysToSkip.has(str))
456          .map(str => {
457            var val = d[str]
458            if (typeof val == 'number' && !Number.isInteger(val)) val = val.toFixed(6)
459            return `<div>${str}: <b>${val}</b></div>`
460          })
461          .join('')
462
463        d3.select('.tooltip').html(tooltipHtml)
464      })
465  }
466
467  function hideTooltip() {
468    d3.select('.tooltip').classed('tooltip-hidden', true);
469  }
470
471  function updateFeatureStyles(nodeSel){
472    nodeSel.call(classAndRaise('hovered', e => e.featureId == visState.hoveredId))
473
474    var pinnedIdSet = new Set(visState.pinnedIds)
475    nodeSel.call(classAndRaise('pinned', d => pinnedIdSet.has(d.nodeId)))
476
477    var hiddenIdSet = new Set(visState.hiddenIds)
478    nodeSel.call(classAndRaise('hidden', d => hiddenIdSet.has(d.featureId)))
479
480    if (nodeSel.datum().nodeId){
481      nodeSel.call(classAndRaise('clicked', e => e.nodeId === visState.clickedId))
482    } else {
483      nodeSel.call(classAndRaise('clicked', d => d.featureId == visState.clickedId))
484    }
485  }
486
487  function classAndRaise(className, filterFn) {
488    return sel => {
489      sel
490        .classed(className, 0)
491        .filter(filterFn)
492        .classed(className, 1)
493        .raise()
494    }
495  }
496
497  var keysToSkip = new Set([
498    'node_id', 'jsNodeId', 'nodeId', 'layerLocationLabel', 'remoteClerp', 'localClerp', 
499    'tmpClickedTargetLink', 'tmpClickedLink', 'tmpClickedSourceLink',
500    'pos', 'xOffset', 'yOffset', 'sourceLinks', 'targetLinks', 'url', 'vis_link', 'run_idx',
501    'featureId', 'active_feature_idx', 'nodeIndex', 'isFeature', 'Distribution',
502    'clerp', 'ppClerp', 'is_target_logit', 'token_prob', 'reverse_ctx_idx', 'ctx_from_end', 'feature', 'logitToken',
503    'featureIndex', 'streamIdx', 'nodeColor', 'umap_enc_x', 'umap_enc_y', 'umap_dec_x', 'umap_dec_y', 'umap_concat_x', 'umap_concat_y',
504  ])
505  
506
507  function layerLocationLabel(layer, location) {
508    if (layer == 'E') return 'Emb'
509    if (layer == 'E1') return 'Lgt'
510    if (location === -1) return 'logit'
511
512    // TODO: is stream probe_location_idx no longer be saved out?
513    // NOTE: For now, location is literally ProbePointLocation
514    return `L${layer}`
515  }
516
517  var memoize = fn => {
518    var cache = new Map()
519    return (...args) => {
520      var key = JSON.stringify(args)
521      if (cache.has(key)) return cache.get(key)
522      var result = fn(...args)
523      cache.set(key, result)
524      return result
525    }
526  }
527
528  var bgColorToTextColor = memoize((backgroundColor, light='#fff', dark='#000') => {
529    if (!backgroundColor) return ''
530    var hsl = d3.hsl(backgroundColor)
531    return hsl.l > 0.55 ? dark : light
532  })
533
534  // gradient for hover && pinned state
535  function addPinnedClickedGradient(svg){
536    svg.append('defs').html(`
537      <linearGradient id='pinned-clicked-gradient' x1='0' x2='2' gradientUnits='userSpaceOnUse' spreadMethod='repeat'>
538        <stop offset='0'    stop-color='#f0f' />
539        <stop offset='70%'  stop-color='#f0f' />
540        <stop offset='71%'  stop-color='#000' />
541        <stop offset='100%' stop-color='#000' />
542      </linearGradient>
543    `)
544  }
545
546  function renderFeatureRow(sel, visState, renderAll, linkKey='tmpClickedLink'){
547    sel.st({
548      background: d => d[linkKey]?.tmpColor,
549      color: d => bgColorToTextColor(d[linkKey]?.tmpColor, '#eee', '#555'),
550    })
551
552    // add events in a timeout to avoid connection clicks leading to an instant hover
553    setTimeout(() =>
553 sel.call(addFeatureEvents(visState, renderAll)), 16)
554
555    let featureIconSel = sel.append('svg')
556      .at({width: 10, height: 10})
557
558    let featureIcon = featureIconSel.append('g')
559
560    featureIcon.append('g.default-icon').append('text')
561      .text(d => featureTypeToText(d.feature_type))
562      .at({
563        fontSize: 9,
564        textAnchor: 'middle',
565        dominantBaseline: 'central',
566        dx: 5,
567        dy: 4,
568      })
569      .at({fill: d => d[linkKey]?.tmpColor})
570
571
572    sel.append('div.label')
573      .text(d => d.ppClerp)
574      .at({ title: d => d.ppClerp })
575
576    sel
577      .filter(d => d[linkKey] && d[linkKey].tmpClickedCtxOffset != 0)
578      .append('div.ctx-offset')
579      .text(d => d[linkKey].tmpClickedCtxOffset < 0 ? '←' : '→')
580
581    if (!visState.isHideLayer){
582      sel.append('div.layer')
583        .text(d => d.layerLocationLabel ?? layerLocationLabel(d.layer, d.probe_location_idx));
584    }
585
586    sel.append('div.weight')
587      .text(d => d[linkKey] ? d3.format('+.3f')(d[linkKey].pctInput) : '')
588  }
589
590  function featureTypeToText(type){
591    if (type == 'logit') return '■'
592    if (type == 'embedding') return '■'
593    if (type === 'mlp reconstruction error') return '◆'
594    return '●'
595    
596  }
597
598
599  return {
600    loadDatapath,
601    formatData,
602    initBcSync,
603    addFeatureEvents,
604    hoverFeature,
605    unHoverFeature,
606    clickFeature,
607    togglePinned,
608    layerLocationLabel,
609    keysToSkip,
610    addFeatureTooltip,
611    showTooltip,
612    hideTooltip,
613    updateFeatureStyles,
614    memoize,
615    bgColorToTextColor,
616    addPinnedClickedGradient,
617    renderFeatureRow,
618    saveHClerpsToLocalStorage,
619    getHClerpsFromLocalStorage,
620    hClerpUpdateFn,
621    deDupHClerps,
622    tabifyHClerps,
623    featureTypeToText,
624  }
625})()
626
627window.init?.()

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.