@opensip-cli/dashboard 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +4 -2
  2. package/dist/client-bundle.generated.d.ts.map +1 -1
  3. package/dist/client-bundle.generated.js +1 -1
  4. package/dist/client-bundle.generated.js.map +1 -1
  5. package/dist/code-paths/graph-view-model.js +0 -1
  6. package/dist/code-paths/graph-view-model.js.map +1 -1
  7. package/dist/css/theme.js +1 -1
  8. package/dist/generator.d.ts +2 -0
  9. package/dist/generator.d.ts.map +1 -1
  10. package/dist/generator.js +10 -4
  11. package/dist/generator.js.map +1 -1
  12. package/dist/report-cup-icon.d.ts +17 -0
  13. package/dist/report-cup-icon.d.ts.map +1 -0
  14. package/dist/report-cup-icon.js +34 -0
  15. package/dist/report-cup-icon.js.map +1 -0
  16. package/dist/tool-tabs-registrations.js +10 -0
  17. package/dist/tool-tabs-registrations.js.map +1 -1
  18. package/package.json +3 -3
  19. package/dist/__tests__/catalog-provenance.test.d.ts +0 -11
  20. package/dist/__tests__/catalog-provenance.test.d.ts.map +0 -1
  21. package/dist/__tests__/catalog-provenance.test.js +0 -112
  22. package/dist/__tests__/catalog-provenance.test.js.map +0 -1
  23. package/dist/__tests__/coupling-attribution.test.d.ts +0 -9
  24. package/dist/__tests__/coupling-attribution.test.d.ts.map +0 -1
  25. package/dist/__tests__/coupling-attribution.test.js +0 -95
  26. package/dist/__tests__/coupling-attribution.test.js.map +0 -1
  27. package/dist/__tests__/dashboard-bundle-weight.test.d.ts +0 -23
  28. package/dist/__tests__/dashboard-bundle-weight.test.d.ts.map +0 -1
  29. package/dist/__tests__/dashboard-bundle-weight.test.js +0 -80
  30. package/dist/__tests__/dashboard-bundle-weight.test.js.map +0 -1
  31. package/dist/__tests__/dashboard-cell-containment.test.d.ts +0 -2
  32. package/dist/__tests__/dashboard-cell-containment.test.d.ts.map +0 -1
  33. package/dist/__tests__/dashboard-cell-containment.test.js +0 -52
  34. package/dist/__tests__/dashboard-cell-containment.test.js.map +0 -1
  35. package/dist/__tests__/dashboard-cytoscape-vendor.test.d.ts +0 -10
  36. package/dist/__tests__/dashboard-cytoscape-vendor.test.d.ts.map +0 -1
  37. package/dist/__tests__/dashboard-cytoscape-vendor.test.js +0 -40
  38. package/dist/__tests__/dashboard-cytoscape-vendor.test.js.map +0 -1
  39. package/dist/__tests__/dashboard-editor-link.test.d.ts +0 -14
  40. package/dist/__tests__/dashboard-editor-link.test.d.ts.map +0 -1
  41. package/dist/__tests__/dashboard-editor-link.test.js +0 -87
  42. package/dist/__tests__/dashboard-editor-link.test.js.map +0 -1
  43. package/dist/__tests__/dashboard-el.test.d.ts +0 -18
  44. package/dist/__tests__/dashboard-el.test.d.ts.map +0 -1
  45. package/dist/__tests__/dashboard-el.test.js +0 -90
  46. package/dist/__tests__/dashboard-el.test.js.map +0 -1
  47. package/dist/__tests__/dashboard-filters.test.d.ts +0 -9
  48. package/dist/__tests__/dashboard-filters.test.d.ts.map +0 -1
  49. package/dist/__tests__/dashboard-filters.test.js +0 -87
  50. package/dist/__tests__/dashboard-filters.test.js.map +0 -1
  51. package/dist/__tests__/dashboard-function-card-singleton.test.d.ts +0 -9
  52. package/dist/__tests__/dashboard-function-card-singleton.test.d.ts.map +0 -1
  53. package/dist/__tests__/dashboard-function-card-singleton.test.js +0 -75
  54. package/dist/__tests__/dashboard-function-card-singleton.test.js.map +0 -1
  55. package/dist/__tests__/dashboard-function-card.test.d.ts +0 -10
  56. package/dist/__tests__/dashboard-function-card.test.d.ts.map +0 -1
  57. package/dist/__tests__/dashboard-function-card.test.js +0 -439
  58. package/dist/__tests__/dashboard-function-card.test.js.map +0 -1
  59. package/dist/__tests__/dashboard-function-row.test.d.ts +0 -11
  60. package/dist/__tests__/dashboard-function-row.test.d.ts.map +0 -1
  61. package/dist/__tests__/dashboard-function-row.test.js +0 -102
  62. package/dist/__tests__/dashboard-function-row.test.js.map +0 -1
  63. package/dist/__tests__/dashboard-generator-graph-catalog.test.d.ts +0 -9
  64. package/dist/__tests__/dashboard-generator-graph-catalog.test.d.ts.map +0 -1
  65. package/dist/__tests__/dashboard-generator-graph-catalog.test.js +0 -77
  66. package/dist/__tests__/dashboard-generator-graph-catalog.test.js.map +0 -1
  67. package/dist/__tests__/dashboard-graph-offline.integration.test.d.ts +0 -13
  68. package/dist/__tests__/dashboard-graph-offline.integration.test.d.ts.map +0 -1
  69. package/dist/__tests__/dashboard-graph-offline.integration.test.js +0 -57
  70. package/dist/__tests__/dashboard-graph-offline.integration.test.js.map +0 -1
  71. package/dist/__tests__/dashboard-graph-scc.test.d.ts +0 -17
  72. package/dist/__tests__/dashboard-graph-scc.test.d.ts.map +0 -1
  73. package/dist/__tests__/dashboard-graph-scc.test.js +0 -89
  74. package/dist/__tests__/dashboard-graph-scc.test.js.map +0 -1
  75. package/dist/__tests__/dashboard-graph-view-model.test.d.ts +0 -12
  76. package/dist/__tests__/dashboard-graph-view-model.test.d.ts.map +0 -1
  77. package/dist/__tests__/dashboard-graph-view-model.test.js +0 -331
  78. package/dist/__tests__/dashboard-graph-view-model.test.js.map +0 -1
  79. package/dist/__tests__/dashboard-help-drawer.test.d.ts +0 -9
  80. package/dist/__tests__/dashboard-help-drawer.test.d.ts.map +0 -1
  81. package/dist/__tests__/dashboard-help-drawer.test.js +0 -95
  82. package/dist/__tests__/dashboard-help-drawer.test.js.map +0 -1
  83. package/dist/__tests__/dashboard-html.test.d.ts +0 -2
  84. package/dist/__tests__/dashboard-html.test.d.ts.map +0 -1
  85. package/dist/__tests__/dashboard-html.test.js +0 -169
  86. package/dist/__tests__/dashboard-html.test.js.map +0 -1
  87. package/dist/__tests__/dashboard-indexes.test.d.ts +0 -11
  88. package/dist/__tests__/dashboard-indexes.test.d.ts.map +0 -1
  89. package/dist/__tests__/dashboard-indexes.test.js +0 -214
  90. package/dist/__tests__/dashboard-indexes.test.js.map +0 -1
  91. package/dist/__tests__/dashboard-pagination.test.d.ts +0 -15
  92. package/dist/__tests__/dashboard-pagination.test.d.ts.map +0 -1
  93. package/dist/__tests__/dashboard-pagination.test.js +0 -140
  94. package/dist/__tests__/dashboard-pagination.test.js.map +0 -1
  95. package/dist/__tests__/dashboard-path-utils.test.d.ts +0 -13
  96. package/dist/__tests__/dashboard-path-utils.test.d.ts.map +0 -1
  97. package/dist/__tests__/dashboard-path-utils.test.js +0 -62
  98. package/dist/__tests__/dashboard-path-utils.test.js.map +0 -1
  99. package/dist/__tests__/dashboard-search.test.d.ts +0 -10
  100. package/dist/__tests__/dashboard-search.test.d.ts.map +0 -1
  101. package/dist/__tests__/dashboard-search.test.js +0 -52
  102. package/dist/__tests__/dashboard-search.test.js.map +0 -1
  103. package/dist/__tests__/dashboard-sessions.test.d.ts +0 -14
  104. package/dist/__tests__/dashboard-sessions.test.d.ts.map +0 -1
  105. package/dist/__tests__/dashboard-sessions.test.js +0 -332
  106. package/dist/__tests__/dashboard-sessions.test.js.map +0 -1
  107. package/dist/__tests__/dashboard-trace.test.d.ts +0 -16
  108. package/dist/__tests__/dashboard-trace.test.d.ts.map +0 -1
  109. package/dist/__tests__/dashboard-trace.test.js +0 -170
  110. package/dist/__tests__/dashboard-trace.test.js.map +0 -1
  111. package/dist/__tests__/dashboard-validation.integration.test.d.ts +0 -14
  112. package/dist/__tests__/dashboard-validation.integration.test.d.ts.map +0 -1
  113. package/dist/__tests__/dashboard-validation.integration.test.js +0 -221
  114. package/dist/__tests__/dashboard-validation.integration.test.js.map +0 -1
  115. package/dist/__tests__/dashboard-view-conformance.test.d.ts +0 -12
  116. package/dist/__tests__/dashboard-view-conformance.test.d.ts.map +0 -1
  117. package/dist/__tests__/dashboard-view-conformance.test.js +0 -52
  118. package/dist/__tests__/dashboard-view-conformance.test.js.map +0 -1
  119. package/dist/__tests__/dashboard-view-coupling.test.d.ts +0 -7
  120. package/dist/__tests__/dashboard-view-coupling.test.d.ts.map +0 -1
  121. package/dist/__tests__/dashboard-view-coupling.test.js +0 -360
  122. package/dist/__tests__/dashboard-view-coupling.test.js.map +0 -1
  123. package/dist/__tests__/dashboard-view-distribution.test.d.ts +0 -10
  124. package/dist/__tests__/dashboard-view-distribution.test.d.ts.map +0 -1
  125. package/dist/__tests__/dashboard-view-distribution.test.js +0 -249
  126. package/dist/__tests__/dashboard-view-distribution.test.js.map +0 -1
  127. package/dist/__tests__/dashboard-view-graph.test.d.ts +0 -13
  128. package/dist/__tests__/dashboard-view-graph.test.d.ts.map +0 -1
  129. package/dist/__tests__/dashboard-view-graph.test.js +0 -299
  130. package/dist/__tests__/dashboard-view-graph.test.js.map +0 -1
  131. package/dist/__tests__/dashboard-view-template.test.d.ts +0 -20
  132. package/dist/__tests__/dashboard-view-template.test.d.ts.map +0 -1
  133. package/dist/__tests__/dashboard-view-template.test.js +0 -206
  134. package/dist/__tests__/dashboard-view-template.test.js.map +0 -1
  135. package/dist/__tests__/dashboard.test.d.ts +0 -2
  136. package/dist/__tests__/dashboard.test.d.ts.map +0 -1
  137. package/dist/__tests__/dashboard.test.js +0 -129
  138. package/dist/__tests__/dashboard.test.js.map +0 -1
  139. package/dist/__tests__/graph-tab.test.d.ts +0 -10
  140. package/dist/__tests__/graph-tab.test.d.ts.map +0 -1
  141. package/dist/__tests__/graph-tab.test.js +0 -78
  142. package/dist/__tests__/graph-tab.test.js.map +0 -1
  143. package/dist/client/el.d.ts +0 -17
  144. package/dist/client/el.d.ts.map +0 -1
  145. package/dist/client/el.js +0 -36
  146. package/dist/client/el.js.map +0 -1
  147. package/dist/code-paths/__tests__/views-registry.test.d.ts +0 -18
  148. package/dist/code-paths/__tests__/views-registry.test.d.ts.map +0 -1
  149. package/dist/code-paths/__tests__/views-registry.test.js +0 -41
  150. package/dist/code-paths/__tests__/views-registry.test.js.map +0 -1
@@ -1,3 +1,3 @@
1
1
  // @generated by scripts/bundle-client.mjs from src/client/*.ts — DO NOT EDIT.
2
- export const DASHBOARD_CLIENT_BUNDLE = "\"use strict\";\n(() => {\n // src/client/el.ts\n function el(tag, attrs, children) {\n const e = document.createElement(tag);\n if (attrs)\n Object.entries(attrs).forEach(([k, v]) => {\n if (k === \"text\") e.textContent = v;\n else if (k === \"class\") e.className = v;\n else if (k.startsWith(\"on\")) e.addEventListener(k.slice(2), v);\n else e.setAttribute(k, v);\n });\n if (children)\n children.forEach((c) => {\n if (typeof c === \"string\" || c) e.append(c);\n });\n return e;\n }\n\n // src/client/path-utils.ts\n function packageOfPath(filePath) {\n if (typeof filePath !== \"string\" || filePath.length === 0) return \"<unknown>\";\n const m = /^packages\\/([^/]+)\\//.exec(filePath);\n return m ? m[1] : \"<unknown>\";\n }\n function shortPkg(name) {\n if (typeof name !== \"string\") return \"<unknown>\";\n return name.codePointAt(0) === 64 ? name.slice(name.indexOf(\"/\") + 1) : name;\n }\n function pkgOf(occ) {\n if (occ && typeof occ.package === \"string\" && occ.package.length > 0)\n return shortPkg(occ.package);\n return packageOfPath(occ ? occ.filePath : \"\");\n }\n function displayName(simpleName) {\n if (typeof simpleName !== \"string\") return \"\";\n const m = /^<([a-z-]+)[:>]/.exec(simpleName);\n if (m) return \"<\" + m[1] + \">\";\n return simpleName;\n }\n\n // src/client/filters.ts\n var filterState = {\n packages: /* @__PURE__ */ new Set(),\n kinds: /* @__PURE__ */ new Set(),\n includeTests: false\n };\n var KIND_LIST = [\n \"function-declaration\",\n \"function-expression\",\n \"method\",\n \"arrow\",\n \"constructor\",\n \"getter\",\n \"setter\",\n \"module-init\"\n ];\n function packagesInCatalog(catalog) {\n const pkgs = /* @__PURE__ */ new Set();\n if (!catalog?.functions) return [];\n for (const name of Object.keys(catalog.functions)) {\n for (const occ of catalog.functions[name] || []) {\n pkgs.add(pkgOf(occ));\n }\n }\n return [...pkgs].sort();\n }\n function passesFilter(occ, fs) {\n if (!fs.includeTests && occ.inTestFile) return false;\n if (fs.packages.size > 0 && !fs.packages.has(pkgOf(occ))) return false;\n if (fs.kinds.size > 0 && !fs.kinds.has(occ.kind ?? \"\")) return false;\n return true;\n }\n\n // src/client/catalog-provenance.ts\n function catalogEngineMode(catalog) {\n const m = /(?:^|\\|)mode=([a-z]+)/.exec(catalog?.cacheKey ?? \"\");\n return m ? m[1] : null;\n }\n function catalogFunctionCount(catalog) {\n if (!catalog?.functions) return 0;\n let n = 0;\n for (const name of Object.keys(catalog.functions)) n += (catalog.functions[name] ?? []).length;\n return n;\n }\n function provenanceRelTime(iso) {\n const t = Date.parse(iso);\n if (Number.isNaN(t)) return \"\";\n const secs = Math.max(0, Math.round((Date.now() - t) / 1e3));\n if (secs < 45) return \"just now\";\n const mins = Math.round(secs / 60);\n if (mins < 60) return mins + \" min\" + (mins === 1 ? \"\" : \"s\") + \" ago\";\n const hrs = Math.round(mins / 60);\n if (hrs < 24) return hrs + \" hour\" + (hrs === 1 ? \"\" : \"s\") + \" ago\";\n const days = Math.round(hrs / 24);\n return days + \" day\" + (days === 1 ? \"\" : \"s\") + \" ago\";\n }\n function provenanceChip(label, value, opts) {\n const o = opts ?? {};\n const chip = el(\"span\", { style: \"display:inline-flex;align-items:baseline;gap:6px\" });\n chip.append(\n el(\"span\", {\n text: label,\n style: \"color:var(--text-dim);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em\"\n })\n );\n chip.append(\n el(\"span\", {\n text: value,\n title: o.title ?? \"\",\n style: \"color:\" + (o.color ?? \"var(--text)\") + \";font-weight:600;font-size:13px\"\n })\n );\n return chip;\n }\n function renderCatalogProvenance(host, catalog) {\n if (!catalog) return;\n const pkgs = packagesInCatalog(catalog);\n const fnCount = catalogFunctionCount(catalog);\n const engine = catalogEngineMode(catalog);\n const builtAtIso = catalog.builtAt;\n const builtAt = builtAtIso ? new Date(builtAtIso) : null;\n const bar = el(\"div\", {\n class: \"catalog-provenance\",\n style: \"display:flex;flex-wrap:wrap;align-items:center;gap:10px 20px;margin:0 0 16px;padding:10px 14px;border:1px solid var(--border);border-radius:8px;background:var(--bg-card)\"\n });\n const pkgLabel = pkgs.length === 1 ? \"1 package\" : String(pkgs.length) + \" packages\";\n const pkgNames = pkgs.length > 0 && pkgs.length <= 4 ? \": \" + pkgs.join(\", \") : \"\";\n bar.append(\n provenanceChip(\"Scope\", pkgLabel + pkgNames, {\n color: \"var(--accent)\",\n title: pkgs.join(\", \")\n })\n );\n bar.append(provenanceChip(\"Functions\", fnCount.toLocaleString()));\n if (builtAtIso && builtAt && !Number.isNaN(builtAt.getTime())) {\n bar.append(\n provenanceChip(\"Built\", provenanceRelTime(builtAtIso), {\n title: builtAt.toLocaleString()\n })\n );\n }\n if (engine) bar.append(provenanceChip(\"Engine\", engine));\n if (catalog.resolutionMode === \"fast\") {\n bar.append(provenanceChip(\"Resolution\", \"fast (approximate)\", { color: \"var(--warning)\" }));\n }\n host.append(bar);\n }\n\n // src/client/catalog-recipes-tables.ts\n function renderGraphRuleCatalog(container, rulesData) {\n if (!rulesData?.length) {\n container.append(el(\"div\", { class: \"empty\", text: \"No rules available.\" }));\n return;\n }\n const table = el(\"table\", { class: \"data-table\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"Rule\", \"Default Severity\", \"Source\"].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n rulesData.forEach((rule) => {\n const row = el(\"tr\");\n row.append(el(\"td\", { text: rule.slug, style: \"font-weight:500\" }));\n const sevCell = el(\"td\");\n const sevColor = rule.defaultSeverity === \"error\" ? \"color:var(--danger)\" : \"color:var(--warning)\";\n sevCell.append(el(\"span\", { text: rule.defaultSeverity, style: sevColor + \";font-size:12px\" }));\n row.append(sevCell);\n const srcCell = el(\"td\");\n srcCell.append(\n el(\"span\", {\n class: \"badge\",\n style: \"background:var(--bg-hover);color:var(--text-muted)\",\n text: rule.source\n })\n );\n row.append(srcCell);\n tbody.append(row);\n });\n table.append(tbody);\n container.append(el(\"div\", { class: \"card\" }, [table]));\n }\n function renderGraphRecipeCatalog(container, recipesData) {\n if (!recipesData?.length) {\n container.append(el(\"div\", { class: \"empty\", text: \"No recipes available.\" }));\n return;\n }\n const table = el(\"table\", { class: \"data-table\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"Recipe\", \"Description\", \"Selector\", \"Tags\"].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n recipesData.forEach((recipe) => {\n const row = el(\"tr\");\n const nameCell = el(\"td\", { style: \"font-weight:500\" });\n nameCell.append(el(\"div\", { text: recipe.displayName }));\n nameCell.append(\n el(\"div\", {\n text: recipe.name,\n style: \"font-size:11px;color:var(--text-dim);font-weight:400\"\n })\n );\n row.append(nameCell);\n row.append(el(\"td\", { text: recipe.description, style: \"color:var(--text-muted)\" }));\n const selCell = el(\"td\");\n selCell.append(\n el(\"span\", {\n class: \"badge\",\n style: \"background:var(--bg-hover);color:var(--text-muted)\",\n text: recipe.selectorType\n })\n );\n row.append(selCell);\n const tagsCell = el(\"td\");\n (recipe.tags ?? []).forEach((t) => {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: t }));\n });\n row.append(tagsCell);\n tbody.append(row);\n });\n table.append(tbody);\n container.append(el(\"div\", { class: \"card\" }, [table]));\n }\n\n // src/client/pagination.ts\n var pageHandlers = /* @__PURE__ */ new WeakMap();\n function wirePagination(paginationContainer, goToPage) {\n const existing = pageHandlers.get(paginationContainer);\n if (existing) {\n existing.current = goToPage;\n return;\n }\n const slot = { current: goToPage };\n pageHandlers.set(paginationContainer, slot);\n paginationContainer.addEventListener(\"click\", (event) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const btn = target.closest(\".pagination-btn[data-page-target]\");\n if (!btn || btn.dataset.pageDisabled === \"yes\") return;\n const page = Number(btn.dataset.pageTarget);\n if (Number.isNaN(page)) return;\n slot.current(page);\n });\n }\n function renderPageButtons(container, currentPage, totalPages) {\n container.append(\n el(\"button\", {\n class: \"pagination-btn\" + (currentPage === 0 ? \" disabled\" : \"\"),\n \"data-page-target\": \"\" + (currentPage - 1),\n \"data-page-disabled\": currentPage === 0 ? \"yes\" : \"no\",\n text: \"\\u2190 Prev\"\n })\n );\n const pages = [];\n for (let p = 0; p < totalPages; p++) {\n if (p < 2 || p >= totalPages - 2 || Math.abs(p - currentPage) <= 1) {\n pages.push(p);\n } else if (pages.length > 0 && pages.at(-1) !== -1) {\n pages.push(-1);\n }\n }\n pages.forEach((p) => {\n if (p === -1) {\n container.append(\n el(\"span\", {\n style: \"color:var(--text-dim);padding:4px 4px;font-size:12px\",\n text: \"\\u2026\"\n })\n );\n } else {\n container.append(\n el(\"button\", {\n class: \"pagination-btn\" + (p === currentPage ? \" active\" : \"\"),\n \"data-page-target\": \"\" + p,\n text: \"\" + (p + 1)\n })\n );\n }\n });\n container.append(\n el(\"button\", {\n class: \"pagination-btn\" + (currentPage >= totalPages - 1 ? \" disabled\" : \"\"),\n \"data-page-target\": \"\" + (currentPage + 1),\n \"data-page-disabled\": currentPage >= totalPages - 1 ? \"yes\" : \"no\",\n text: \"Next \\u2192\"\n })\n );\n }\n function paginateTable(tbody, paginationContainer, pageSize) {\n const rows = [...tbody.children];\n const totalPages = Math.max(1, Math.ceil(rows.length / pageSize));\n function renderPage(currentPage) {\n const start = currentPage * pageSize;\n const end = start + pageSize;\n rows.forEach((row, i) => {\n row.style.display = i >= start && i < end ? \"\" : \"none\";\n });\n while (paginationContainer.firstChild) paginationContainer.firstChild.remove();\n if (rows.length <= pageSize) return;\n const info = el(\"div\", {\n class: \"pagination-info\",\n text: \"Showing \" + (start + 1) + \"-\" + Math.min(end, rows.length) + \" of \" + rows.length\n });\n paginationContainer.append(info);\n const btns = el(\"div\", { class: \"pagination-btns\" });\n renderPageButtons(btns, currentPage, totalPages);\n paginationContainer.append(btns);\n }\n wirePagination(paginationContainer, renderPage);\n renderPage(0);\n }\n function paginateGroupedRows(tbody, paginationContainer, pageSize) {\n const allRows = [...tbody.children];\n const groups = [];\n for (let i = 0; i < allRows.length; i++) {\n const row = allRows[i];\n if (row.classList.contains(\"expander-row\")) continue;\n const group = [row];\n if (i + 1 < allRows.length && allRows[i + 1].classList.contains(\"expander-row\")) {\n group.push(allRows[i + 1]);\n }\n groups.push(group);\n }\n const totalPages = Math.max(1, Math.ceil(groups.length / pageSize));\n function renderPage(currentPage) {\n const start = currentPage * pageSize;\n const end = start + pageSize;\n groups.forEach((group, i) => {\n const visible = i >= start && i < end;\n group.forEach((row) => {\n if (row.classList.contains(\"expander-row\")) {\n row.dataset.paged = visible ? \"yes\" : \"no\";\n if (!visible) row.style.display = \"none\";\n } else {\n row.style.display = visible ? \"\" : \"none\";\n }\n });\n });\n while (paginationContainer.firstChild) paginationContainer.firstChild.remove();\n if (groups.length <= pageSize) return;\n const info = el(\"div\", {\n class: \"pagination-info\",\n text: \"Showing \" + (start + 1) + \"-\" + Math.min(end, groups.length) + \" of \" + groups.length + \" checks\"\n });\n paginationContainer.append(info);\n const btns = el(\"div\", { class: \"pagination-btns\" });\n renderPageButtons(btns, currentPage, totalPages);\n paginationContainer.append(btns);\n }\n wirePagination(paginationContainer, renderPage);\n renderPage(0);\n }\n\n // src/client/checks.ts\n var DIM = \"color:var(--text-dim)\";\n var EM_DASH = \"\\u2014\";\n var EXPANDER_ROW = \"expander-row\";\n var PAGE_SIZE = 10;\n var EMPTY_STAT = { runs: 0, passed: 0, failed: 0, lastRun: null };\n function rateColorFor(rate) {\n if (rate >= 90) return \"var(--success)\";\n if (rate >= 70) return \"var(--warning)\";\n return \"var(--error)\";\n }\n function renderFilteredPage(pag, groups, currentPage, totalPages) {\n groups.forEach((g2) => g2.forEach((r) => r.style.display = \"none\"));\n const start = currentPage * PAGE_SIZE;\n const end = start + PAGE_SIZE;\n groups.slice(start, end).forEach((g2) => g2[0].style.display = \"\");\n while (pag.firstChild) pag.firstChild.remove();\n if (groups.length <= PAGE_SIZE) return;\n pag.append(\n el(\"div\", {\n class: \"pagination-info\",\n text: \"Showing \" + (start + 1) + \"-\" + Math.min(end, groups.length) + \" of \" + groups.length + \" checks\"\n })\n );\n const btns = el(\"div\", { class: \"pagination-btns\" });\n renderPageButtons(btns, currentPage, totalPages);\n pag.append(btns);\n }\n function paginateFilteredGroups(pag, groups) {\n const totalPages = Math.max(1, Math.ceil(groups.length / PAGE_SIZE));\n wirePagination(pag, (p) => renderFilteredPage(pag, groups, p, totalPages));\n renderFilteredPage(pag, groups, 0, totalPages);\n }\n function computeCheckStats() {\n const stats = {};\n for (const s of sessions) {\n const checks = s.payload?.checks ?? [];\n for (const ch of checks) {\n stats[ch.checkSlug] ??= { runs: 0, passed: 0, failed: 0, lastRun: null };\n const st = stats[ch.checkSlug];\n st.runs++;\n if (ch.passed) st.passed++;\n else st.failed++;\n if (!st.lastRun || s.startedAt > st.lastRun) st.lastRun = s.startedAt;\n }\n }\n return stats;\n }\n var checkStats = computeCheckStats();\n function renderLongDesc(text) {\n const container = document.createElement(\"div\");\n container.className = \"check-long-desc\";\n if (!text) return container;\n const parts = text.split(/(\\*\\*[^*]+\\*\\*|`[^`]+`|\\n)/g);\n parts.forEach((part) => {\n if (part === \"\\n\") {\n container.append(document.createElement(\"br\"));\n } else if (part.startsWith(\"**\") && part.endsWith(\"**\")) {\n const strong = document.createElement(\"strong\");\n strong.textContent = part.slice(2, -2);\n container.append(strong);\n } else if (part.startsWith(\"`\") && part.endsWith(\"`\")) {\n const code = document.createElement(\"code\");\n code.textContent = part.slice(1, -1);\n container.append(code);\n } else {\n container.append(part);\n }\n });\n return container;\n }\n function buildFilterBar(sortedTags) {\n const filterBar = el(\"div\", { class: \"filter-bar\" });\n const searchInput = el(\"input\", {\n class: \"search-input\",\n type: \"text\",\n placeholder: \"Search checks...\"\n });\n const tagSelect = el(\"select\", { class: \"filter-select\" });\n tagSelect.append(el(\"option\", { value: \"\", text: \"All tags\" }));\n sortedTags.forEach((t) => tagSelect.append(el(\"option\", { value: t, text: t })));\n const sourceSelect = el(\"select\", { class: \"filter-select\" });\n [\"\", \"built-in\", \"community\"].forEach((v) => {\n sourceSelect.append(el(\"option\", { value: v, text: v || \"All sources\" }));\n });\n filterBar.append(searchInput);\n filterBar.append(tagSelect);\n filterBar.append(sourceSelect);\n return { filterBar, searchInput, tagSelect, sourceSelect };\n }\n function buildRateCell(rate) {\n const rateCell = el(\"td\");\n if (rate >= 0) {\n const rateColor = rateColorFor(rate);\n const bar = el(\"span\", { class: \"pass-rate-bar\" });\n const track = el(\"span\", { class: \"pass-rate-track\" });\n track.append(\n el(\"span\", { class: \"pass-rate-fill\", style: \"width:\" + rate + \"%;background:\" + rateColor })\n );\n bar.append(track);\n bar.append(el(\"span\", { text: rate + \"%\", style: \"font-size:12px;color:\" + rateColor }));\n rateCell.append(bar);\n } else {\n rateCell.textContent = EM_DASH;\n rateCell.style.color = \"var(--text-dim)\";\n }\n return rateCell;\n }\n function buildCheckRow(check, i, uid) {\n const st = checkStats[check.slug] ?? EMPTY_STAT;\n const rate = st.runs > 0 ? Math.round(st.passed / st.runs * 100) : -1;\n const hasDesc = !!check.longDescription;\n const expanderId = uid + \"-exp-\" + i;\n const tags = check.tags ?? [];\n const arrowCell = el(\"td\", {\n style: \"width:24px;text-align:center;\" + DIM + \";font-size:12px\"\n });\n if (hasDesc) arrowCell.textContent = \"\\u25B6\";\n const row = el(\"tr\", {\n class: hasDesc ? \"clickable\" : \"\",\n \"data-slug\": check.slug,\n \"data-tags\": tags.join(\",\"),\n \"data-source\": check.source,\n \"data-name\": check.name.toLowerCase(),\n onclick: hasDesc ? () => {\n const exp = document.querySelector(\"#\" + expanderId);\n if (exp) {\n const isOpen = exp.classList.toggle(\"open\");\n exp.style.display = isOpen ? \"table-row\" : \"none\";\n arrowCell.textContent = isOpen ? \"\\u25BC\" : \"\\u25B6\";\n }\n row.classList.toggle(\"expanded\");\n } : void 0\n });\n row.append(arrowCell);\n const nameCell = el(\"td\", { style: \"font-weight:500\" });\n nameCell.append(check.slug);\n row.append(nameCell);\n const tagsCell = el(\"td\");\n tags.slice(0, 4).forEach((t) => {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: t }));\n });\n if (tags.length > 4) {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: \"+\" + (tags.length - 4) }));\n }\n row.append(tagsCell);\n const confCell = el(\"td\");\n confCell.append(el(\"span\", { class: \"badge badge-\" + check.confidence, text: check.confidence }));\n row.append(confCell);\n const sourceCell = el(\"td\");\n const sourceStyle = check.source === \"built-in\" ? \"color:var(--accent)\" : \"color:var(--accent-sim)\";\n sourceCell.append(el(\"span\", { text: check.source, style: sourceStyle + \";font-size:12px\" }));\n row.append(sourceCell);\n row.append(el(\"td\", { text: st.runs > 0 ? \"\" + st.runs : EM_DASH, style: DIM }));\n row.append(buildRateCell(rate));\n row.append(\n el(\"td\", {\n text: st.lastRun ? new Date(st.lastRun).toLocaleDateString() : EM_DASH,\n style: DIM + \";font-size:12px\"\n })\n );\n const rows = [row];\n if (hasDesc) {\n const expRow = el(\"tr\", {\n id: expanderId,\n class: EXPANDER_ROW,\n \"data-slug\": check.slug,\n \"data-tags\": tags.join(\",\"),\n \"data-source\": check.source,\n \"data-name\": check.name.toLowerCase()\n });\n const expCell = el(\"td\", { colspan: \"8\", style: \"padding:0\" });\n const expContent = el(\"div\", { class: \"expander-content\" });\n expContent.append(renderLongDesc(check.longDescription));\n expCell.append(expContent);\n expRow.append(expCell);\n rows.push(expRow);\n }\n return rows;\n }\n function renderChecksCatalog(panel, catalogData) {\n const entries = catalogData;\n if (entries.length === 0) {\n panel.append(el(\"div\", { class: \"empty\", text: \"No checks registered.\" }));\n return;\n }\n const allTags = /* @__PURE__ */ new Set();\n entries.forEach((c) => (c.tags ?? []).forEach((t) => allTags.add(t)));\n const sortedTags = [...allTags].sort();\n const { filterBar, searchInput, tagSelect, sourceSelect } = buildFilterBar(sortedTags);\n panel.append(filterBar);\n const totalChecks = entries.length;\n const builtinCount = entries.filter((c) => c.source === \"built-in\").length;\n const communityCount = entries.filter((c) => c.source === \"community\").length;\n const statsRow = el(\"div\", {\n style: \"display:flex;gap:16px;margin-bottom:16px;font-size:13px;color:var(--text-muted)\"\n });\n statsRow.append(el(\"span\", { text: totalChecks + \" total checks\" }));\n statsRow.append(el(\"span\", { text: builtinCount + \" built-in\", style: \"color:var(--accent)\" }));\n if (communityCount > 0)\n statsRow.append(\n el(\"span\", { text: communityCount + \" community\", style: \"color:var(--accent-sim)\" })\n );\n panel.append(statsRow);\n const table = el(\"table\", { class: \"data-table sortable\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"\", \"Check\", \"Tags\", \"Confidence\", \"Source\", \"Runs\", \"Pass Rate\", \"Last Run\"].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n const sorted = [...entries].sort((a, b) => a.slug.localeCompare(b.slug));\n const uid = \"cc-\" + Math.random().toString(36).slice(2, 8);\n sorted.forEach((check, i) => {\n buildCheckRow(check, i, uid).forEach((r) => tbody.append(r));\n });\n table.append(tbody);\n const pag = el(\"div\", { class: \"pagination\" });\n const card = el(\"div\", { class: \"card\" }, [table, pag]);\n panel.append(card);\n const emptyMsg = el(\"div\", {\n class: \"empty\",\n style: \"display:none\",\n text: \"No checks match your filters.\"\n });\n pag.before(emptyMsg);\n paginateGroupedRows(tbody, pag, 10);\n const filterVisible = /* @__PURE__ */ new WeakMap();\n function rowMatchesFilters(row) {\n const search = searchInput.value.toLowerCase();\n const tag = tagSelect.value;\n const source = sourceSelect.value;\n const slug = row.dataset.slug ?? \"\";\n const name = row.dataset.name ?? \"\";\n const rowTags = row.dataset.tags ?? \"\";\n const rowSource = row.dataset.source ?? \"\";\n const matchSearch = !search || slug.includes(search) || name.includes(search);\n const matchTag = !tag || rowTags.split(\",\").includes(tag);\n const matchSource = !source || rowSource === source;\n return matchSearch && matchTag && matchSource;\n }\n function collapseExpander(row, next) {\n if (!next?.classList.contains(EXPANDER_ROW)) return;\n next.style.display = \"none\";\n next.classList.remove(\"open\");\n if (!row.classList.contains(\"expanded\")) return;\n row.classList.remove(\"expanded\");\n const arrowTd = row.firstElementChild;\n if (arrowTd) arrowTd.textContent = \"\\u25B6\";\n }\n function markRowVisibility(allRows) {\n let visibleCount = 0;\n for (let i = 0; i < allRows.length; i++) {\n const row = allRows[i];\n if (row.classList.contains(EXPANDER_ROW)) continue;\n const visible = rowMatchesFilters(row);\n row.style.display = visible ? \"\" : \"none\";\n filterVisible.set(row, visible);\n if (visible) visibleCount++;\n collapseExpander(row, allRows[i + 1]);\n }\n return visibleCount;\n }\n function collectVisibleGroups(allRows) {\n const groups = [];\n for (let i = 0; i < allRows.length; i++) {\n const row = allRows[i];\n if (row.classList.contains(EXPANDER_ROW)) continue;\n if (!filterVisible.get(row)) continue;\n const group = [row];\n const next = allRows[i + 1];\n if (next?.classList.contains(EXPANDER_ROW)) group.push(next);\n groups.push(group);\n }\n return groups;\n }\n function applyFilters() {\n const allRows = [...tbody.children];\n const visibleCount = markRowVisibility(allRows);\n emptyMsg.style.display = visibleCount === 0 ? \"\" : \"none\";\n const hasFilters = searchInput.value || tagSelect.value || sourceSelect.value;\n if (hasFilters) {\n paginateFilteredGroups(pag, collectVisibleGroups(allRows));\n } else {\n paginateGroupedRows(tbody, pag, 10);\n }\n }\n searchInput.addEventListener(\"input\", applyFilters);\n tagSelect.addEventListener(\"change\", applyFilters);\n sourceSelect.addEventListener(\"change\", applyFilters);\n }\n\n // src/client/editor-link.ts\n function editorLinkUrl(filePath, line) {\n if (typeof EDITOR_PROTOCOL !== \"string\" || !EDITOR_PROTOCOL) return null;\n if (EDITOR_PROTOCOL === \"vscode\" || EDITOR_PROTOCOL === \"cursor\") {\n return EDITOR_PROTOCOL + \"://file/\" + filePath + \":\" + (line || 1);\n }\n return null;\n }\n\n // src/client/trace.ts\n function inferEntryPointHashes(catalog, indexes) {\n const entries = [];\n if (!catalog?.functions) return entries;\n for (const occ of indexes.byBodyHash.values()) {\n const isCli = occ.filePath === \"packages/cli/src/index.ts\";\n const callerList = indexes.callers.get(occ.bodyHash) ?? [];\n const isExportedRoot = occ.visibility === \"exported\" && callerList.length === 0;\n if (isCli || isExportedRoot) entries.push(occ.bodyHash);\n }\n return entries;\n }\n function traceFromEntry(targetHash, catalog, indexes) {\n if (!targetHash || !indexes?.byBodyHash.has(targetHash)) return null;\n const entries = inferEntryPointHashes(catalog, indexes);\n if (entries.length === 0) return null;\n const queue = [];\n const visited = /* @__PURE__ */ new Set();\n const parent = /* @__PURE__ */ new Map();\n for (const e of entries) {\n queue.push(e);\n visited.add(e);\n }\n while (queue.length > 0) {\n const v = queue.shift();\n if (v === targetHash) {\n const path = [];\n let cur = v;\n while (cur !== void 0) {\n path.unshift(cur);\n cur = parent.get(cur);\n }\n return path;\n }\n const adj = indexes.callees.get(v) ?? [];\n for (const w of adj) {\n if (visited.has(w)) continue;\n visited.add(w);\n parent.set(w, v);\n queue.push(w);\n }\n }\n return null;\n }\n\n // src/client/function-card.ts\n var drillIn = {};\n function occItem(c) {\n return el(\"li\", {\n \"data-body-hash\": c.bodyHash,\n text: displayName(c.simpleName) + \" \\u2014 \" + c.filePath + \":\" + c.line\n });\n }\n function buildMetaRow(occ) {\n const paramText = (occ.params ?? []).map((p) => (p.rest ? \"...\" : \"\") + p.name + (p.optional ? \"?\" : \"\")).join(\", \");\n const metaText = \"Body: \" + Math.max(0, (occ.endLine ?? occ.line ?? 0) - (occ.line ?? 0) + 1) + \" lines \\xB7 \" + (occ.kind ?? \"function\") + \" \\xB7 \" + (occ.visibility ?? \"module-local\") + (paramText ? \" \\xB7 params: (\" + paramText + \")\" : \"\") + (occ.returnType ? \" \\xB7 returns: \" + occ.returnType : \"\");\n return el(\"div\", { class: \"fc-meta\", text: metaText });\n }\n function buildCallersSection(occ) {\n const callerHashes = graphIndexes.callers.get(occ.bodyHash) ?? [];\n const section = el(\"div\", { class: \"fc-section\" });\n section.append(el(\"h4\", { text: \"Callers (\" + callerHashes.length + \")\" }));\n if (callerHashes.length === 0) {\n section.append(el(\"div\", { class: \"empty\", text: \"No callers in catalog.\" }));\n return section;\n }\n const list = el(\"ul\", { class: \"fc-list\" });\n const grouped = /* @__PURE__ */ new Map();\n for (const h of callerHashes) {\n const c = graphIndexes.byBodyHash.get(h);\n if (!c) continue;\n const pkg = pkgOf(c);\n const bucket = grouped.get(pkg);\n if (bucket) bucket.push(c);\n else grouped.set(pkg, [c]);\n }\n for (const pkg of [...grouped.keys()].sort()) {\n const bucket = grouped.get(pkg);\n list.append(el(\"li\", { class: \"external\", text: pkg + \" (\" + bucket.length + \")\" }));\n for (const c of bucket) list.append(occItem(c));\n }\n section.append(list);\n return section;\n }\n function countExternalCalls(occ) {\n return (occ.calls ?? []).reduce((n, e) => {\n let c = 0;\n for (const t of e.to ?? []) if (!graphIndexes.byBodyHash.has(t)) c++;\n return n + c + ((e.to ?? []).length === 0 ? 1 : 0);\n }, 0);\n }\n function buildCalleesSection(occ) {\n const calleeHashes = graphIndexes.callees.get(occ.bodyHash) ?? [];\n const externalCalls = countExternalCalls(occ);\n const section = el(\"div\", { class: \"fc-section\" });\n section.append(\n el(\"h4\", {\n text: \"Callees (\" + calleeHashes.length + \" resolved\" + (externalCalls > 0 ? \", \" + externalCalls + \" external\" : \"\") + \")\"\n })\n );\n if (calleeHashes.length === 0 && externalCalls === 0) {\n section.append(el(\"div\", { class: \"empty\", text: \"No callees.\" }));\n return section;\n }\n const list = el(\"ul\", { class: \"fc-list\" });\n for (const h of calleeHashes) {\n const c = graphIndexes.byBodyHash.get(h);\n if (c) list.append(occItem(c));\n }\n if (externalCalls > 0) {\n list.append(\n el(\"li\", { class: \"external\", text: externalCalls + \" external or unresolved call(s)\" })\n );\n }\n section.append(list);\n return section;\n }\n function buildActions(occ, card) {\n const actions = el(\"div\", { class: \"fc-actions\" });\n const editorUrl = editorLinkUrl(occ.filePath ?? \"\", occ.line ?? 1);\n if (editorUrl) {\n actions.append(el(\"a\", { class: \"fc-action\", href: editorUrl, text: \"Open in editor\" }));\n } else {\n actions.append(\n el(\"button\", {\n class: \"fc-action\",\n text: \"Copy path\",\n onclick: () => {\n if (navigator?.clipboard) {\n void navigator.clipboard.writeText(occ.filePath + \":\" + occ.line);\n }\n }\n })\n );\n }\n actions.append(\n el(\"button\", {\n class: \"fc-action\",\n text: \"Trace from entry\",\n onclick: () => {\n renderTraceInCard(card, traceFromEntry(occ.bodyHash, graphCatalog, graphIndexes));\n }\n })\n );\n return actions;\n }\n function getOrCreateOverlay() {\n const existing = document.querySelector(\".function-card-overlay\");\n if (existing) return existing;\n const overlay = el(\"div\", { class: \"function-card-overlay\" });\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) {\n closeFunctionCard();\n return;\n }\n const target = e.target;\n if (!(target instanceof Element)) return;\n const item = target.closest(\"li[data-body-hash]\");\n const hash = item?.dataset.bodyHash;\n if (hash) drillIn.open?.(hash);\n });\n document.body.append(overlay);\n return overlay;\n }\n function openFunctionCard(bodyHash) {\n if (!bodyHash) return;\n const occ = graphIndexes.byBodyHash.get(bodyHash);\n if (!occ) return;\n const overlay = getOrCreateOverlay();\n while (overlay.firstChild) overlay.firstChild.remove();\n const card = el(\"div\", { class: \"function-card\" });\n overlay.append(card);\n const closeBtn = el(\"button\", { class: \"fc-close\", text: \"\\xD7\", onclick: closeFunctionCard });\n card.append(closeBtn);\n card.append(el(\"h3\", { text: displayName(occ.simpleName ?? \"<anonymous>\") }));\n card.append(el(\"div\", { class: \"fc-loc\", text: occ.filePath + \":\" + occ.line }));\n card.append(buildMetaRow(occ));\n card.append(buildCallersSection(occ));\n card.append(buildCalleesSection(occ));\n card.append(buildActions(occ, card));\n closeBtn.focus();\n }\n function renderTraceInCard(card, path) {\n const old = card.querySelector(\".fc-trace-result\");\n if (old) old.remove();\n const section = el(\"div\", { class: \"fc-section fc-trace-result\" });\n section.append(el(\"h4\", { text: \"Trace from entry point\" }));\n if (!path || path.length === 0) {\n section.append(el(\"div\", { class: \"empty\", text: \"No path from any entry point.\" }));\n } else {\n const list = el(\"ol\", { class: \"fc-list\" });\n for (const h of path) {\n const occ = graphIndexes.byBodyHash.get(h);\n if (!occ) continue;\n list.append(occItem(occ));\n }\n section.append(list);\n }\n card.append(section);\n }\n function closeFunctionCard() {\n const overlay = document.querySelector(\".function-card-overlay\");\n if (overlay) overlay.remove();\n }\n drillIn.open = openFunctionCard;\n\n // src/client/indexes.ts\n function pushToBucket(map, key, value) {\n const bucket = map.get(key);\n if (bucket) bucket.push(value);\n else map.set(key, [value]);\n }\n function indexOccurrences(catalog, byBodyHash, occurrencesByHash, bySimpleName) {\n for (const name of Object.keys(catalog.functions ?? {})) {\n for (const occ of catalog.functions?.[name] ?? []) {\n byBodyHash.set(occ.bodyHash, occ);\n pushToBucket(occurrencesByHash, occ.bodyHash, occ);\n pushToBucket(bySimpleName, name, occ.bodyHash);\n }\n }\n }\n function indexEdges(byBodyHash, callees, callers) {\n for (const occ of byBodyHash.values()) {\n const out = [];\n for (const edge of occ.calls ?? []) {\n for (const target of edge.to ?? []) {\n if (!byBodyHash.has(target)) continue;\n out.push(target);\n pushToBucket(callers, target, occ.bodyHash);\n }\n }\n if (out.length > 0) callees.set(occ.bodyHash, out);\n }\n }\n function buildIndexes(catalog) {\n const byBodyHash = /* @__PURE__ */ new Map();\n const occurrencesByHash = /* @__PURE__ */ new Map();\n const bySimpleName = /* @__PURE__ */ new Map();\n const callees = /* @__PURE__ */ new Map();\n const callers = /* @__PURE__ */ new Map();\n if (!catalog?.functions) {\n return { byBodyHash, occurrencesByHash, bySimpleName, callees, callers };\n }\n indexOccurrences(catalog, byBodyHash, occurrencesByHash, bySimpleName);\n indexEdges(byBodyHash, callees, callers);\n return { byBodyHash, occurrencesByHash, bySimpleName, callees, callers };\n }\n function resolveCalleeOcc(target, callerOcc, indexes) {\n const candidates = indexes.occurrencesByHash?.get(target);\n if (!candidates || candidates.length === 0) return indexes.byBodyHash.get(target);\n if (candidates.length === 1) return candidates[0];\n const callerPkg = pkgOf(callerOcc);\n let samePkg = null;\n let lowest = candidates[0];\n for (const c of candidates) {\n if (!samePkg && pkgOf(c) === callerPkg) samePkg = c;\n if ((c.qualifiedName ?? \"\") < (lowest.qualifiedName ?? \"\")) lowest = c;\n }\n return samePkg ?? lowest;\n }\n\n // src/client/sortable.ts\n function collectRowGroups(tbody) {\n const allRows = [...tbody.children];\n const groups = [];\n for (let i = 0; i < allRows.length; i++) {\n const row = allRows[i];\n if (row.classList.contains(\"expander-row\")) continue;\n const group = [row];\n if (i + 1 < allRows.length && allRows[i + 1].classList.contains(\"expander-row\")) {\n group.push(allRows[i + 1]);\n }\n groups.push(group);\n }\n return groups;\n }\n function compareGroups(a, b, colIdx, asc) {\n const aText = (a[0].children[colIdx]?.textContent || \"\").trim();\n const bText = (b[0].children[colIdx]?.textContent || \"\").trim();\n const aNum = Number.parseFloat(aText);\n const bNum = Number.parseFloat(bText);\n if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) {\n return asc ? aNum - bNum : bNum - aNum;\n }\n const aDate = Date.parse(aText);\n const bDate = Date.parse(bText);\n if (!Number.isNaN(aDate) && !Number.isNaN(bDate)) {\n return asc ? aDate - bDate : bDate - aDate;\n }\n return asc ? aText.localeCompare(bText) : bText.localeCompare(aText);\n }\n function reorderRows(tbody, groups) {\n for (const group of groups) {\n for (const row of group) tbody.append(row);\n }\n }\n function repaginate(table, tbody, groups) {\n const pagContainer = table.parentElement?.querySelector(\".pagination\");\n if (!(pagContainer instanceof HTMLElement)) return;\n const hasExpanders = groups.some((g2) => g2.length > 1);\n if (hasExpanders) {\n paginateGroupedRows(tbody, pagContainer, 10);\n } else {\n paginateTable(tbody, pagContainer, 10);\n }\n }\n function sortByColumn(options) {\n const { table, tbody, headers, th, colIdx, state } = options;\n if (state.col === colIdx) {\n state.asc = !state.asc;\n } else {\n state.col = colIdx;\n state.asc = true;\n }\n for (const h of headers) h.dataset.sort = \"\";\n th.dataset.sort = state.asc ? \"asc\" : \"desc\";\n const groups = collectRowGroups(tbody);\n groups.sort((a, b) => compareGroups(a, b, colIdx, state.asc));\n reorderRows(tbody, groups);\n repaginate(table, tbody, groups);\n }\n function makeSortable(table) {\n const thead = table.querySelector(\"thead\");\n const tbody = table.querySelector(\"tbody\");\n if (!thead || !tbody) return;\n const headers = [...thead.querySelectorAll(\"th\")];\n const state = { col: -1, asc: true };\n headers.forEach((th, colIdx) => {\n if (!th.textContent?.trim()) return;\n th.style.cursor = \"pointer\";\n th.style.userSelect = \"none\";\n th.addEventListener(\"click\", () => {\n sortByColumn({ table, tbody, headers, th, colIdx, state });\n });\n });\n }\n setTimeout(() => {\n document.querySelectorAll(\".data-table.sortable\").forEach((t) => makeSortable(t));\n }, 0);\n\n // src/client/session-detail.ts\n var RULE_METRIC_COLUMNS = {\n \"graph:large-function\": { label: \"Lines\", key: \"bodyLines\" },\n \"graph:high-blast-untested\": { label: \"Score\", key: \"blast\" },\n \"graph:wide-function\": { label: \"Parameters\", key: \"paramCount\" },\n \"graph:cycle\": { label: \"Call Cycle\", key: \"sccSize\" }\n };\n var DIM2 = \"color:var(--text-dim)\";\n var FINDING_CELL_PAD = \"padding:6px 12px\";\n var EM_DASH2 = \"\\u2014\";\n function formatMetricValue(mv) {\n if (typeof mv === \"number\" || typeof mv === \"string\" || typeof mv === \"boolean\") {\n return String(mv);\n }\n return EM_DASH2;\n }\n function formatFindingFile(f) {\n if (!f.filePath) return EM_DASH2;\n return f.line ? f.filePath + \":\" + f.line : f.filePath;\n }\n function countSeverity(check, severity) {\n return check.findings ? check.findings.filter((f) => f.severity === severity).length : 0;\n }\n function sortChecksBySeverity(checks) {\n return [...checks].sort((a, b) => {\n const aErrors = countSeverity(a, \"error\");\n const bErrors = countSeverity(b, \"error\");\n if (bErrors !== aErrors) return bErrors - aErrors;\n return countSeverity(b, \"warning\") - countSeverity(a, \"warning\");\n });\n }\n function buildFindingsTable(check, metricColumn) {\n const fTable = el(\"table\", { class: \"data-table\", style: \"margin:0;border:none\" });\n const fHead = el(\"thead\");\n const fHeaderRow = el(\"tr\");\n const fHeaders = metricColumn ? [\"Severity\", \"File\", metricColumn.label, \"Suggestion\"] : [\"Severity\", \"Message\", \"File\", \"Suggestion\"];\n fHeaders.forEach((h) => {\n fHeaderRow.append(el(\"th\", { text: h, style: \"font-size:11px;padding:6px 12px\" }));\n });\n fHead.append(fHeaderRow);\n fTable.append(fHead);\n const fBody = el(\"tbody\");\n const sevWeight = { error: 0, warning: 1 };\n const sortedFindings = [...check.findings ?? []].sort(\n (a, b) => (sevWeight[a.severity ?? \"\"] ?? 2) - (sevWeight[b.severity ?? \"\"] ?? 2)\n );\n const fileCellStyle = FINDING_CELL_PAD + \";\" + DIM2 + \";font-size:12px\";\n sortedFindings.forEach((f) => {\n const fRow = el(\"tr\");\n const sevCell = el(\"td\", { style: FINDING_CELL_PAD });\n sevCell.append(el(\"span\", { class: \"finding-sev \" + f.severity, text: f.severity }));\n fRow.append(sevCell);\n const fileText = formatFindingFile(f);\n if (metricColumn) {\n fRow.append(el(\"td\", { text: fileText, style: fileCellStyle }));\n const mv = f.metadata ? f.metadata[metricColumn.key] : void 0;\n fRow.append(\n el(\"td\", { text: formatMetricValue(mv), style: FINDING_CELL_PAD + \";font-size:13px\" })\n );\n } else {\n fRow.append(el(\"td\", { text: f.message, style: FINDING_CELL_PAD + \";font-size:13px\" }));\n fRow.append(el(\"td\", { text: fileText, style: fileCellStyle }));\n }\n fRow.append(\n el(\"td\", {\n text: f.suggestion ?? EM_DASH2,\n style: FINDING_CELL_PAD + \";color:var(--accent);font-size:12px\"\n })\n );\n fBody.append(fRow);\n });\n fTable.append(fBody);\n return fTable;\n }\n function detailSubline(session, totalErrors, totalWarnings) {\n const sub = el(\"div\", { style: \"color:var(--text-dim);font-size:12px\" });\n const countParts = [];\n if (totalErrors > 0) countParts.push(totalErrors + \" error\" + (totalErrors === 1 ? \"\" : \"s\"));\n if (totalWarnings > 0)\n countParts.push(totalWarnings + \" warning\" + (totalWarnings === 1 ? \"\" : \"s\"));\n const countsStr = countParts.length > 0 ? \" \\u2014 \" + countParts.join(\", \") : \"\";\n sub.textContent = session.cwd + (session.recipe ? \" \\u2014 recipe: \" + session.recipe : \"\") + countsStr;\n return sub;\n }\n function buildDetailHeader(session, totalErrors, totalWarnings) {\n const headerRow = el(\"div\", {\n style: \"display:flex;align-items:center;justify-content:space-between;margin-bottom:16px\"\n });\n const headerLeft = el(\"div\");\n headerLeft.append(\n el(\"h3\", {\n text: \"Session Detail \\u2014 \" + new Date(session.startedAt).toLocaleString(),\n style: \"margin-bottom:4px\"\n })\n );\n headerLeft.append(detailSubline(session, totalErrors, totalWarnings));\n headerRow.append(headerLeft);\n return headerRow;\n }\n function buildCountCell(value, activeColor) {\n return el(\"td\", { text: \"\" + value, style: value > 0 ? activeColor : DIM2 });\n }\n function buildCheckExpanderRow(check, expanderId, checkStatusVal, itemHeadersLength) {\n const expRow = el(\"tr\", {\n id: expanderId,\n class: \"expander-row\",\n \"data-check-status\": checkStatusVal\n });\n const expCell = el(\"td\", { colspan: \"\" + itemHeadersLength, style: \"padding:0\" });\n const expContent = el(\"div\", { class: \"expander-content\" });\n const fTable = buildFindingsTable(check, RULE_METRIC_COLUMNS[check.checkSlug]);\n const fScroll = el(\"div\", { style: \"overflow-x:auto;max-width:100%\" }, [fTable]);\n expContent.append(fScroll);\n expCell.append(expContent);\n expRow.append(expCell);\n return expRow;\n }\n function buildDetailHead(itemHeaders) {\n const thead = el(\"thead\");\n const thRow = el(\"tr\");\n itemHeaders.forEach((h) => {\n thRow.append(el(\"th\", { text: h }));\n });\n thead.append(thRow);\n return thead;\n }\n function appendCheckRow(detailBody, check, i, ctx) {\n const checkErrors = countSeverity(check, \"error\");\n const checkWarnings = countSeverity(check, \"warning\");\n const findingsTotal = checkErrors + checkWarnings;\n const hasFindings = findingsTotal > 0;\n const expanderId = ctx.filterUid + \"-exp-\" + i;\n const checkStatusVal = check.passed ? \"pass\" : \"fail\";\n const arrowCell = el(\"td\", {\n style: \"width:24px;text-align:center;\" + DIM2 + \";font-size:12px\"\n });\n if (hasFindings) arrowCell.textContent = \"\\u25B6\";\n const row = el(\"tr\", {\n class: hasFindings ? \"clickable\" : \"\",\n \"data-check-status\": checkStatusVal,\n onclick: hasFindings ? () => {\n const exp = document.querySelector(\"#\" + expanderId);\n if (exp) {\n const isOpen = exp.classList.toggle(\"open\");\n exp.style.display = isOpen ? \"table-row\" : \"none\";\n arrowCell.textContent = isOpen ? \"\\u25BC\" : \"\\u25B6\";\n }\n row.classList.toggle(\"expanded\");\n } : void 0\n });\n row.append(arrowCell);\n row.append(el(\"td\", { text: check.checkSlug, style: \"font-weight:500\" }));\n const statusCell = el(\"td\");\n statusCell.append(\n el(\"span\", {\n class: \"badge \" + (check.passed ? \"badge-pass\" : \"badge-fail\"),\n text: check.passed ? \"PASS\" : \"FAIL\"\n })\n );\n row.append(statusCell);\n row.append(buildCountCell(checkErrors, \"color:var(--error)\"));\n row.append(buildCountCell(checkWarnings, \"color:var(--warning)\"));\n row.append(buildCountCell(findingsTotal, \"color:var(--text)\"));\n if (ctx.showDuration)\n row.append(\n el(\"td\", {\n text: (check.durationMs ?? 0) > 0 ? check.durationMs + \"ms\" : \"0ms\",\n style: DIM2\n })\n );\n detailBody.append(row);\n if (hasFindings) {\n detailBody.append(\n buildCheckExpanderRow(check, expanderId, checkStatusVal, ctx.itemHeadersLength)\n );\n }\n }\n function renderNoDetail(detailContainer, session) {\n detailContainer.append(\n el(\"h3\", {\n text: \"Session Detail \\u2014 \" + new Date(session.startedAt).toLocaleString(),\n style: \"margin-bottom:4px\"\n })\n );\n detailContainer.append(\n el(\"div\", { class: \"empty\", text: \"No detail recorded for this session.\" })\n );\n }\n function renderEmptyChecks(detailContainer, session) {\n detailContainer.append(\n el(\"h3\", {\n text: \"Session Detail \\u2014 \" + new Date(session.startedAt).toLocaleString(),\n style: \"margin-bottom:4px\"\n })\n );\n const sm = session.payload?.summary ?? {};\n const clean = (sm.errors ?? 0) === 0 && (sm.warnings ?? 0) === 0;\n const subline = el(\"div\", { style: DIM2 + \";font-size:12px;margin-bottom:12px\" });\n subline.textContent = session.cwd + (session.recipe ? \" \\u2014 recipe: \" + session.recipe : \"\");\n detailContainer.append(subline);\n detailContainer.append(\n el(\"div\", {\n class: \"empty\",\n text: clean ? \"No findings \\u2014 this run was clean. Every rule passed with zero violations.\" : \"No per-rule detail was recorded for this run.\"\n })\n );\n }\n function buildDetailTable(checks, tool, filterUid) {\n const itemColumn = tool === \"graph\" ? \"Rule\" : \"Check\";\n const showDuration = tool !== \"graph\";\n const itemHeaders = [\"\", itemColumn, \"Status\", \"Errors\", \"Warnings\", \"Findings\"];\n if (showDuration) itemHeaders.push(\"Duration\");\n const table = el(\"table\", { class: \"data-table sortable\" });\n table.append(buildDetailHead(itemHeaders));\n const detailBody = el(\"tbody\");\n sortChecksBySeverity(checks).forEach((check, i) => {\n appendCheckRow(detailBody, check, i, {\n filterUid,\n itemHeadersLength: itemHeaders.length,\n showDuration\n });\n });\n table.append(detailBody);\n const detailPag = el(\"div\", { class: \"pagination\" });\n const card = el(\"div\", { class: \"card\" }, [table, detailPag]);\n makeSortable(table);\n paginateGroupedRows(detailBody, detailPag, 10);\n return card;\n }\n function renderSessionDetail(detailContainer, session, idx, tool) {\n detailContainer.style.display = \"block\";\n while (detailContainer.firstChild) detailContainer.firstChild.remove();\n if (!session.payload) {\n renderNoDetail(detailContainer, session);\n return;\n }\n const checks = session.payload.checks ?? [];\n if (checks.length === 0) {\n renderEmptyChecks(detailContainer, session);\n return;\n }\n let totalErrors = 0;\n let totalWarnings = 0;\n checks.forEach((c) => {\n totalErrors += countSeverity(c, \"error\");\n totalWarnings += countSeverity(c, \"warning\");\n });\n detailContainer.append(buildDetailHeader(session, totalErrors, totalWarnings));\n const filterUid = \"df-\" + tool + \"-\" + idx + \"-\" + Math.random().toString(36).slice(2, 6);\n detailContainer.append(buildDetailTable(checks, tool, filterUid));\n }\n\n // src/client/sessions.ts\n var DIM3 = \"color:var(--text-dim)\";\n function scoreColorStyle(score) {\n if (score >= 90) return \"color:var(--success)\";\n if (score >= 70) return \"color:var(--warning)\";\n return \"color:var(--error)\";\n }\n var EMPTY_SUMMARY = { total: 0, passed: 0, failed: 0, errors: 0, warnings: 0 };\n function sessionStatus(s) {\n const sm = s.payload?.summary ?? {};\n if ((sm.failed ?? 0) > 0) return \"fail\";\n if ((sm.warnings ?? 0) > 0) return \"warn\";\n return \"pass\";\n }\n function statusBadge(status) {\n const labels = { fail: \"FAIL\", warn: \"WARN\", pass: \"PASS\" };\n const classes = {\n fail: \"badge-fail\",\n warn: \"badge-warn\",\n pass: \"badge-pass\"\n };\n return el(\"span\", { class: \"badge \" + classes[status], text: labels[status] });\n }\n function buildSessionHead() {\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\n \"Timestamp\",\n \"Recipe\",\n \"Pass Rate\",\n \"Status\",\n \"Passed\",\n \"Failed\",\n \"Findings\",\n \"Duration\"\n ].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n return thead;\n }\n function renderSessionTable(panel, toolSessions, _accentColor) {\n if (toolSessions.length === 0) {\n panel.append(el(\"div\", { class: \"empty\", text: \"No sessions yet.\" }));\n return;\n }\n const tool = toolSessions[0].tool;\n const table = el(\"table\", { class: \"data-table sortable\" });\n table.append(buildSessionHead());\n const detailContainer = el(\"div\", {\n id: \"detail-\" + tool + \"-\" + Math.random().toString(36).slice(2, 8),\n class: \"section\",\n style: \"display:none\"\n });\n const tbody = el(\"tbody\");\n toolSessions.forEach((s, idx) => {\n const sc = scoreColorStyle(s.score);\n const sm = s.payload?.summary ?? EMPTY_SUMMARY;\n const row = el(\"tr\", {\n class: \"clickable\",\n id: \"session-row-\" + tool + \"-\" + idx,\n \"data-session-id\": s.id,\n onclick: () => {\n tbody.querySelectorAll(\"tr.selected\").forEach((r) => r.classList.remove(\"selected\"));\n row.classList.add(\"selected\");\n renderSessionDetail(detailContainer, s, idx, tool);\n }\n });\n row.append(el(\"td\", { class: \"cell-nowrap\", text: new Date(s.startedAt).toLocaleString() }));\n row.append(el(\"td\", { text: s.recipe ?? \"default\", style: \"color:var(--text-muted)\" }));\n const scoreCell = el(\"td\", { style: \"font-weight:600;\" + sc });\n scoreCell.textContent = s.score + \"%\";\n row.append(scoreCell);\n const badgeCell = el(\"td\");\n badgeCell.append(statusBadge(sessionStatus(s)));\n row.append(badgeCell);\n row.append(el(\"td\", { text: \"\" + (sm.passed ?? 0), style: \"color:var(--success)\" }));\n row.append(\n el(\"td\", {\n text: \"\" + (sm.failed ?? 0),\n style: (sm.failed ?? 0) > 0 ? \"color:var(--error)\" : DIM3\n })\n );\n row.append(el(\"td\", { text: \"\" + ((sm.errors ?? 0) + (sm.warnings ?? 0)) }));\n row.append(el(\"td\", { text: (s.durationMs / 1e3).toFixed(1) + \"s\", style: DIM3 }));\n tbody.append(row);\n });\n table.append(tbody);\n const sessionPag = el(\"div\", { class: \"pagination\" });\n const sec = el(\"div\", { class: \"section\" }, [\n el(\"h3\", { text: \"Sessions (\" + toolSessions.length + \")\" }),\n el(\"div\", { class: \"card\" }, [table, sessionPag])\n ]);\n panel.append(sec);\n paginateTable(tbody, sessionPag, 10);\n panel.append(detailContainer);\n renderSessionDetail(detailContainer, toolSessions[0], 0, tool);\n const firstRow = tbody.querySelector(\"tr\");\n if (firstRow) firstRow.classList.add(\"selected\");\n }\n\n // src/client/subtab-bar.ts\n function renderSubtabBar(panel, subtabs) {\n const subtabBar = el(\"div\", { class: \"subtab-bar\" });\n const panels = {};\n subtabs.forEach((t, i) => {\n const subtab = el(\"div\", {\n class: \"subtab\" + (i === 0 ? \" active\" : \"\"),\n \"data-subtab\": t.id,\n text: t.label\n });\n subtabBar.append(subtab);\n const subpanel = el(\"div\", {\n class: \"subtab-panel\" + (i === 0 ? \" active\" : \"\"),\n id: panel.id + \"-\" + t.id\n });\n panels[t.id] = subpanel;\n });\n panel.append(subtabBar);\n subtabs.forEach((t) => panel.append(panels[t.id]));\n subtabBar.addEventListener(\"click\", (e) => {\n const tab = e.target?.closest(\".subtab\");\n if (!tab) return;\n subtabBar.querySelectorAll(\".subtab\").forEach((t) => t.classList.remove(\"active\"));\n tab.classList.add(\"active\");\n subtabs.forEach((t) => panels[t.id].classList.remove(\"active\"));\n panels[tab.dataset.subtab].classList.add(\"active\");\n });\n subtabs.forEach((t) => t.render(panels[t.id]));\n return panels;\n }\n\n // src/client/tab-activators.ts\n var tabActivators = {};\n function registerTabActivator(key, fn) {\n tabActivators[key] = fn;\n }\n function activateTabForSession(session) {\n if (!session) return false;\n const fn = tabActivators[session.tool];\n if (typeof fn !== \"function\") return false;\n fn(session.id);\n return true;\n }\n\n // src/client/views-registry.ts\n var views = [];\n var activeViewId = null;\n function getView(id) {\n for (const v of views) if (v.id === id) return v;\n return null;\n }\n function renderActiveView() {\n if (!activeViewId) return;\n const view = getView(activeViewId);\n if (!view) return;\n const container = document.querySelector(\"#code-paths-view-\" + view.id);\n if (!(container instanceof HTMLElement)) return;\n view.render(container, graphCatalog, graphIndexes, filterState);\n }\n function activateView(id) {\n const view = getView(id);\n if (!view) return;\n activeViewId = id;\n document.querySelectorAll(\".code-paths-tab\").forEach((t) => {\n t.classList.toggle(\"active\", t.dataset.view === id);\n });\n document.querySelectorAll(\".code-paths-view\").forEach((p) => {\n p.classList.toggle(\"active\", p.id === \"code-paths-view-\" + id);\n });\n const next = \"#code-paths/\" + id;\n if (globalThis.window !== void 0 && globalThis.location.hash !== next) {\n try {\n history.replaceState(null, \"\", next);\n } catch {\n }\n }\n renderActiveView();\n if (typeof view.onActivate === \"function\") {\n try {\n view.onActivate();\n } catch {\n }\n }\n }\n\n // src/client/help-drawer.ts\n function openHelpDrawer(viewId) {\n const view = getView(viewId);\n if (!view?.help) return;\n closeHelpDrawer();\n const overlay = el(\"div\", { class: \"help-drawer-overlay\", id: \"help-drawer-overlay\" });\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) closeHelpDrawer();\n });\n const drawer = el(\"aside\", {\n class: \"help-drawer\",\n role: \"dialog\",\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty-string title must also fall back to the label (byte-identical to the legacy emitter).\n \"aria-label\": view.help.title || view.label\n });\n const header = el(\"div\", { class: \"help-drawer-header\" });\n header.append(el(\"h3\", { text: view.help.title || view.label }));\n const closeBtn = el(\"button\", {\n class: \"help-drawer-close\",\n \"aria-label\": \"Close\",\n text: \"\\xD7\",\n onclick: closeHelpDrawer\n });\n header.append(closeBtn);\n drawer.append(header);\n const body = el(\"div\", { class: \"help-drawer-body\" });\n for (const section of view.help.sections ?? []) {\n body.append(el(\"h4\", { text: section.heading }));\n body.append(el(\"p\", { text: section.body }));\n }\n drawer.append(body);\n overlay.append(drawer);\n document.body.append(overlay);\n requestAnimationFrame(() => overlay.classList.add(\"open\"));\n closeBtn.focus();\n }\n function closeHelpDrawer() {\n const existing = document.querySelector(\"#help-drawer-overlay\");\n if (existing) existing.remove();\n }\n document.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\" && document.querySelector(\"#help-drawer-overlay\")) closeHelpDrawer();\n });\n\n // src/client/function-row.ts\n function makeSectionHeading(text, viewId) {\n const h3 = el(\"h3\");\n h3.append(text);\n if (viewId) {\n const info = el(\"button\", {\n class: \"section-info\",\n \"aria-label\": \"About this view\",\n title: \"About this view\",\n text: \"i\"\n });\n info.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n openHelpDrawer(viewId);\n });\n h3.append(info);\n }\n return h3;\n }\n function renderFunctionRows(container, options) {\n const { occurrences, columns, heading, viewId, skipHeading } = options;\n while (container.firstChild) container.firstChild.remove();\n if (!occurrences || occurrences.length === 0) {\n container.append(el(\"div\", { class: \"empty\", text: \"No functions to show.\" }));\n return;\n }\n const headingText = (heading || \"Results\") + \" (\" + occurrences.length + \")\";\n const section = el(\"div\", { class: \"section\" });\n if (!skipHeading) section.append(makeSectionHeading(headingText, viewId));\n const card = el(\"div\", { class: \"card\" });\n const table = el(\"table\", { class: \"data-table sortable\" });\n const thead = el(\"thead\");\n const headRow = el(\"tr\");\n for (const col of columns) headRow.append(el(\"th\", { text: col.label }));\n thead.append(headRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n for (const occ of occurrences) {\n const tr = el(\"tr\", { class: \"clickable\", \"data-body-hash\": occ.bodyHash });\n for (const col of columns) {\n const v = col.value(occ);\n tr.append(el(\"td\", { text: v == null ? \"\" : String(v) }));\n }\n tbody.append(tr);\n }\n table.append(tbody);\n const pag = el(\"div\", { class: \"pagination\" });\n card.append(table);\n card.append(pag);\n section.append(card);\n container.append(section);\n paginateTable(tbody, pag, 10);\n makeSortable(table);\n }\n\n // src/client/view-coupling.ts\n views.push({\n id: \"coupling\",\n label: \"Coupling\",\n help: {\n title: \"Package coupling heat map\",\n sections: [\n {\n heading: \"What this is\",\n body: \"A caller-by-callee matrix. Each cell counts the static call edges from one package into another. Darker shading = more calls. Click a cell to see the actual call sites.\"\n },\n {\n heading: \"Why you care\",\n body: \"Layered architectures want a clear flow of dependencies. Surprises in this matrix \\u2014 a leaf package calling into core, a kernel package calling a peer \\u2014 are usually layering violations or stale abstractions.\"\n },\n {\n heading: \"How to read it\",\n body: 'Read rows as \"this package calls\". Read columns as \"this package is called by\". The diagonal (a package calling itself) is normally densest. Off-diagonal density tells you which packages know about each other; absence of a cell means no call sites in that direction.'\n },\n {\n heading: \"What to do\",\n body: \"Cells you did not expect deserve investigation. If a package is called by everyone (a column with many filled cells), that is a hub \\u2014 make sure its API is intentional. If two peers both call into each other, you may have a circular dependency hiding in plain sight.\"\n }\n ]\n },\n render(container, catalog, indexes, filterState2) {\n while (container.firstChild) container.firstChild.remove();\n if (!catalog?.functions) {\n container.append(el(\"div\", { class: \"empty\", text: \"No catalog loaded.\" }));\n return;\n }\n const features = catalog.features;\n const edges = features?.edge ?? null;\n if (!edges) {\n container.append(\n el(\"div\", {\n class: \"empty\",\n text: \"No coupling data in this catalog. Re-run the graph for a dashboard to compute the package matrix.\"\n })\n );\n return;\n }\n const { counts, max } = buildCounts(edges);\n const pkgs = packageSet(counts);\n if (pkgs.length === 0) {\n container.append(el(\"div\", { class: \"empty\", text: \"No cross-package calls found.\" }));\n return;\n }\n const section = el(\"div\", { class: \"section\" });\n section.append(\n makeSectionHeading(\"Package coupling (\" + pkgs.length + \"\\xD7\" + pkgs.length + \")\", \"coupling\")\n );\n const toolbar = el(\"div\", { class: \"coupling-toolbar\" });\n toolbar.append(\n el(\"button\", {\n class: \"coupling-export-btn\",\n text: \"Export CSV\",\n onclick: () => downloadCouplingCsv(counts)\n })\n );\n section.append(toolbar);\n const card = el(\"div\", { class: \"card\" });\n const scroll = el(\"div\", { class: \"coupling-scroll\" });\n scroll.append(\n buildCouplingTable(\n pkgs,\n counts,\n max,\n (caller, callee) => openCouplingDrilldown(caller, callee, indexes, filterState2)\n )\n );\n card.append(scroll);\n section.append(card);\n container.append(section);\n }\n });\n function buildCounts(edges) {\n const counts = /* @__PURE__ */ new Map();\n let max = 0;\n for (const e of edges) {\n let row = counts.get(e.callerPackage);\n if (!row) {\n row = /* @__PURE__ */ new Map();\n counts.set(e.callerPackage, row);\n }\n row.set(e.calleePackage, e.count);\n if (e.count > max) max = e.count;\n }\n return { counts, max };\n }\n function buildCouplingTable(pkgs, counts, max, onCell) {\n const table = el(\"table\", { class: \"coupling-table\" });\n const thead = el(\"thead\");\n const headRow = el(\"tr\");\n headRow.append(el(\"th\", { class: \"row-label\", text: \"caller \\\\ callee\" }));\n for (const callee of pkgs) headRow.append(el(\"th\", { text: callee }));\n thead.append(headRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n for (const caller of pkgs) {\n const row = el(\"tr\");\n row.append(el(\"th\", { class: \"row-label\", text: caller }));\n const rowCounts = counts.get(caller);\n for (const callee of pkgs) {\n const c = rowCounts?.get(callee) ?? 0;\n if (c === 0) {\n row.append(el(\"td\", { class: \"coupling-cell empty\", text: \"\\xB7\" }));\n } else {\n const density = max > 0 ? (c / max).toFixed(2) : \"0\";\n row.append(\n el(\"td\", {\n class: \"coupling-cell\",\n style: \"--coupling-density: \" + density,\n text: String(c),\n \"data-caller\": caller,\n \"data-callee\": callee,\n onclick: () => onCell(caller, callee)\n })\n );\n }\n }\n tbody.append(row);\n }\n table.append(tbody);\n return table;\n }\n function packageSet(counts) {\n const callees = [];\n for (const m of counts.values()) for (const k of m.keys()) callees.push(k);\n return [.../* @__PURE__ */ new Set([...counts.keys(), ...callees])].sort();\n }\n function buildCouplingCsv(counts) {\n const pkgs = packageSet(counts);\n const header = [csvField(\"caller \\\\ callee\"), ...pkgs.map(csvField)].join(\",\");\n const rows = [header];\n for (const caller of pkgs) {\n const row = counts.get(caller);\n const cells = [csvField(caller)];\n for (const callee of pkgs) cells.push(String(row?.get(callee) ?? 0));\n rows.push(cells.join(\",\"));\n }\n return rows.join(\"\\n\");\n }\n function csvField(value) {\n let s = value;\n if (s.length > 0 && /^[=+\\-@\\t\\r]/.test(s)) s = \"'\" + s;\n if (/[\"\\r\\n,]/.test(s)) return '\"' + s.replaceAll('\"', '\"\"') + '\"';\n return s;\n }\n function downloadCouplingCsv(counts) {\n const csv = buildCouplingCsv(counts);\n try {\n const blob = new Blob([csv], { type: \"text/csv;charset=utf-8\" });\n const url = URL.createObjectURL(blob);\n const a = el(\"a\", { href: url, download: \"coupling.csv\", style: \"display:none\" });\n document.body.append(a);\n a.click();\n a.remove();\n setTimeout(() => {\n try {\n URL.revokeObjectURL(url);\n } catch {\n }\n }, 0);\n } catch {\n }\n }\n function callSitesFromOcc(occ, calleePkg, indexes) {\n const sites = [];\n for (const edge of occ.calls ?? []) {\n for (const target of edge.to ?? []) {\n const callee = resolveCalleeOcc(target, occ, indexes);\n if (callee && pkgOf(callee) === calleePkg) sites.push({ occ, callee, line: edge.line });\n }\n }\n return sites;\n }\n function collectCallSites(callerPkg, calleePkg, indexes, filterState2) {\n const sites = [];\n for (const occ of indexes.byBodyHash.values()) {\n if (!passesFilter(occ, filterState2)) continue;\n if (pkgOf(occ) !== callerPkg) continue;\n sites.push(...callSitesFromOcc(occ, calleePkg, indexes));\n if (sites.length > 200) return sites;\n }\n return sites;\n }\n function openCouplingDrilldown(callerPkg, calleePkg, indexes, filterState2) {\n let overlay = document.querySelector(\".function-card-overlay\");\n if (!overlay) {\n overlay = el(\"div\", { class: \"function-card-overlay\" });\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) closeFunctionCard();\n });\n document.body.append(overlay);\n }\n while (overlay.firstChild) overlay.firstChild.remove();\n const card = el(\"div\", { class: \"function-card\" });\n overlay.append(card);\n card.append(el(\"button\", { class: \"fc-close\", text: \"\\xD7\", onclick: closeFunctionCard }));\n card.append(el(\"h3\", { text: callerPkg + \" \\u2192 \" + calleePkg }));\n card.append(el(\"div\", { class: \"fc-loc\", text: \"Call sites between these packages\" }));\n const list = el(\"ul\", { class: \"fc-list\" });\n const sites = collectCallSites(callerPkg, calleePkg, indexes, filterState2);\n for (const { occ, callee, line } of sites) {\n const item = el(\"li\", {\n \"data-body-hash\": occ.bodyHash,\n text: displayName(occ.simpleName) + \" \\u2192 \" + displayName(callee.simpleName) + \" (\" + occ.filePath + \":\" + line + \")\"\n });\n const hash = occ.bodyHash;\n item.addEventListener(\"click\", () => openFunctionCard(hash));\n list.append(item);\n }\n if (sites.length === 0)\n list.append(el(\"li\", { class: \"external\", text: \"No call sites found.\" }));\n card.append(list);\n }\n\n // src/client/view-template.ts\n function defineRankedView(config) {\n const predicate = config.predicate ?? ((occ, filterState2) => passesFilter(occ, filterState2));\n const rowExtras = config.rowExtras ?? (() => ({}));\n const searchByName = config.searchByName === true;\n const filterByKP = config.filterByKindPackage === true;\n const toggle = config.filterToggle ?? null;\n const hasControls = searchByName || filterByKP || toggle !== null;\n const state = { query: \"\", kind: \"\", pkg: \"\", toggleOn: false };\n function shouldShowRow(occ) {\n if (filterByKP && state.kind && occ.kind !== state.kind) return false;\n if (filterByKP && state.pkg && pkgOf(occ) !== state.pkg) return false;\n const q = searchByName ? state.query.trim().toLowerCase() : \"\";\n if (q.length > 0 && !(occ.simpleName ?? \"\").toLowerCase().includes(q)) return false;\n return !(toggle && state.toggleOn && !toggle.predicate(occ));\n }\n function buildKindPackage(controlsRow, catalog, onChange) {\n controlsRow.append(el(\"span\", { class: \"code-paths-graph-toolbar-label\", text: \"Kind\" }));\n const fnKindSel = el(\"select\", {\n class: \"code-paths-graph-select\",\n \"data-control\": \"fn-kind\"\n });\n fnKindSel.append(el(\"option\", { value: \"\", text: \"All kinds\" }));\n for (const k of KIND_LIST) {\n const o = el(\"option\", { value: k, text: k });\n if (k === state.kind) o.selected = true;\n fnKindSel.append(o);\n }\n fnKindSel.addEventListener(\"change\", (e) => {\n state.kind = e.target.value || \"\";\n onChange();\n });\n controlsRow.append(fnKindSel);\n controlsRow.append(el(\"span\", { class: \"code-paths-graph-toolbar-label\", text: \"Package\" }));\n const fnPkgSel = el(\"select\", {\n class: \"code-paths-graph-select\",\n \"data-control\": \"fn-package\"\n });\n fnPkgSel.append(el(\"option\", { value: \"\", text: \"All packages\" }));\n for (const p of packagesInCatalog(catalog)) {\n const o = el(\"option\", { value: p, text: p });\n if (p === state.pkg) o.selected = true;\n fnPkgSel.append(o);\n }\n fnPkgSel.addEventListener(\"change\", (e) => {\n state.pkg = e.target.value || \"\";\n onChange();\n });\n controlsRow.append(fnPkgSel);\n }\n function buildSearchInput(controlsRow, onChange) {\n const searchInput = el(\"input\", {\n type: \"search\",\n class: \"search-input code-paths-search\",\n id: \"code-paths-search-\" + config.id,\n placeholder: \"Filter functions by name\\u2026\"\n });\n searchInput.value = state.query;\n searchInput.addEventListener(\"input\", (e) => {\n state.query = e.target.value || \"\";\n onChange();\n });\n controlsRow.append(searchInput);\n }\n function buildToggle(controlsRow, t, onChange) {\n const toggleLabel = el(\"label\", { class: \"code-paths-graph-checkbox\" });\n const toggleCb = el(\"input\", {\n type: \"checkbox\",\n \"data-control\": \"fn-toggle\"\n });\n toggleCb.checked = state.toggleOn;\n toggleCb.addEventListener(\"change\", () => {\n state.toggleOn = toggleCb.checked;\n onChange();\n });\n toggleLabel.append(toggleCb);\n toggleLabel.append(\" \" + t.label);\n controlsRow.append(toggleLabel);\n }\n views.push({\n id: config.id,\n label: config.label,\n help: config.help,\n render(container, catalog, indexes, filterState2) {\n while (container.firstChild) container.firstChild.remove();\n if (!catalog?.functions) {\n container.append(el(\"div\", { class: \"empty\", text: \"No catalog loaded.\" }));\n return;\n }\n const ranked = [];\n for (const occ of indexes.byBodyHash.values()) {\n if (!predicate(occ, filterState2)) continue;\n const metric = config.metric(occ, indexes);\n if (metric === false) continue;\n ranked.push({ occ, metric });\n }\n ranked.sort((a, b) => b.metric - a.metric);\n if (ranked.length === 0) {\n container.append(el(\"div\", { class: \"empty\", text: config.emptyMessage }));\n return;\n }\n const headingHost = el(\"div\");\n container.append(headingHost);\n const rowsHost = el(\"div\");\n function renderRows() {\n const filtered = ranked.filter((r) => shouldShowRow(r.occ));\n while (headingHost.firstChild) headingHost.firstChild.remove();\n headingHost.append(\n makeSectionHeading(config.headingText + \" (\" + filtered.length + \")\", config.id)\n );\n if (filtered.length === 0) {\n while (rowsHost.firstChild) rowsHost.firstChild.remove();\n rowsHost.append(el(\"div\", { class: \"empty\", text: config.emptyMessage }));\n return;\n }\n renderFunctionRows(rowsHost, {\n occurrences: filtered.map(\n (r) => ({ ...r.occ, __metric: r.metric, ...rowExtras(r.occ, r.metric) })\n ),\n columns: config.columns,\n heading: config.headingText,\n viewId: config.id,\n skipHeading: true\n });\n }\n if (hasControls) {\n const controlsRow = el(\"div\", { class: \"code-paths-ranked-controls\" });\n if (filterByKP) buildKindPackage(controlsRow, catalog, renderRows);\n if (searchByName) buildSearchInput(controlsRow, renderRows);\n if (toggle) buildToggle(controlsRow, toggle, renderRows);\n container.append(controlsRow);\n }\n container.append(rowsHost);\n renderRows();\n },\n onActivate: searchByName ? () => {\n const input = document.querySelector(\"#code-paths-search-\" + config.id);\n if (input instanceof HTMLInputElement && typeof input.focus === \"function\") input.focus();\n } : void 0\n });\n }\n\n // src/client/view-distribution.ts\n var currentIndexes;\n function lineCount(occ) {\n return Math.max(0, (occ.endLine ?? occ.line ?? 0) - (occ.line ?? 0) + 1);\n }\n function distCallerCount(occ, indexes) {\n return (indexes.callers.get(occ.bodyHash) ?? []).length;\n }\n function distParamCount(occ) {\n return (occ.params ?? []).length;\n }\n function distTestOnly(occ, indexes) {\n if (occ.inTestFile) return false;\n const callers = indexes.callers.get(occ.bodyHash) ?? [];\n if (callers.length === 0) return false;\n return callers.every((h) => {\n const c = indexes.byBodyHash.get(h);\n return c?.inTestFile === true;\n });\n }\n defineRankedView({\n id: \"distribution\",\n label: \"Functions\",\n help: {\n title: \"Functions (distribution)\",\n sections: [\n {\n heading: \"What this is\",\n body: \"Every function in the catalog in one sortable table. Columns cover the metrics the former single-metric tabs ranked individually: body length (lines), inbound callers, parameter count (width), and whether the function is reachable only from tests.\"\n },\n {\n heading: \"Why you care\",\n body: \"Findings surface the rules that actually fired; this table is the raw distribution behind them. It is where you triage the sub-threshold tail \\u2014 a 140-line function under a 150-line gate, a 3-param function just under a width rule \\u2014 that a pass/fail findings list cannot show.\"\n },\n {\n heading: \"How to read it\",\n body: \"Sort by any column (click the header). Lines descending is the default. Callers = 0 plus no test reachability is an orphan; Test-only = yes means production code reached only from tests. Use the filter chips above the tab bar to scope by package, kind, or test-file membership.\"\n },\n {\n heading: \"What to do\",\n body: \"Click a row to open the Function Card and inspect callers/callees. Long bodies split along callee boundaries; wide signatures often want an options object; test-only functions usually belong in a __tests__/ helper.\"\n }\n ]\n },\n // Default ranking metric: body length (lines). Re-sortable to any column.\n // Records the active `indexes` for the Callers column + Test-only toggle (see\n // the `currentIndexes` note above) — metric runs for every row before render.\n metric: (occ, indexes) => {\n currentIndexes = indexes;\n return lineCount(occ);\n },\n columns: [\n { label: \"Function\", value: (o) => displayName(o.simpleName) },\n { label: \"Lines\", value: (o) => lineCount(o) },\n // The indexes closure is captured per-render via the renderer; callers need\n // it, so these columns read it from the module-level binding set on render.\n { label: \"Callers\", value: (o) => distCallerCount(o, currentIndexes) },\n { label: \"Params\", value: (o) => distParamCount(o) },\n // The former 'Test-only' column moved to a \"Test-only\" filter toggle (below).\n { label: \"Kind\", value: (o) => o.kind },\n { label: \"Package\", value: (o) => pkgOf(o) },\n { label: \"File\", value: (o) => o.filePath + \":\" + o.line }\n ],\n headingText: \"Functions\",\n emptyMessage: \"No functions match the active filters.\",\n // Absorbs the former standalone Search subtab: a name filter above the table,\n // re-filtering rows in place by function simple-name.\n searchByName: true,\n // Kind (single-select) + Package (single-select) dropdowns in the same\n // controls row, before the search box: Kind · Package · search.\n filterByKindPackage: true,\n // A \"Test-only\" checkbox after the search box — when checked, narrows the\n // table to production functions reached only from tests (distTestOnly).\n filterToggle: { label: \"Test-only\", predicate: (occ) => distTestOnly(occ, currentIndexes) }\n });\n\n // src/client/search.ts\n function fuzzyMatch(query, names) {\n const q = (query || \"\").trim();\n if (q.length === 0) return [];\n const qLower = q.toLowerCase();\n const out = [];\n for (const name of names) {\n const score = fuzzyScore(qLower, q, name);\n if (score < 0) continue;\n out.push({ name, score });\n }\n out.sort((a, b) => b.score - a.score);\n return out;\n }\n function matchBonus(name, q, i, qi, lastMatchIdx) {\n let bonus = 0;\n if (name[i] === q[qi]) bonus += 1;\n if (i === lastMatchIdx + 1) bonus += 2;\n if (i === 0 && qi === 0) bonus += 50;\n return bonus;\n }\n function fuzzyScore(qLower, q, name) {\n if (typeof name !== \"string\" || name.length === 0) return -1;\n const nameLower = name.toLowerCase();\n let qi = 0;\n let score = 0;\n let lastMatchIdx = -2;\n for (let i = 0; i < name.length && qi < qLower.length; i++) {\n if (nameLower[i] === qLower[qi]) {\n score += matchBonus(name, q, i, qi, lastMatchIdx);\n lastMatchIdx = i;\n qi++;\n }\n }\n if (qi < qLower.length) return -1;\n score -= name.length * 0.01;\n return score;\n }\n\n // src/client/view-graph-state.ts\n var GV_LAYOUTS = [\n { id: \"dagre\", label: \"Dagre (layered)\" },\n { id: \"cose\", label: \"Cose (force)\" },\n { id: \"breadthfirst\", label: \"Breadthfirst\" }\n ];\n var gvState = {\n level: \"package\",\n includeTests: false,\n selectedPackage: null,\n kinds: [],\n crossPackage: false,\n currentLayout: \"dagre\",\n cy: null,\n sccHighlight: false,\n escHandler: null\n };\n\n // src/client/view-graph-controls.ts\n var SELECT_CLASS = \"code-paths-graph-select\";\n function gvAddOptions(sel, pairs, current) {\n for (const [value, label] of pairs) {\n const opt = el(\"option\", { value, text: label });\n if (value === current) opt.selected = true;\n sel.append(opt);\n }\n }\n function gvMultiSelect(opts) {\n const wrap = el(\"div\", { class: \"code-paths-graph-ms\" });\n const selected = [...opts.selected];\n function triggerLabel() {\n if (selected.length === 0) return opts.allLabel;\n if (selected.length === 1) return selected[0];\n return selected.length + \" selected\";\n }\n const trigger = el(\"button\", {\n class: \"code-paths-graph-select code-paths-graph-ms-trigger\",\n \"data-control\": opts.id,\n text: triggerLabel() + \" \\u25BE\"\n });\n trigger.disabled = !!opts.disabled;\n const panel = el(\"div\", { class: \"code-paths-graph-ms-panel\" });\n panel.style.display = \"none\";\n let open = false;\n let docHandler = null;\n function close() {\n if (!open) return;\n open = false;\n panel.style.display = \"none\";\n if (docHandler) {\n document.removeEventListener(\"mousedown\", docHandler);\n docHandler = null;\n }\n opts.onClose([...selected]);\n }\n function openPanel() {\n if (open || opts.disabled) return;\n open = true;\n panel.style.display = \"block\";\n docHandler = (e) => {\n if (!wrap.contains(e.target)) close();\n };\n document.addEventListener(\"mousedown\", docHandler);\n }\n trigger.addEventListener(\"click\", () => {\n if (open) close();\n else openPanel();\n });\n for (const item of opts.items) {\n const row = el(\"label\", { class: \"code-paths-graph-ms-item\" });\n const cb = el(\"input\", { type: \"checkbox\" });\n cb.checked = selected.includes(item);\n cb.addEventListener(\"change\", () => {\n const ix = selected.indexOf(item);\n if (cb.checked && ix === -1) selected.push(item);\n else if (!cb.checked && ix !== -1) selected.splice(ix, 1);\n trigger.textContent = triggerLabel() + \" \\u25BE\";\n });\n row.append(cb);\n row.append(\" \" + item);\n panel.append(row);\n }\n wrap.append(trigger);\n wrap.append(panel);\n return wrap;\n }\n function gvRenderCyclesToggle(host, applySccHighlight) {\n const sccToggle = el(\"label\", { class: \"code-paths-graph-checkbox\" });\n const sccCb = el(\"input\", { type: \"checkbox\", \"data-scc-toggle\": \"1\" });\n sccCb.checked = gvState.sccHighlight;\n sccCb.addEventListener(\"change\", () => {\n gvState.sccHighlight = sccCb.checked;\n applySccHighlight();\n });\n sccToggle.append(sccCb);\n sccToggle.append(\" Highlight cycles\");\n host.append(sccToggle);\n }\n function gvRenderControls(host, catalog, indexes, handlers) {\n const { rerender, runLayout, applySccHighlight, renderSearchBox } = handlers;\n const fnLevel = gvState.level === \"function\";\n const grid = el(\"div\", { class: \"code-paths-graph-grid\" });\n function cell(labelText, control) {\n const c = el(\"div\", { class: \"code-paths-graph-cell\" });\n if (labelText)\n c.append(el(\"span\", { class: \"code-paths-graph-toolbar-label\", text: labelText }));\n if (control) c.append(control);\n grid.append(c);\n return c;\n }\n const layoutSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"layout\"\n });\n gvAddOptions(\n layoutSel,\n GV_LAYOUTS.map((l) => [l.id, l.label]),\n gvState.currentLayout\n );\n layoutSel.addEventListener(\"change\", (e) => {\n runLayout(e.target.value);\n });\n cell(\"Layout\", layoutSel);\n const scopeSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"scope\"\n });\n gvAddOptions(\n scopeSel,\n [\n [\"prod\", \"Production only\"],\n [\"tests\", \"Include tests\"]\n ],\n gvState.includeTests ? \"tests\" : \"prod\"\n );\n scopeSel.addEventListener(\"change\", (e) => {\n gvState.includeTests = e.target.value === \"tests\";\n rerender();\n });\n cell(\"Scope\", scopeSel);\n const searchCell = el(\"div\", { class: \"code-paths-graph-cell code-paths-graph-cell-search\" });\n renderSearchBox(searchCell);\n grid.append(searchCell);\n const cyclesCell = el(\"div\", { class: \"code-paths-graph-cell\" });\n gvRenderCyclesToggle(cyclesCell, applySccHighlight);\n grid.append(cyclesCell);\n const levelSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"level\"\n });\n gvAddOptions(\n levelSel,\n [\n [\"package\", \"Package\"],\n [\"function\", \"Function\"]\n ],\n gvState.level\n );\n levelSel.addEventListener(\"change\", (e) => {\n gvState.level = e.target.value;\n rerender();\n });\n cell(\"Level\", levelSel);\n const pkgs = packagesInCatalog(catalog);\n const pkgSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"package\"\n });\n pkgSel.append(el(\"option\", { value: \"\", text: pkgs.length > 0 ? \"\\u2014 select \\u2014\" : \"\\u2014 none \\u2014\" }));\n gvAddOptions(\n pkgSel,\n pkgs.map((p) => [p, p]),\n gvState.selectedPackage\n );\n pkgSel.disabled = !fnLevel;\n pkgSel.addEventListener(\"change\", (e) => {\n gvState.selectedPackage = e.target.value || null;\n rerender();\n });\n cell(\"Package\", pkgSel);\n cell(\n \"Kind\",\n gvMultiSelect({\n id: \"kind\",\n items: KIND_LIST,\n selected: gvState.kinds,\n allLabel: \"All kinds\",\n disabled: !fnLevel,\n onClose: (sel) => {\n gvState.kinds = sel;\n rerender();\n }\n })\n );\n const edgeSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"granularity\"\n });\n gvAddOptions(\n edgeSel,\n [\n [\"intra\", \"Intra-package\"],\n [\"cross\", \"+ cross-package\"]\n ],\n gvState.crossPackage ? \"cross\" : \"intra\"\n );\n edgeSel.disabled = !fnLevel;\n edgeSel.addEventListener(\"change\", (e) => {\n gvState.crossPackage = e.target.value === \"cross\";\n rerender();\n });\n cell(\"Edges\", edgeSel);\n host.append(grid);\n }\n function gvBuildFunctionElements(indexes, pkg, includeTests, kinds, crossPackage) {\n const elements = [];\n if (!indexes?.occurrencesByHash || !indexes.callees) return elements;\n const kindSet = kinds && kinds.length > 0 ? kinds : null;\n function passes(occ) {\n if (!includeTests && occ.inTestFile) return false;\n if (kindSet && !kindSet.includes(occ.kind ?? \"\")) return false;\n return true;\n }\n const seeds = [];\n const seenSeed = {};\n indexes.occurrencesByHash.forEach((occs) => {\n for (const occ of occs) {\n if (pkgOf(occ) === pkg && passes(occ)) {\n if (!seenSeed[occ.bodyHash]) {\n seenSeed[occ.bodyHash] = true;\n seeds.push(occ);\n }\n break;\n }\n }\n });\n const nodeIds = {};\n const degree = {};\n function addNode(occ, external) {\n if (nodeIds[occ.bodyHash]) return;\n nodeIds[occ.bodyHash] = true;\n degree[occ.bodyHash] ??= 0;\n elements.push({\n group: \"nodes\",\n data: {\n id: occ.bodyHash,\n label: displayName(occ.simpleName),\n external: external ? 1 : 0,\n totalCoupling: 0\n }\n });\n }\n for (const seed of seeds) addNode(seed, false);\n const edgeSeen = {};\n seeds.forEach((seed, s2) => {\n const targets = indexes.callees.get(seed.bodyHash) ?? [];\n targets.forEach((target, t) => {\n const callee = resolveCalleeOcc(target, seed, indexes);\n if (!callee) return;\n const external = pkgOf(callee) !== pkg;\n if (external && !crossPackage) return;\n if (!external && !passes(callee)) return;\n addNode(callee, external);\n const ekey = seed.bodyHash + \"\\n\" + callee.bodyHash;\n if (edgeSeen[ekey]) return;\n edgeSeen[ekey] = true;\n elements.push({\n group: \"edges\",\n data: {\n id: \"fe\" + s2 + \"_\" + t,\n source: seed.bodyHash,\n target: callee.bodyHash,\n weight: 1,\n isCycleEdge: false\n }\n });\n degree[seed.bodyHash] = (degree[seed.bodyHash] || 0) + 1;\n degree[callee.bodyHash] = (degree[callee.bodyHash] || 0) + 1;\n });\n });\n for (const elem of elements) {\n if (elem.group === \"nodes\") elem.data.totalCoupling = degree[elem.data.id] || 0;\n }\n return elements;\n }\n\n // src/client/view-graph-elements.ts\n function gvSccColor(sccId) {\n if (!sccId) return null;\n let h = 0;\n for (let i = 0; i < sccId.length; i++) {\n h = (h * 31 + (sccId.codePointAt(i) ?? 0)) % 360;\n }\n return \"hsl(\" + h + \", 70%, 55%)\";\n }\n function gvBuildElements(vm) {\n const elements = [];\n for (const n of vm.nodes) {\n elements.push({\n group: \"nodes\",\n data: {\n // totalCoupling/sccId are non-negative counts / a stable id; `??` mirrors\n // the legacy `||` fallback for these (a 0 coupling stays 0).\n id: n.id,\n label: n.label,\n totalCoupling: n.totalCoupling ?? 0,\n sccId: n.sccId ?? null,\n sccColor: gvSccColor(n.sccId)\n }\n });\n }\n vm.edges.forEach((e, j) => {\n elements.push({\n group: \"edges\",\n data: {\n id: \"e\" + j,\n source: e.source,\n target: e.target,\n // An edge always carries weight ≥ 1; default to 1 when the blob omits it.\n weight: e.weight ?? 1,\n isCycleEdge: !!e.isCycleEdge\n }\n });\n });\n return elements;\n }\n\n // src/client/view-graph-help.ts\n var GRAPH_VIEW_HELP = {\n title: \"Visualization\",\n sections: [\n {\n heading: \"What this is\",\n body: \"A node-link visualization of the call graph, rendered with Cytoscape.js. At Package level each node is a package and each edge is the directed coupling from one package into another; node size reflects total coupling (calls in + calls out) and edge thickness reflects the number of call edges. At Function level it shows the functions of one selected package and the calls among them.\"\n },\n {\n heading: \"Levels\",\n body: 'Use the Level control to switch between Package (the whole-repo package rollup, the same data as the Coupling matrix) and Function (one package at a time). Function level enables the Package picker (which package to show) and the Kind multi-select, plus an Edges toggle: \"Intra-package\" shows only calls inside the package; \"+ cross-package\" also draws calls leaving the package to faded external nodes. The Scope control (production only vs include tests) applies at both levels.'\n },\n {\n heading: \"Why you care\",\n body: \"The table views project the graph into rankings and lists, and the Coupling matrix shows the same package data as a grid. This view shows that topology directly \\u2014 hub packages, tightly-coupled clusters, and circular package dependencies at package level; the internal call structure of a single package at function level.\"\n },\n {\n heading: \"How to read it\",\n body: \"Bigger nodes are more coupled; thicker edges carry more calls. Use the layout selector to switch between layered (dagre), force (cose), and hierarchical (breadthfirst). The matrix on the Coupling tab is the package-level data in tabular form.\"\n },\n {\n heading: \"What to do\",\n body: \"Pan and zoom to explore. Type in the search box to center and highlight a node by name; non-matches fade. Click a node to trace its direct callers (upstream) and callees (downstream).\"\n },\n {\n heading: \"Cross-package cycles\",\n body: 'Strongly-connected components are groups of packages that can all reach each other through call edges (found via Tarjan\\u2019s algorithm). Click \"Highlight cycles\" in the toolbar to emphasize cycle members and cycle edges while dimming the acyclic remainder. A cycle between packages is usually a layering smell. Break it by extracting the shared protocol into a third package both sides depend on, or by inverting one call into a callback/event.'\n }\n ]\n };\n\n // src/client/view-graph-stylesheet.ts\n function gvStylesheet() {\n return [\n {\n selector: \"node\",\n style: {\n \"background-color\": \"#c4956a\",\n \"border-color\": (ele) => ele.data(\"sccColor\") || \"#8a8a8a\",\n \"border-width\": (ele) => ele.data(\"sccId\") ? 3 : 1,\n shape: \"round-rectangle\",\n // Size by total coupling degree (fan-in + fan-out call count). The\n // log-ish clamp keeps a megabus package from dwarfing the canvas.\n width: (ele) => 22 + Math.min(56, Math.sqrt(ele.data(\"totalCoupling\") || 0) * 6),\n height: (ele) => 22 + Math.min(56, Math.sqrt(ele.data(\"totalCoupling\") || 0) * 6),\n label: (ele) => ele.data(\"label\") || \"\",\n \"font-size\": 9,\n color: \"#ddd\",\n \"text-valign\": \"bottom\",\n \"text-halign\": \"center\",\n \"text-margin-y\": 2,\n \"text-wrap\": \"none\"\n }\n },\n {\n selector: \"edge\",\n style: {\n // Thickness by call-count weight (clamped). A solid uniform style —\n // resolution/confidence encoding is function-level and not meaningful\n // once edges are aggregated to packages.\n width: (ele) => 1 + Math.min(7, Math.sqrt(ele.data(\"weight\") || 1) * 1.2),\n \"line-color\": \"#5a5a5a\",\n \"target-arrow-color\": \"#5a5a5a\",\n \"target-arrow-shape\": \"triangle\",\n \"arrow-scale\": 0.8,\n \"curve-style\": \"bezier\"\n }\n },\n {\n selector: \"edge[?isCycleEdge]\",\n style: { \"line-color\": \"#d46a6a\", \"target-arrow-color\": \"#d46a6a\" }\n },\n // Function-level \"+ cross-package\" mode only: a callee that lives OUTSIDE\n // the selected package is drawn as a faded ellipse so the boundary reads\n // at a glance. Package-level view-models never set 'external', so this\n // selector is inert there.\n {\n selector: \"node[?external]\",\n style: {\n \"background-color\": \"#3a3a3a\",\n \"border-color\": \"#666\",\n color: \"#9a9a9a\",\n shape: \"ellipse\",\n opacity: 0.55\n }\n },\n {\n selector: \"node.gv-search-hit\",\n style: {\n \"background-color\": \"#e0a96d\",\n \"border-color\": \"#fff\",\n \"border-width\": 3,\n opacity: 1\n }\n },\n {\n selector: \"node.gv-search-fade\",\n style: { opacity: 0.12 }\n },\n {\n selector: \"edge.gv-search-fade\",\n style: { opacity: 0.05 }\n },\n // Impact highlight (adapted to packages): clicking a package lights its\n // direct caller packages (upstream) and callee packages (downstream).\n // Accent palette mirrors the dashboard theme: --accent (selected),\n // --accent-fitness (downstream), --accent-sim (upstream). Hard-coded\n // because the Cytoscape canvas can't read CSS custom properties.\n {\n selector: \"node.gv-selected\",\n style: {\n \"background-color\": \"#e0a96d\",\n \"border-color\": \"#fff\",\n \"border-width\": 4,\n opacity: 1\n }\n },\n {\n selector: \"node.gv-upstream\",\n style: { \"background-color\": \"#6a9bd4\", opacity: 1 }\n },\n {\n selector: \"node.gv-downstream\",\n style: { \"background-color\": \"#7ec47e\", opacity: 1 }\n },\n {\n selector: \".gv-dimmed\",\n style: { opacity: 0.1 }\n },\n // Cross-package cycle highlight (folded-in \"Cycles / SCCs\" affordance).\n // Cycle members get a bright accent fill; cycle edges turn red and\n // thicken; the acyclic remainder fades so multi-package cycles stand out.\n {\n selector: \"node.gv-scc-member\",\n style: {\n \"background-color\": \"#d46a6a\",\n \"border-color\": \"#fff\",\n \"border-width\": 3,\n opacity: 1\n }\n },\n {\n selector: \"edge.gv-scc-edge\",\n style: {\n \"line-color\": \"#d46a6a\",\n \"target-arrow-color\": \"#d46a6a\",\n width: 3,\n opacity: 1\n }\n },\n {\n selector: \".gv-scc-dimmed\",\n style: { opacity: 0.08 }\n }\n ];\n }\n\n // src/client/view-graph.ts\n function gvRegisterGraphLayouts() {\n try {\n if (typeof cytoscape === \"function\" && typeof cytoscapeDagre !== \"undefined\" && !cytoscape.__gvDagreRegistered) {\n cytoscape.use(cytoscapeDagre);\n cytoscape.__gvDagreRegistered = true;\n }\n } catch {\n }\n }\n function gvLoadViewModel() {\n const blob = document.querySelector(\"#graph-view-model\");\n if (!blob?.textContent) return null;\n try {\n return JSON.parse(blob.textContent);\n } catch {\n return null;\n }\n }\n function gvPanelHidden(container) {\n return !!(container?.classList?.contains(\"code-paths-view\") && !container.classList.contains(\"active\"));\n }\n function gvLayoutOptions(layoutId) {\n if (layoutId === \"dagre\") {\n return { name: \"dagre\", rankDir: \"LR\", nodeSep: 24, rankSep: 64, fit: true, padding: 24 };\n }\n if (layoutId === \"breadthfirst\") {\n return { name: \"breadthfirst\", directed: true, spacingFactor: 1.2, fit: true, padding: 24 };\n }\n return { name: \"cose\", animate: false, fit: true, padding: 24, nodeRepulsion: 6e3 };\n }\n function gvRunLayout(layoutId) {\n if (!gvState.cy) return;\n gvState.currentLayout = layoutId;\n const layout = gvState.cy.layout(gvLayoutOptions(layoutId));\n layout.run();\n }\n function gvApplySccHighlight() {\n const cy = gvState.cy;\n if (!cy) return;\n cy.batch(() => {\n cy.elements().removeClass(\"gv-scc-member gv-scc-edge gv-scc-dimmed\");\n if (!gvState.sccHighlight) return;\n cy.nodes().forEach((n) => {\n if (n.data(\"sccId\")) n.addClass(\"gv-scc-member\");\n else n.addClass(\"gv-scc-dimmed\");\n });\n cy.edges().forEach((ed) => {\n if (ed.data(\"isCycleEdge\")) ed.addClass(\"gv-scc-edge\");\n else ed.addClass(\"gv-scc-dimmed\");\n });\n });\n }\n function gvClearImpact() {\n if (!gvState.cy) return;\n gvState.cy.elements().removeClass(\"gv-selected gv-upstream gv-downstream gv-dimmed\");\n }\n function gvApplyImpact(seedId) {\n const cy = gvState.cy;\n if (!cy) return;\n const seed = cy.getElementById(seedId);\n if (!seed || seed.length === 0) return;\n const upstream = {};\n const downstream = {};\n seed.incomers(\"node\").forEach((n) => {\n if (n.id() !== seedId) upstream[n.id()] = true;\n });\n seed.outgoers(\"node\").forEach((n) => {\n if (n.id() !== seedId) downstream[n.id()] = true;\n });\n cy.batch(() => {\n cy.elements().removeClass(\"gv-selected gv-upstream gv-downstream\");\n cy.elements().addClass(\"gv-dimmed\");\n cy.nodes().forEach((n) => {\n const id = n.id();\n if (id === seedId) {\n n.removeClass(\"gv-dimmed\").addClass(\"gv-selected\");\n } else if (upstream[id]) {\n n.removeClass(\"gv-dimmed\").addClass(\"gv-upstream\");\n } else if (downstream[id]) {\n n.removeClass(\"gv-dimmed\").addClass(\"gv-downstream\");\n }\n });\n cy.edges().forEach((ed) => {\n const s = ed.source().id();\n const t = ed.target().id();\n if (s === seedId || t === seedId) ed.removeClass(\"gv-dimmed\");\n });\n });\n }\n function gvRenderSearchBox(host) {\n const input = el(\"input\", {\n type: \"search\",\n class: \"search-input code-paths-graph-search\",\n id: \"code-paths-graph-search-input\",\n // Labels are package names at package level, function names at function\n // level; the search matches whatever the live node labels are.\n placeholder: gvState.level === \"function\" ? \"Find a function by name\\u2026\" : \"Find a package by name\\u2026\"\n });\n input.addEventListener(\"input\", (e) => {\n gvApplySearch(e.target.value ?? \"\");\n });\n host.append(input);\n }\n function gvApplySearch(query) {\n const cy = gvState.cy;\n if (!cy) return;\n const q = (query ?? \"\").trim();\n cy.nodes().removeClass(\"gv-search-hit gv-search-fade\");\n if (q.length === 0) return;\n const labels = cy.nodes().map((n) => n.data(\"label\") ?? \"\");\n const matches = fuzzyMatch(q, labels);\n const hitLabels = {};\n for (const m of matches) hitLabels[m.name] = true;\n let hitCollection = cy.collection();\n cy.nodes().forEach((n) => {\n if (hitLabels[n.data(\"label\")]) {\n n.addClass(\"gv-search-hit\");\n hitCollection = hitCollection.union(n);\n } else {\n n.addClass(\"gv-search-fade\");\n }\n });\n if (hitCollection.length > 0) {\n try {\n cy.center(hitCollection);\n cy.fit(hitCollection, 120);\n } catch {\n }\n }\n }\n function gvEmpty(container, text) {\n container.append(el(\"div\", { class: \"empty\", text }));\n return null;\n }\n function gvResolveElements(container, indexes) {\n if (gvState.level === \"function\") {\n if (typeof cytoscape !== \"function\") return gvEmpty(container, \"Graph renderer unavailable.\");\n if (!gvState.selectedPackage) {\n return gvEmpty(container, \"Select a package to view its functions.\");\n }\n const elements2 = gvBuildFunctionElements(\n indexes,\n gvState.selectedPackage,\n gvState.includeTests,\n gvState.kinds,\n gvState.crossPackage\n );\n if (!elements2 || elements2.length === 0) {\n return gvEmpty(container, \"No functions in this package.\");\n }\n return elements2;\n }\n const vm = gvLoadViewModel();\n if (!vm?.nodes || vm.nodes.length === 0) return gvEmpty(container, \"No graph to display.\");\n if (typeof cytoscape !== \"function\") return gvEmpty(container, \"Graph renderer unavailable.\");\n const elements = gvBuildElements(vm);\n if (elements.length === 0) return gvEmpty(container, \"No packages to display.\");\n return elements;\n }\n function gvMountCanvas(container, elements) {\n const canvas = el(\"div\", { class: \"code-paths-graph-canvas\", id: \"code-paths-graph-canvas\" });\n container.append(canvas);\n try {\n gvState.cy = cytoscape({\n container: canvas,\n elements,\n style: gvStylesheet(),\n layout: gvLayoutOptions(gvState.currentLayout),\n wheelSensitivity: 0.2,\n minZoom: 0.05,\n maxZoom: 4\n });\n } catch {\n gvState.cy = null;\n canvas.append(\n el(\"div\", {\n class: \"empty\",\n text: \"Graph renderer could not initialize in this environment.\"\n })\n );\n return false;\n }\n return true;\n }\n function gvWireInteractions() {\n const cy = gvState.cy;\n if (!cy) return;\n cy.on(\"tap\", \"node\", (evt) => {\n gvApplyImpact(evt.target.id());\n });\n cy.on(\"tap\", (evt) => {\n if (evt.target === cy) gvClearImpact();\n });\n if (gvState.escHandler) {\n try {\n document.removeEventListener(\"keydown\", gvState.escHandler);\n } catch {\n }\n }\n gvState.escHandler = (e) => {\n if (e.key === \"Escape\") gvClearImpact();\n };\n document.addEventListener(\"keydown\", gvState.escHandler);\n }\n function gvRenderGraph(container, catalog, indexes) {\n while (container.firstChild) container.firstChild.remove();\n if (gvPanelHidden(container)) return;\n gvRegisterGraphLayouts();\n container.append(makeSectionHeading(\"Visualization\", \"graph\"));\n gvRenderControls(container, catalog, indexes, {\n rerender: () => gvRenderGraph(container, catalog, indexes),\n runLayout: gvRunLayout,\n applySccHighlight: gvApplySccHighlight,\n renderSearchBox: gvRenderSearchBox\n });\n const elements = gvResolveElements(container, indexes);\n if (!elements) return;\n if (!gvMountCanvas(container, elements)) return;\n gvWireInteractions();\n gvApplySccHighlight();\n }\n views.push({\n id: \"graph\",\n label: \"Visualization\",\n help: GRAPH_VIEW_HELP,\n render(container, catalog, indexes) {\n gvRenderGraph(container, catalog, indexes);\n },\n onActivate() {\n if (!gvState.cy) return;\n setTimeout(() => {\n try {\n gvState.cy?.resize();\n gvState.cy?.fit(void 0, 24);\n } catch {\n }\n }, 0);\n }\n });\n\n // src/client/code-paths-panel.ts\n var cg = globalThis;\n cg.graphCatalog = null;\n cg.graphIndexes = {\n byBodyHash: /* @__PURE__ */ new Map(),\n occurrencesByHash: /* @__PURE__ */ new Map(),\n bySimpleName: /* @__PURE__ */ new Map(),\n callees: /* @__PURE__ */ new Map(),\n callers: /* @__PURE__ */ new Map()\n };\n function loadGraphCatalogFromBlob() {\n const blob = document.querySelector(\"#graph-catalog\");\n if (!blob?.textContent) return null;\n try {\n return JSON.parse(blob.textContent);\n } catch {\n return null;\n }\n }\n function renderCodePathsTab() {\n const panel = document.querySelector(\"#panel-code-paths\");\n if (!panel) return;\n while (panel.firstChild) panel.firstChild.remove();\n const graphSessions = sessions.filter((s) => s.tool === \"graph\");\n cg.graphCatalog = loadGraphCatalogFromBlob();\n renderSubtabBar(panel, [\n {\n id: \"sessions\",\n label: \"Sessions\",\n render(p) {\n if (graphSessions.length > 0) {\n renderSessionTable(p, graphSessions, \"var(--accent)\");\n } else {\n p.append(el(\"div\", { class: \"empty\", text: \"No sessions yet.\" }));\n }\n }\n },\n {\n id: \"catalog\",\n label: \"Catalog\",\n render(p) {\n renderGraphRuleCatalog(\n p,\n typeof graphRuleCatalog === \"undefined\" ? [] : graphRuleCatalog\n );\n }\n },\n {\n id: \"recipes\",\n label: \"Recipes\",\n render(p) {\n renderGraphRecipeCatalog(\n p,\n typeof graphRecipeCatalog === \"undefined\" ? [] : graphRecipeCatalog\n );\n }\n },\n {\n id: \"explore\",\n label: \"Explore\",\n render(p) {\n if (cg.graphCatalog) {\n renderCodePathsExplore(p);\n } else {\n p.append(el(\"div\", { class: \"empty\", text: \"No catalog yet.\" }));\n }\n }\n }\n ]);\n }\n function renderCodePathsExplore(host) {\n cg.graphIndexes = buildIndexes(cg.graphCatalog);\n renderCatalogProvenance(host, cg.graphCatalog);\n const tabBar = el(\"div\", { class: \"code-paths-tabs\", id: \"code-paths-tabs\" });\n for (const view of views) {\n const tab = el(\"div\", {\n class: \"code-paths-tab\",\n \"data-view\": view.id,\n text: view.label,\n onclick: () => activateView(view.id)\n });\n tabBar.append(tab);\n }\n host.append(tabBar);\n const stack = el(\"div\", { class: \"code-paths-view-container\", id: \"code-paths-view-container\" });\n for (const view of views) {\n const c = el(\"div\", { class: \"code-paths-view\", id: \"code-paths-view-\" + view.id });\n stack.append(c);\n }\n host.append(stack);\n stack.addEventListener(\"click\", (e) => {\n const target = e.target;\n const row = target?.closest?.(\"[data-body-hash]\");\n if (!row) return;\n openFunctionCard(row.dataset.bodyHash ?? \"\");\n });\n document.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") closeFunctionCard();\n });\n for (const view of views) {\n const container = document.querySelector(\"#code-paths-view-\" + view.id);\n if (container) view.render(container, cg.graphCatalog, cg.graphIndexes, filterState);\n }\n const initialId = readViewIdFromHash() ?? views[0]?.id;\n if (initialId) activateView(initialId);\n }\n function readViewIdFromHash() {\n const m = /^#code-paths\\/([a-z]+)/.exec(globalThis.location.hash || \"\");\n return m ? m[1] : null;\n }\n function openCodePathsSession(sessionId) {\n const tab = document.querySelector('.tab[data-tab=\"code-paths\"]');\n const panel = document.querySelector(\"#panel-code-paths\");\n if (!tab || !panel) return;\n document.querySelectorAll(\".tab\").forEach((t) => t.classList.remove(\"active\"));\n document.querySelectorAll(\".tab-panel\").forEach((p) => p.classList.remove(\"active\"));\n tab.classList.add(\"active\");\n panel.classList.add(\"active\");\n const sessionsSub = panel.querySelector('.subtab[data-subtab=\"sessions\"]');\n const exploreSub = panel.querySelector('.subtab[data-subtab=\"explore\"]');\n const sessionsPanel = document.querySelector(\"#panel-code-paths-sessions\");\n const explorePanel = document.querySelector(\"#panel-code-paths-explore\");\n if (sessionsSub) sessionsSub.classList.add(\"active\");\n if (exploreSub) exploreSub.classList.remove(\"active\");\n if (sessionsPanel) sessionsPanel.classList.add(\"active\");\n if (explorePanel) explorePanel.classList.remove(\"active\");\n const row = sessionsPanel?.querySelector('tr[data-session-id=\"' + sessionId + '\"]');\n if (row) row.click();\n }\n registerTabActivator(\"graph\", openCodePathsSession);\n\n // src/client/overview.ts\n function renderOverview() {\n const panel = document.querySelector(\"#panel-overview\");\n if (!panel) return;\n if (sessions.length === 0) {\n panel.append(el(\"div\", { class: \"empty\", text: \"No sessions yet.\" }));\n return;\n }\n const sec = el(\"div\", { class: \"section\" }, [el(\"h3\", { text: \"Recent Activity\" })]);\n const table = el(\"table\", { class: \"data-table sortable\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"Timestamp\", \"Tool\", \"Recipe\", \"Pass Rate\", \"Status\", \"Checks\", \"Findings\", \"Duration\"].forEach(\n (h) => {\n headerRow.append(el(\"th\", { text: h }));\n }\n );\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n sessions.forEach((s) => {\n const sc2 = scoreColorStyle(s.score);\n const sm = s.payload?.summary ?? { total: 0, passed: 0, failed: 0, errors: 0, warnings: 0 };\n const row = el(\"tr\", {\n class: \"clickable\",\n onclick: () => {\n if (activateTabForSession(s)) return;\n const tabName = tabMap[s.tool] ?? s.tool;\n document.querySelectorAll(\".tab\").forEach((t) => t.classList.remove(\"active\"));\n document.querySelectorAll(\".tab-panel\").forEach((p) => p.classList.remove(\"active\"));\n const tab = document.querySelector('.tab[data-tab=\"' + tabName + '\"]');\n if (tab) tab.classList.add(\"active\");\n const activePanel = document.querySelector(\"#panel-\" + tabName);\n if (activePanel) activePanel.classList.add(\"active\");\n }\n });\n row.append(\n el(\"td\", {\n class: \"cell-nowrap\",\n text: new Date(s.startedAt).toLocaleString(),\n style: \"color:var(--text-dim)\"\n })\n );\n const toolCell = el(\"td\");\n toolCell.append(\n el(\"span\", {\n class: \"badge\",\n style: toolBadgeStyles[s.tool] ?? \"\",\n text: s.tool.toUpperCase()\n })\n );\n row.append(toolCell);\n row.append(el(\"td\", { text: s.recipe ?? \"default\", style: \"color:var(--text-muted)\" }));\n row.append(el(\"td\", { text: s.score + \"%\", style: \"font-weight:600;\" + sc2 }));\n const statusCell = el(\"td\");\n statusCell.append(statusBadge(sessionStatus(s)));\n row.append(statusCell);\n row.append(el(\"td\", { text: (sm.passed ?? 0) + \"/\" + (sm.total ?? 0) }));\n row.append(el(\"td\", { text: \"\" + ((sm.errors ?? 0) + (sm.warnings ?? 0)) }));\n row.append(\n el(\"td\", { text: (s.durationMs / 1e3).toFixed(1) + \"s\", style: \"color:var(--text-dim)\" })\n );\n tbody.append(row);\n });\n table.append(tbody);\n const pag = el(\"div\", { class: \"pagination\" });\n sec.append(el(\"div\", { class: \"card\" }, [table, pag]));\n panel.append(sec);\n paginateTable(tbody, pag, 10);\n }\n\n // src/client/recipes.ts\n function renderRecipesPanel(container, recipesData) {\n const recipes = recipesData;\n if (!recipes?.length) {\n container.append(el(\"div\", { class: \"empty\", text: \"No recipes available.\" }));\n return;\n }\n const table = el(\"table\", { class: \"data-table\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"Recipe\", \"Description\", \"Selector\", \"Mode\", \"Timeout\", \"Tags\"].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n recipes.forEach((recipe) => {\n const row = el(\"tr\");\n const nameCell = el(\"td\", { style: \"font-weight:500\" });\n nameCell.append(el(\"div\", { text: recipe.displayName }));\n nameCell.append(\n el(\"div\", {\n text: recipe.name,\n style: \"font-size:11px;color:var(--text-dim);font-weight:400\"\n })\n );\n row.append(nameCell);\n row.append(el(\"td\", { text: recipe.description, style: \"color:var(--text-muted)\" }));\n const selCell = el(\"td\");\n selCell.append(\n el(\"span\", {\n class: \"badge\",\n style: \"background:var(--bg-hover);color:var(--text-muted)\",\n text: recipe.selectorType\n })\n );\n row.append(selCell);\n const modeCell = el(\"td\");\n const modeColor = recipe.mode === \"parallel\" ? \"color:var(--success)\" : \"color:var(--warning)\";\n modeCell.append(el(\"span\", { text: recipe.mode, style: modeColor + \";font-size:12px\" }));\n row.append(modeCell);\n row.append(\n el(\"td\", {\n text: recipe.timeout / 1e3 + \"s\",\n style: \"color:var(--text-dim);font-size:12px\"\n })\n );\n const tagsCell = el(\"td\");\n (recipe.tags ?? []).forEach((t) => {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: t }));\n });\n row.append(tagsCell);\n tbody.append(row);\n });\n table.append(tbody);\n container.append(el(\"div\", { class: \"card\" }, [table]));\n }\n\n // src/client/tool-tabs.ts\n function renderToolTab(options) {\n const {\n panelId,\n toolSessions,\n accentColor,\n catalogLabel,\n catalogData,\n renderCatalogFn,\n recipesData\n } = options;\n const panel = document.querySelector(\"#\" + panelId);\n if (!panel) return;\n renderSubtabBar(panel, [\n {\n id: \"overview\",\n label: \"Sessions\",\n render: (p) => {\n renderSessionTable(p, toolSessions, accentColor);\n }\n },\n {\n id: \"catalog\",\n label: catalogLabel,\n render: (p) => {\n if (catalogData && catalogData.length > 0) {\n renderCatalogFn(p, catalogData);\n } else {\n p.append(\n el(\"div\", {\n class: \"empty\",\n text: \"No \" + catalogLabel.toLowerCase() + \" available yet.\"\n })\n );\n }\n }\n },\n {\n id: \"recipes\",\n label: \"Recipes\",\n render: (p) => {\n renderRecipesPanel(p, recipesData);\n }\n }\n ]);\n }\n function renderFitnessTab() {\n renderToolTab({\n panelId: \"panel-fitness\",\n toolSessions: fitSessions,\n accentColor: \"var(--accent-fitness)\",\n catalogLabel: \"Checks\",\n catalogData: checkCatalog,\n renderCatalogFn: (container, data) => renderChecksCatalog(container, data),\n recipesData: recipeCatalog\n });\n }\n function renderScenariosCatalog(container, catalogData) {\n const scenarios = catalogData;\n const table = el(\"table\", { class: \"session-table\" });\n const tbody = el(\"tbody\");\n [...scenarios].sort((a, b) => a.name.localeCompare(b.name)).forEach((s) => {\n const row = el(\"tr\");\n const nameCell = el(\"td\");\n nameCell.append(el(\"strong\", { text: s.name }));\n if (s.kind)\n nameCell.append(el(\"span\", { class: \"badge\", text: s.kind, style: \"margin-left:8px\" }));\n if (s.description)\n nameCell.append(\n el(\"div\", { class: \"muted\", style: \"font-size:12px\", text: s.description })\n );\n row.append(nameCell);\n const tagsCell = el(\"td\");\n (s.tags ?? []).slice(0, 4).forEach((t) => {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: t }));\n });\n row.append(tagsCell);\n tbody.append(row);\n });\n table.append(tbody);\n container.append(table);\n }\n function renderSimulationTab() {\n renderToolTab({\n panelId: \"panel-simulation\",\n toolSessions: simSessions,\n accentColor: \"var(--accent-sim)\",\n catalogLabel: \"Scenarios\",\n catalogData: simScenarioCatalog,\n renderCatalogFn: (container, data) => renderScenariosCatalog(container, data),\n recipesData: simRecipeCatalog\n });\n }\n\n // src/client/tab-bar.ts\n document.querySelector(\"#tab-bar\")?.addEventListener(\"click\", (e) => {\n const tab = e.target?.closest(\".tab\");\n if (!tab) return;\n document.querySelectorAll(\".tab\").forEach((t) => t.classList.remove(\"active\"));\n document.querySelectorAll(\".tab-panel\").forEach((p) => p.classList.remove(\"active\"));\n tab.classList.add(\"active\");\n document.querySelector(\"#panel-\" + tab.dataset.tab)?.classList.add(\"active\");\n });\n\n // src/client/index.ts\n var g = globalThis;\n g.el = el;\n g.paginateTable = paginateTable;\n g.makeSortable = makeSortable;\n g.registerTabActivator = registerTabActivator;\n g.activateTabForSession = activateTabForSession;\n g.renderSubtabBar = renderSubtabBar;\n g.renderSessionTable = renderSessionTable;\n g.renderChecksCatalog = renderChecksCatalog;\n g.renderRecipesPanel = renderRecipesPanel;\n g.renderOverview = renderOverview;\n g.renderFitnessTab = renderFitnessTab;\n g.renderSimulationTab = renderSimulationTab;\n g.renderCodePathsTab = renderCodePathsTab;\n g.openCodePathsSession = openCodePathsSession;\n g.packageOfPath = packageOfPath;\n g.shortPkg = shortPkg;\n g.pkgOf = pkgOf;\n g.displayName = displayName;\n g.buildIndexes = buildIndexes;\n g.resolveCalleeOcc = resolveCalleeOcc;\n g.filterState = filterState;\n g.KIND_LIST = KIND_LIST;\n g.packagesInCatalog = packagesInCatalog;\n g.passesFilter = passesFilter;\n g.fuzzyMatch = fuzzyMatch;\n g.makeSectionHeading = makeSectionHeading;\n g.renderFunctionRows = renderFunctionRows;\n g.openFunctionCard = openFunctionCard;\n g.closeFunctionCard = closeFunctionCard;\n g.views = views;\n g.activateView = activateView;\n g.defineRankedView = defineRankedView;\n g.openHelpDrawer = openHelpDrawer;\n g.renderCatalogProvenance = renderCatalogProvenance;\n g.renderGraphRuleCatalog = renderGraphRuleCatalog;\n g.renderGraphRecipeCatalog = renderGraphRecipeCatalog;\n})();\n";
2
+ export const DASHBOARD_CLIENT_BUNDLE = "\"use strict\";\n(() => {\n // src/client/el.ts\n function el(tag, attrs, children) {\n const e = document.createElement(tag);\n if (attrs)\n Object.entries(attrs).forEach(([k, v]) => {\n if (k === \"text\") e.textContent = v;\n else if (k === \"class\") e.className = v;\n else if (k.startsWith(\"on\")) e.addEventListener(k.slice(2), v);\n else e.setAttribute(k, v);\n });\n if (children)\n children.forEach((c) => {\n if (typeof c === \"string\" || c) e.append(c);\n });\n return e;\n }\n\n // src/client/path-utils.ts\n function packageOfPath(filePath) {\n if (typeof filePath !== \"string\" || filePath.length === 0) return \"<unknown>\";\n const m = /^packages\\/([^/]+)\\//.exec(filePath);\n return m ? m[1] : \"<unknown>\";\n }\n function shortPkg(name) {\n if (typeof name !== \"string\") return \"<unknown>\";\n return name.codePointAt(0) === 64 ? name.slice(name.indexOf(\"/\") + 1) : name;\n }\n function pkgOf(occ) {\n if (occ && typeof occ.package === \"string\" && occ.package.length > 0)\n return shortPkg(occ.package);\n return packageOfPath(occ ? occ.filePath : \"\");\n }\n function displayName(simpleName) {\n if (typeof simpleName !== \"string\") return \"\";\n const m = /^<([a-z-]+)[:>]/.exec(simpleName);\n if (m) return \"<\" + m[1] + \">\";\n return simpleName;\n }\n\n // src/client/filters.ts\n var filterState = {\n packages: /* @__PURE__ */ new Set(),\n kinds: /* @__PURE__ */ new Set(),\n includeTests: false\n };\n var KIND_LIST = [\n \"function-declaration\",\n \"function-expression\",\n \"method\",\n \"arrow\",\n \"constructor\",\n \"getter\",\n \"setter\",\n \"module-init\"\n ];\n function packagesInCatalog(catalog) {\n const pkgs = /* @__PURE__ */ new Set();\n if (!catalog?.functions) return [];\n for (const name of Object.keys(catalog.functions)) {\n for (const occ of catalog.functions[name] || []) {\n pkgs.add(pkgOf(occ));\n }\n }\n return [...pkgs].sort();\n }\n function passesFilter(occ, fs) {\n if (!fs.includeTests && occ.inTestFile) return false;\n if (fs.packages.size > 0 && !fs.packages.has(pkgOf(occ))) return false;\n if (fs.kinds.size > 0 && !fs.kinds.has(occ.kind ?? \"\")) return false;\n return true;\n }\n\n // src/client/catalog-provenance.ts\n function catalogEngineMode(catalog) {\n const m = /(?:^|\\|)mode=([a-z]+)/.exec(catalog?.cacheKey ?? \"\");\n return m ? m[1] : null;\n }\n function catalogFunctionCount(catalog) {\n if (!catalog?.functions) return 0;\n let n = 0;\n for (const name of Object.keys(catalog.functions)) n += (catalog.functions[name] ?? []).length;\n return n;\n }\n function provenanceRelTime(iso) {\n const t = Date.parse(iso);\n if (Number.isNaN(t)) return \"\";\n const secs = Math.max(0, Math.round((Date.now() - t) / 1e3));\n if (secs < 45) return \"just now\";\n const mins = Math.round(secs / 60);\n if (mins < 60) return mins + \" min\" + (mins === 1 ? \"\" : \"s\") + \" ago\";\n const hrs = Math.round(mins / 60);\n if (hrs < 24) return hrs + \" hour\" + (hrs === 1 ? \"\" : \"s\") + \" ago\";\n const days = Math.round(hrs / 24);\n return days + \" day\" + (days === 1 ? \"\" : \"s\") + \" ago\";\n }\n function provenanceChip(label, value, opts) {\n const o = opts ?? {};\n const chip = el(\"span\", { style: \"display:inline-flex;align-items:baseline;gap:6px\" });\n chip.append(\n el(\"span\", {\n text: label,\n style: \"color:var(--text-dim);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em\"\n })\n );\n chip.append(\n el(\"span\", {\n text: value,\n title: o.title ?? \"\",\n style: \"color:\" + (o.color ?? \"var(--text)\") + \";font-weight:600;font-size:13px\"\n })\n );\n return chip;\n }\n function renderCatalogProvenance(host, catalog) {\n if (!catalog) return;\n const pkgs = packagesInCatalog(catalog);\n const fnCount = catalogFunctionCount(catalog);\n const engine = catalogEngineMode(catalog);\n const builtAtIso = catalog.builtAt;\n const builtAt = builtAtIso ? new Date(builtAtIso) : null;\n const bar = el(\"div\", {\n class: \"catalog-provenance\",\n style: \"display:flex;flex-wrap:wrap;align-items:center;gap:10px 20px;margin:0 0 16px;padding:10px 14px;border:1px solid var(--border);border-radius:8px;background:var(--bg-card)\"\n });\n const pkgLabel = pkgs.length === 1 ? \"1 package\" : String(pkgs.length) + \" packages\";\n const pkgNames = pkgs.length > 0 && pkgs.length <= 4 ? \": \" + pkgs.join(\", \") : \"\";\n bar.append(\n provenanceChip(\"Scope\", pkgLabel + pkgNames, {\n color: \"var(--accent)\",\n title: pkgs.join(\", \")\n })\n );\n bar.append(provenanceChip(\"Functions\", fnCount.toLocaleString()));\n if (builtAtIso && builtAt && !Number.isNaN(builtAt.getTime())) {\n bar.append(\n provenanceChip(\"Built\", provenanceRelTime(builtAtIso), {\n title: builtAt.toLocaleString()\n })\n );\n }\n if (engine) bar.append(provenanceChip(\"Engine\", engine));\n if (catalog.resolutionMode === \"fast\") {\n bar.append(provenanceChip(\"Resolution\", \"fast (approximate)\", { color: \"var(--warning)\" }));\n }\n host.append(bar);\n }\n\n // src/client/catalog-recipes-tables.ts\n function renderGraphRuleCatalog(container, rulesData) {\n if (!rulesData?.length) {\n container.append(el(\"div\", { class: \"empty\", text: \"No rules available.\" }));\n return;\n }\n const table = el(\"table\", { class: \"data-table\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"Rule\", \"Default Severity\", \"Source\"].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n rulesData.forEach((rule) => {\n const row = el(\"tr\");\n row.append(el(\"td\", { text: rule.slug, style: \"font-weight:500\" }));\n const sevCell = el(\"td\");\n const sevColor = rule.defaultSeverity === \"error\" ? \"color:var(--danger)\" : \"color:var(--warning)\";\n sevCell.append(el(\"span\", { text: rule.defaultSeverity, style: sevColor + \";font-size:12px\" }));\n row.append(sevCell);\n const srcCell = el(\"td\");\n srcCell.append(\n el(\"span\", {\n class: \"badge\",\n style: \"background:var(--bg-hover);color:var(--text-muted)\",\n text: rule.source\n })\n );\n row.append(srcCell);\n tbody.append(row);\n });\n table.append(tbody);\n container.append(el(\"div\", { class: \"card\" }, [table]));\n }\n function renderGraphRecipeCatalog(container, recipesData) {\n if (!recipesData?.length) {\n container.append(el(\"div\", { class: \"empty\", text: \"No recipes available.\" }));\n return;\n }\n const table = el(\"table\", { class: \"data-table\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"Recipe\", \"Description\", \"Selector\", \"Tags\"].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n recipesData.forEach((recipe) => {\n const row = el(\"tr\");\n const nameCell = el(\"td\", { style: \"font-weight:500\" });\n nameCell.append(el(\"div\", { text: recipe.displayName }));\n nameCell.append(\n el(\"div\", {\n text: recipe.name,\n style: \"font-size:11px;color:var(--text-dim);font-weight:400\"\n })\n );\n row.append(nameCell);\n row.append(el(\"td\", { text: recipe.description, style: \"color:var(--text-muted)\" }));\n const selCell = el(\"td\");\n selCell.append(\n el(\"span\", {\n class: \"badge\",\n style: \"background:var(--bg-hover);color:var(--text-muted)\",\n text: recipe.selectorType\n })\n );\n row.append(selCell);\n const tagsCell = el(\"td\");\n (recipe.tags ?? []).forEach((t) => {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: t }));\n });\n row.append(tagsCell);\n tbody.append(row);\n });\n table.append(tbody);\n container.append(el(\"div\", { class: \"card\" }, [table]));\n }\n\n // src/client/pagination.ts\n var pageHandlers = /* @__PURE__ */ new WeakMap();\n function wirePagination(paginationContainer, goToPage) {\n const existing = pageHandlers.get(paginationContainer);\n if (existing) {\n existing.current = goToPage;\n return;\n }\n const slot = { current: goToPage };\n pageHandlers.set(paginationContainer, slot);\n paginationContainer.addEventListener(\"click\", (event) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const btn = target.closest(\".pagination-btn[data-page-target]\");\n if (!btn || btn.dataset.pageDisabled === \"yes\") return;\n const page = Number(btn.dataset.pageTarget);\n if (Number.isNaN(page)) return;\n slot.current(page);\n });\n }\n function renderPageButtons(container, currentPage, totalPages) {\n container.append(\n el(\"button\", {\n class: \"pagination-btn\" + (currentPage === 0 ? \" disabled\" : \"\"),\n \"data-page-target\": \"\" + (currentPage - 1),\n \"data-page-disabled\": currentPage === 0 ? \"yes\" : \"no\",\n text: \"\\u2190 Prev\"\n })\n );\n const pages = [];\n for (let p = 0; p < totalPages; p++) {\n if (p < 2 || p >= totalPages - 2 || Math.abs(p - currentPage) <= 1) {\n pages.push(p);\n } else if (pages.length > 0 && pages.at(-1) !== -1) {\n pages.push(-1);\n }\n }\n pages.forEach((p) => {\n if (p === -1) {\n container.append(\n el(\"span\", {\n style: \"color:var(--text-dim);padding:4px 4px;font-size:12px\",\n text: \"\\u2026\"\n })\n );\n } else {\n container.append(\n el(\"button\", {\n class: \"pagination-btn\" + (p === currentPage ? \" active\" : \"\"),\n \"data-page-target\": \"\" + p,\n text: \"\" + (p + 1)\n })\n );\n }\n });\n container.append(\n el(\"button\", {\n class: \"pagination-btn\" + (currentPage >= totalPages - 1 ? \" disabled\" : \"\"),\n \"data-page-target\": \"\" + (currentPage + 1),\n \"data-page-disabled\": currentPage >= totalPages - 1 ? \"yes\" : \"no\",\n text: \"Next \\u2192\"\n })\n );\n }\n function paginateTable(tbody, paginationContainer, pageSize) {\n const rows = [...tbody.children];\n const totalPages = Math.max(1, Math.ceil(rows.length / pageSize));\n function renderPage(currentPage) {\n const start = currentPage * pageSize;\n const end = start + pageSize;\n rows.forEach((row, i) => {\n row.style.display = i >= start && i < end ? \"\" : \"none\";\n });\n while (paginationContainer.firstChild) paginationContainer.firstChild.remove();\n if (rows.length <= pageSize) return;\n const info = el(\"div\", {\n class: \"pagination-info\",\n text: \"Showing \" + (start + 1) + \"-\" + Math.min(end, rows.length) + \" of \" + rows.length\n });\n paginationContainer.append(info);\n const btns = el(\"div\", { class: \"pagination-btns\" });\n renderPageButtons(btns, currentPage, totalPages);\n paginationContainer.append(btns);\n }\n wirePagination(paginationContainer, renderPage);\n renderPage(0);\n }\n function paginateGroupedRows(tbody, paginationContainer, pageSize) {\n const allRows = [...tbody.children];\n const groups = [];\n for (let i = 0; i < allRows.length; i++) {\n const row = allRows[i];\n if (row.classList.contains(\"expander-row\")) continue;\n const group = [row];\n if (i + 1 < allRows.length && allRows[i + 1].classList.contains(\"expander-row\")) {\n group.push(allRows[i + 1]);\n }\n groups.push(group);\n }\n const totalPages = Math.max(1, Math.ceil(groups.length / pageSize));\n function renderPage(currentPage) {\n const start = currentPage * pageSize;\n const end = start + pageSize;\n groups.forEach((group, i) => {\n const visible = i >= start && i < end;\n group.forEach((row) => {\n if (row.classList.contains(\"expander-row\")) {\n row.dataset.paged = visible ? \"yes\" : \"no\";\n if (!visible) row.style.display = \"none\";\n } else {\n row.style.display = visible ? \"\" : \"none\";\n }\n });\n });\n while (paginationContainer.firstChild) paginationContainer.firstChild.remove();\n if (groups.length <= pageSize) return;\n const info = el(\"div\", {\n class: \"pagination-info\",\n text: \"Showing \" + (start + 1) + \"-\" + Math.min(end, groups.length) + \" of \" + groups.length + \" checks\"\n });\n paginationContainer.append(info);\n const btns = el(\"div\", { class: \"pagination-btns\" });\n renderPageButtons(btns, currentPage, totalPages);\n paginationContainer.append(btns);\n }\n wirePagination(paginationContainer, renderPage);\n renderPage(0);\n }\n\n // src/client/checks.ts\n var DIM = \"color:var(--text-dim)\";\n var EM_DASH = \"\\u2014\";\n var EXPANDER_ROW = \"expander-row\";\n var PAGE_SIZE = 10;\n var EMPTY_STAT = { runs: 0, passed: 0, failed: 0, lastRun: null };\n function rateColorFor(rate) {\n if (rate >= 90) return \"var(--success)\";\n if (rate >= 70) return \"var(--warning)\";\n return \"var(--error)\";\n }\n function renderFilteredPage(pag, groups, currentPage, totalPages) {\n groups.forEach((g2) => g2.forEach((r) => r.style.display = \"none\"));\n const start = currentPage * PAGE_SIZE;\n const end = start + PAGE_SIZE;\n groups.slice(start, end).forEach((g2) => g2[0].style.display = \"\");\n while (pag.firstChild) pag.firstChild.remove();\n if (groups.length <= PAGE_SIZE) return;\n pag.append(\n el(\"div\", {\n class: \"pagination-info\",\n text: \"Showing \" + (start + 1) + \"-\" + Math.min(end, groups.length) + \" of \" + groups.length + \" checks\"\n })\n );\n const btns = el(\"div\", { class: \"pagination-btns\" });\n renderPageButtons(btns, currentPage, totalPages);\n pag.append(btns);\n }\n function paginateFilteredGroups(pag, groups) {\n const totalPages = Math.max(1, Math.ceil(groups.length / PAGE_SIZE));\n wirePagination(pag, (p) => renderFilteredPage(pag, groups, p, totalPages));\n renderFilteredPage(pag, groups, 0, totalPages);\n }\n function computeCheckStats() {\n const stats = {};\n for (const s of sessions) {\n const checks = s.payload?.checks ?? [];\n for (const ch of checks) {\n stats[ch.checkSlug] ??= { runs: 0, passed: 0, failed: 0, lastRun: null };\n const st = stats[ch.checkSlug];\n st.runs++;\n if (ch.passed) st.passed++;\n else st.failed++;\n if (!st.lastRun || s.startedAt > st.lastRun) st.lastRun = s.startedAt;\n }\n }\n return stats;\n }\n var checkStats = computeCheckStats();\n function renderLongDesc(text) {\n const container = document.createElement(\"div\");\n container.className = \"check-long-desc\";\n if (!text) return container;\n const parts = text.split(/(\\*\\*[^*]+\\*\\*|`[^`]+`|\\n)/g);\n parts.forEach((part) => {\n if (part === \"\\n\") {\n container.append(document.createElement(\"br\"));\n } else if (part.startsWith(\"**\") && part.endsWith(\"**\")) {\n const strong = document.createElement(\"strong\");\n strong.textContent = part.slice(2, -2);\n container.append(strong);\n } else if (part.startsWith(\"`\") && part.endsWith(\"`\")) {\n const code = document.createElement(\"code\");\n code.textContent = part.slice(1, -1);\n container.append(code);\n } else {\n container.append(part);\n }\n });\n return container;\n }\n function buildFilterBar(sortedTags) {\n const filterBar = el(\"div\", { class: \"filter-bar\" });\n const searchInput = el(\"input\", {\n class: \"search-input\",\n type: \"text\",\n placeholder: \"Search checks...\"\n });\n const tagSelect = el(\"select\", { class: \"filter-select\" });\n tagSelect.append(el(\"option\", { value: \"\", text: \"All tags\" }));\n sortedTags.forEach((t) => tagSelect.append(el(\"option\", { value: t, text: t })));\n const sourceSelect = el(\"select\", { class: \"filter-select\" });\n [\"\", \"built-in\", \"community\"].forEach((v) => {\n sourceSelect.append(el(\"option\", { value: v, text: v || \"All sources\" }));\n });\n filterBar.append(searchInput);\n filterBar.append(tagSelect);\n filterBar.append(sourceSelect);\n return { filterBar, searchInput, tagSelect, sourceSelect };\n }\n function buildRateCell(rate) {\n const rateCell = el(\"td\");\n if (rate >= 0) {\n const rateColor = rateColorFor(rate);\n const bar = el(\"span\", { class: \"pass-rate-bar\" });\n const track = el(\"span\", { class: \"pass-rate-track\" });\n track.append(\n el(\"span\", { class: \"pass-rate-fill\", style: \"width:\" + rate + \"%;background:\" + rateColor })\n );\n bar.append(track);\n bar.append(el(\"span\", { text: rate + \"%\", style: \"font-size:12px;color:\" + rateColor }));\n rateCell.append(bar);\n } else {\n rateCell.textContent = EM_DASH;\n rateCell.style.color = \"var(--text-dim)\";\n }\n return rateCell;\n }\n function buildCheckRow(check, i, uid) {\n const st = checkStats[check.slug] ?? EMPTY_STAT;\n const rate = st.runs > 0 ? Math.round(st.passed / st.runs * 100) : -1;\n const hasDesc = !!check.longDescription;\n const expanderId = uid + \"-exp-\" + i;\n const tags = check.tags ?? [];\n const arrowCell = el(\"td\", {\n style: \"width:24px;text-align:center;\" + DIM + \";font-size:12px\"\n });\n if (hasDesc) arrowCell.textContent = \"\\u25B6\";\n const row = el(\"tr\", {\n class: hasDesc ? \"clickable\" : \"\",\n \"data-slug\": check.slug,\n \"data-tags\": tags.join(\",\"),\n \"data-source\": check.source,\n \"data-name\": check.name.toLowerCase(),\n onclick: hasDesc ? () => {\n const exp = document.querySelector(\"#\" + expanderId);\n if (exp) {\n const isOpen = exp.classList.toggle(\"open\");\n exp.style.display = isOpen ? \"table-row\" : \"none\";\n arrowCell.textContent = isOpen ? \"\\u25BC\" : \"\\u25B6\";\n }\n row.classList.toggle(\"expanded\");\n } : void 0\n });\n row.append(arrowCell);\n const nameCell = el(\"td\", { style: \"font-weight:500\" });\n nameCell.append(check.slug);\n row.append(nameCell);\n const tagsCell = el(\"td\");\n tags.slice(0, 4).forEach((t) => {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: t }));\n });\n if (tags.length > 4) {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: \"+\" + (tags.length - 4) }));\n }\n row.append(tagsCell);\n const confCell = el(\"td\");\n confCell.append(el(\"span\", { class: \"badge badge-\" + check.confidence, text: check.confidence }));\n row.append(confCell);\n const sourceCell = el(\"td\");\n const sourceStyle = check.source === \"built-in\" ? \"color:var(--accent)\" : \"color:var(--accent-sim)\";\n sourceCell.append(el(\"span\", { text: check.source, style: sourceStyle + \";font-size:12px\" }));\n row.append(sourceCell);\n row.append(el(\"td\", { text: st.runs > 0 ? \"\" + st.runs : EM_DASH, style: DIM }));\n row.append(buildRateCell(rate));\n row.append(\n el(\"td\", {\n text: st.lastRun ? new Date(st.lastRun).toLocaleDateString() : EM_DASH,\n style: DIM + \";font-size:12px\"\n })\n );\n const rows = [row];\n if (hasDesc) {\n const expRow = el(\"tr\", {\n id: expanderId,\n class: EXPANDER_ROW,\n \"data-slug\": check.slug,\n \"data-tags\": tags.join(\",\"),\n \"data-source\": check.source,\n \"data-name\": check.name.toLowerCase()\n });\n const expCell = el(\"td\", { colspan: \"8\", style: \"padding:0\" });\n const expContent = el(\"div\", { class: \"expander-content\" });\n expContent.append(renderLongDesc(check.longDescription));\n expCell.append(expContent);\n expRow.append(expCell);\n rows.push(expRow);\n }\n return rows;\n }\n function renderChecksCatalog(panel, catalogData) {\n const entries = catalogData;\n if (entries.length === 0) {\n panel.append(el(\"div\", { class: \"empty\", text: \"No checks registered.\" }));\n return;\n }\n const allTags = /* @__PURE__ */ new Set();\n entries.forEach((c) => (c.tags ?? []).forEach((t) => allTags.add(t)));\n const sortedTags = [...allTags].sort();\n const { filterBar, searchInput, tagSelect, sourceSelect } = buildFilterBar(sortedTags);\n panel.append(filterBar);\n const totalChecks = entries.length;\n const builtinCount = entries.filter((c) => c.source === \"built-in\").length;\n const communityCount = entries.filter((c) => c.source === \"community\").length;\n const statsRow = el(\"div\", {\n style: \"display:flex;gap:16px;margin-bottom:16px;font-size:13px;color:var(--text-muted)\"\n });\n statsRow.append(el(\"span\", { text: totalChecks + \" total checks\" }));\n statsRow.append(el(\"span\", { text: builtinCount + \" built-in\", style: \"color:var(--accent)\" }));\n if (communityCount > 0)\n statsRow.append(\n el(\"span\", { text: communityCount + \" community\", style: \"color:var(--accent-sim)\" })\n );\n panel.append(statsRow);\n const table = el(\"table\", { class: \"data-table sortable\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"\", \"Check\", \"Tags\", \"Confidence\", \"Source\", \"Runs\", \"Pass Rate\", \"Last Run\"].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n const sorted = [...entries].sort((a, b) => a.slug.localeCompare(b.slug));\n const uid = \"cc-\" + Math.random().toString(36).slice(2, 8);\n sorted.forEach((check, i) => {\n buildCheckRow(check, i, uid).forEach((r) => tbody.append(r));\n });\n table.append(tbody);\n const pag = el(\"div\", { class: \"pagination\" });\n const card = el(\"div\", { class: \"card\" }, [table, pag]);\n panel.append(card);\n const emptyMsg = el(\"div\", {\n class: \"empty\",\n style: \"display:none\",\n text: \"No checks match your filters.\"\n });\n pag.before(emptyMsg);\n paginateGroupedRows(tbody, pag, 10);\n const filterVisible = /* @__PURE__ */ new WeakMap();\n function rowMatchesFilters(row) {\n const search = searchInput.value.toLowerCase();\n const tag = tagSelect.value;\n const source = sourceSelect.value;\n const slug = row.dataset.slug ?? \"\";\n const name = row.dataset.name ?? \"\";\n const rowTags = row.dataset.tags ?? \"\";\n const rowSource = row.dataset.source ?? \"\";\n const matchSearch = !search || slug.includes(search) || name.includes(search);\n const matchTag = !tag || rowTags.split(\",\").includes(tag);\n const matchSource = !source || rowSource === source;\n return matchSearch && matchTag && matchSource;\n }\n function collapseExpander(row, next) {\n if (!next?.classList.contains(EXPANDER_ROW)) return;\n next.style.display = \"none\";\n next.classList.remove(\"open\");\n if (!row.classList.contains(\"expanded\")) return;\n row.classList.remove(\"expanded\");\n const arrowTd = row.firstElementChild;\n if (arrowTd) arrowTd.textContent = \"\\u25B6\";\n }\n function markRowVisibility(allRows) {\n let visibleCount = 0;\n for (let i = 0; i < allRows.length; i++) {\n const row = allRows[i];\n if (row.classList.contains(EXPANDER_ROW)) continue;\n const visible = rowMatchesFilters(row);\n row.style.display = visible ? \"\" : \"none\";\n filterVisible.set(row, visible);\n if (visible) visibleCount++;\n collapseExpander(row, allRows[i + 1]);\n }\n return visibleCount;\n }\n function collectVisibleGroups(allRows) {\n const groups = [];\n for (let i = 0; i < allRows.length; i++) {\n const row = allRows[i];\n if (row.classList.contains(EXPANDER_ROW)) continue;\n if (!filterVisible.get(row)) continue;\n const group = [row];\n const next = allRows[i + 1];\n if (next?.classList.contains(EXPANDER_ROW)) group.push(next);\n groups.push(group);\n }\n return groups;\n }\n function applyFilters() {\n const allRows = [...tbody.children];\n const visibleCount = markRowVisibility(allRows);\n emptyMsg.style.display = visibleCount === 0 ? \"\" : \"none\";\n const hasFilters = searchInput.value || tagSelect.value || sourceSelect.value;\n if (hasFilters) {\n paginateFilteredGroups(pag, collectVisibleGroups(allRows));\n } else {\n paginateGroupedRows(tbody, pag, 10);\n }\n }\n searchInput.addEventListener(\"input\", applyFilters);\n tagSelect.addEventListener(\"change\", applyFilters);\n sourceSelect.addEventListener(\"change\", applyFilters);\n }\n\n // src/client/editor-link.ts\n function editorLinkUrl(filePath, line) {\n if (typeof EDITOR_PROTOCOL !== \"string\" || !EDITOR_PROTOCOL) return null;\n if (EDITOR_PROTOCOL === \"vscode\" || EDITOR_PROTOCOL === \"cursor\") {\n return EDITOR_PROTOCOL + \"://file/\" + filePath + \":\" + (line || 1);\n }\n return null;\n }\n\n // src/client/trace.ts\n function inferEntryPointHashes(catalog, indexes) {\n const entries = [];\n if (!catalog?.functions) return entries;\n for (const occ of indexes.byBodyHash.values()) {\n const isCli = occ.filePath === \"packages/cli/src/index.ts\";\n const callerList = indexes.callers.get(occ.bodyHash) ?? [];\n const isExportedRoot = occ.visibility === \"exported\" && callerList.length === 0;\n if (isCli || isExportedRoot) entries.push(occ.bodyHash);\n }\n return entries;\n }\n function traceFromEntry(targetHash, catalog, indexes) {\n if (!targetHash || !indexes?.byBodyHash.has(targetHash)) return null;\n const entries = inferEntryPointHashes(catalog, indexes);\n if (entries.length === 0) return null;\n const queue = [];\n const visited = /* @__PURE__ */ new Set();\n const parent = /* @__PURE__ */ new Map();\n for (const e of entries) {\n queue.push(e);\n visited.add(e);\n }\n while (queue.length > 0) {\n const v = queue.shift();\n if (v === targetHash) {\n const path = [];\n let cur = v;\n while (cur !== void 0) {\n path.unshift(cur);\n cur = parent.get(cur);\n }\n return path;\n }\n const adj = indexes.callees.get(v) ?? [];\n for (const w of adj) {\n if (visited.has(w)) continue;\n visited.add(w);\n parent.set(w, v);\n queue.push(w);\n }\n }\n return null;\n }\n\n // src/client/function-card.ts\n var drillIn = {};\n function occItem(c) {\n return el(\"li\", {\n \"data-body-hash\": c.bodyHash,\n text: displayName(c.simpleName) + \" \\u2014 \" + c.filePath + \":\" + c.line\n });\n }\n function buildMetaRow(occ) {\n const paramText = (occ.params ?? []).map((p) => (p.rest ? \"...\" : \"\") + p.name + (p.optional ? \"?\" : \"\")).join(\", \");\n const metaText = \"Body: \" + Math.max(0, (occ.endLine ?? occ.line ?? 0) - (occ.line ?? 0) + 1) + \" lines \\xB7 \" + (occ.kind ?? \"function\") + \" \\xB7 \" + (occ.visibility ?? \"module-local\") + (paramText ? \" \\xB7 params: (\" + paramText + \")\" : \"\") + (occ.returnType ? \" \\xB7 returns: \" + occ.returnType : \"\");\n return el(\"div\", { class: \"fc-meta\", text: metaText });\n }\n function buildCallersSection(occ) {\n const callerHashes = graphIndexes.callers.get(occ.bodyHash) ?? [];\n const section = el(\"div\", { class: \"fc-section\" });\n section.append(el(\"h4\", { text: \"Callers (\" + callerHashes.length + \")\" }));\n if (callerHashes.length === 0) {\n section.append(el(\"div\", { class: \"empty\", text: \"No callers in catalog.\" }));\n return section;\n }\n const list = el(\"ul\", { class: \"fc-list\" });\n const grouped = /* @__PURE__ */ new Map();\n for (const h of callerHashes) {\n const c = graphIndexes.byBodyHash.get(h);\n if (!c) continue;\n const pkg = pkgOf(c);\n const bucket = grouped.get(pkg);\n if (bucket) bucket.push(c);\n else grouped.set(pkg, [c]);\n }\n for (const pkg of [...grouped.keys()].sort()) {\n const bucket = grouped.get(pkg);\n list.append(el(\"li\", { class: \"external\", text: pkg + \" (\" + bucket.length + \")\" }));\n for (const c of bucket) list.append(occItem(c));\n }\n section.append(list);\n return section;\n }\n function countExternalCalls(occ) {\n return (occ.calls ?? []).reduce((n, e) => {\n let c = 0;\n for (const t of e.to ?? []) if (!graphIndexes.byBodyHash.has(t)) c++;\n return n + c + ((e.to ?? []).length === 0 ? 1 : 0);\n }, 0);\n }\n function buildCalleesSection(occ) {\n const calleeHashes = graphIndexes.callees.get(occ.bodyHash) ?? [];\n const externalCalls = countExternalCalls(occ);\n const section = el(\"div\", { class: \"fc-section\" });\n section.append(\n el(\"h4\", {\n text: \"Callees (\" + calleeHashes.length + \" resolved\" + (externalCalls > 0 ? \", \" + externalCalls + \" external\" : \"\") + \")\"\n })\n );\n if (calleeHashes.length === 0 && externalCalls === 0) {\n section.append(el(\"div\", { class: \"empty\", text: \"No callees.\" }));\n return section;\n }\n const list = el(\"ul\", { class: \"fc-list\" });\n for (const h of calleeHashes) {\n const c = graphIndexes.byBodyHash.get(h);\n if (c) list.append(occItem(c));\n }\n if (externalCalls > 0) {\n list.append(\n el(\"li\", { class: \"external\", text: externalCalls + \" external or unresolved call(s)\" })\n );\n }\n section.append(list);\n return section;\n }\n function buildActions(occ, card) {\n const actions = el(\"div\", { class: \"fc-actions\" });\n const editorUrl = editorLinkUrl(occ.filePath ?? \"\", occ.line ?? 1);\n if (editorUrl) {\n actions.append(el(\"a\", { class: \"fc-action\", href: editorUrl, text: \"Open in editor\" }));\n } else {\n actions.append(\n el(\"button\", {\n class: \"fc-action\",\n text: \"Copy path\",\n onclick: () => {\n if (navigator?.clipboard) {\n void navigator.clipboard.writeText(occ.filePath + \":\" + occ.line);\n }\n }\n })\n );\n }\n actions.append(\n el(\"button\", {\n class: \"fc-action\",\n text: \"Trace from entry\",\n onclick: () => {\n renderTraceInCard(card, traceFromEntry(occ.bodyHash, graphCatalog, graphIndexes));\n }\n })\n );\n return actions;\n }\n function getOrCreateOverlay() {\n const existing = document.querySelector(\".function-card-overlay\");\n if (existing) return existing;\n const overlay = el(\"div\", { class: \"function-card-overlay\" });\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) {\n closeFunctionCard();\n return;\n }\n const target = e.target;\n if (!(target instanceof Element)) return;\n const item = target.closest(\"li[data-body-hash]\");\n const hash = item?.dataset.bodyHash;\n if (hash) drillIn.open?.(hash);\n });\n document.body.append(overlay);\n return overlay;\n }\n function openFunctionCard(bodyHash) {\n if (!bodyHash) return;\n const occ = graphIndexes.byBodyHash.get(bodyHash);\n if (!occ) return;\n const overlay = getOrCreateOverlay();\n while (overlay.firstChild) overlay.firstChild.remove();\n const card = el(\"div\", { class: \"function-card\" });\n overlay.append(card);\n const closeBtn = el(\"button\", { class: \"fc-close\", text: \"\\xD7\", onclick: closeFunctionCard });\n card.append(closeBtn);\n card.append(el(\"h3\", { text: displayName(occ.simpleName ?? \"<anonymous>\") }));\n card.append(el(\"div\", { class: \"fc-loc\", text: occ.filePath + \":\" + occ.line }));\n card.append(buildMetaRow(occ));\n card.append(buildCallersSection(occ));\n card.append(buildCalleesSection(occ));\n card.append(buildActions(occ, card));\n closeBtn.focus();\n }\n function renderTraceInCard(card, path) {\n const old = card.querySelector(\".fc-trace-result\");\n if (old) old.remove();\n const section = el(\"div\", { class: \"fc-section fc-trace-result\" });\n section.append(el(\"h4\", { text: \"Trace from entry point\" }));\n if (!path || path.length === 0) {\n section.append(el(\"div\", { class: \"empty\", text: \"No path from any entry point.\" }));\n } else {\n const list = el(\"ol\", { class: \"fc-list\" });\n for (const h of path) {\n const occ = graphIndexes.byBodyHash.get(h);\n if (!occ) continue;\n list.append(occItem(occ));\n }\n section.append(list);\n }\n card.append(section);\n }\n function closeFunctionCard() {\n const overlay = document.querySelector(\".function-card-overlay\");\n if (overlay) overlay.remove();\n }\n drillIn.open = openFunctionCard;\n\n // src/client/indexes.ts\n function pushToBucket(map, key, value) {\n const bucket = map.get(key);\n if (bucket) bucket.push(value);\n else map.set(key, [value]);\n }\n function indexOccurrences(catalog, byBodyHash, occurrencesByHash, bySimpleName) {\n for (const name of Object.keys(catalog.functions ?? {})) {\n for (const occ of catalog.functions?.[name] ?? []) {\n byBodyHash.set(occ.bodyHash, occ);\n pushToBucket(occurrencesByHash, occ.bodyHash, occ);\n pushToBucket(bySimpleName, name, occ.bodyHash);\n }\n }\n }\n function indexEdges(byBodyHash, callees, callers) {\n for (const occ of byBodyHash.values()) {\n const out = [];\n for (const edge of occ.calls ?? []) {\n for (const target of edge.to ?? []) {\n if (!byBodyHash.has(target)) continue;\n out.push(target);\n pushToBucket(callers, target, occ.bodyHash);\n }\n }\n if (out.length > 0) callees.set(occ.bodyHash, out);\n }\n }\n function buildIndexes(catalog) {\n const byBodyHash = /* @__PURE__ */ new Map();\n const occurrencesByHash = /* @__PURE__ */ new Map();\n const bySimpleName = /* @__PURE__ */ new Map();\n const callees = /* @__PURE__ */ new Map();\n const callers = /* @__PURE__ */ new Map();\n if (!catalog?.functions) {\n return { byBodyHash, occurrencesByHash, bySimpleName, callees, callers };\n }\n indexOccurrences(catalog, byBodyHash, occurrencesByHash, bySimpleName);\n indexEdges(byBodyHash, callees, callers);\n return { byBodyHash, occurrencesByHash, bySimpleName, callees, callers };\n }\n function resolveCalleeOcc(target, callerOcc, indexes) {\n const candidates = indexes.occurrencesByHash?.get(target);\n if (!candidates || candidates.length === 0) return indexes.byBodyHash.get(target);\n if (candidates.length === 1) return candidates[0];\n const callerPkg = pkgOf(callerOcc);\n let samePkg = null;\n let lowest = candidates[0];\n for (const c of candidates) {\n if (!samePkg && pkgOf(c) === callerPkg) samePkg = c;\n if ((c.qualifiedName ?? \"\") < (lowest.qualifiedName ?? \"\")) lowest = c;\n }\n return samePkg ?? lowest;\n }\n\n // src/client/sortable.ts\n function collectRowGroups(tbody) {\n const allRows = [...tbody.children];\n const groups = [];\n for (let i = 0; i < allRows.length; i++) {\n const row = allRows[i];\n if (row.classList.contains(\"expander-row\")) continue;\n const group = [row];\n if (i + 1 < allRows.length && allRows[i + 1].classList.contains(\"expander-row\")) {\n group.push(allRows[i + 1]);\n }\n groups.push(group);\n }\n return groups;\n }\n function compareGroups(a, b, colIdx, asc) {\n const aText = (a[0].children[colIdx]?.textContent || \"\").trim();\n const bText = (b[0].children[colIdx]?.textContent || \"\").trim();\n const aNum = Number.parseFloat(aText);\n const bNum = Number.parseFloat(bText);\n if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) {\n return asc ? aNum - bNum : bNum - aNum;\n }\n const aDate = Date.parse(aText);\n const bDate = Date.parse(bText);\n if (!Number.isNaN(aDate) && !Number.isNaN(bDate)) {\n return asc ? aDate - bDate : bDate - aDate;\n }\n return asc ? aText.localeCompare(bText) : bText.localeCompare(aText);\n }\n function reorderRows(tbody, groups) {\n for (const group of groups) {\n for (const row of group) tbody.append(row);\n }\n }\n function repaginate(table, tbody, groups) {\n const pagContainer = table.parentElement?.querySelector(\".pagination\");\n if (!(pagContainer instanceof HTMLElement)) return;\n const hasExpanders = groups.some((g2) => g2.length > 1);\n if (hasExpanders) {\n paginateGroupedRows(tbody, pagContainer, 10);\n } else {\n paginateTable(tbody, pagContainer, 10);\n }\n }\n function sortByColumn(options) {\n const { table, tbody, headers, th, colIdx, state } = options;\n if (state.col === colIdx) {\n state.asc = !state.asc;\n } else {\n state.col = colIdx;\n state.asc = true;\n }\n for (const h of headers) h.dataset.sort = \"\";\n th.dataset.sort = state.asc ? \"asc\" : \"desc\";\n const groups = collectRowGroups(tbody);\n groups.sort((a, b) => compareGroups(a, b, colIdx, state.asc));\n reorderRows(tbody, groups);\n repaginate(table, tbody, groups);\n }\n function makeSortable(table) {\n const thead = table.querySelector(\"thead\");\n const tbody = table.querySelector(\"tbody\");\n if (!thead || !tbody) return;\n const headers = [...thead.querySelectorAll(\"th\")];\n const state = { col: -1, asc: true };\n headers.forEach((th, colIdx) => {\n if (!th.textContent?.trim()) return;\n th.style.cursor = \"pointer\";\n th.style.userSelect = \"none\";\n th.addEventListener(\"click\", () => {\n sortByColumn({ table, tbody, headers, th, colIdx, state });\n });\n });\n }\n setTimeout(() => {\n document.querySelectorAll(\".data-table.sortable\").forEach((t) => makeSortable(t));\n }, 0);\n\n // src/client/session-detail.ts\n var RULE_METRIC_COLUMNS = {\n \"graph:large-function\": { label: \"Lines\", key: \"bodyLines\" },\n \"graph:high-blast-untested\": { label: \"Score\", key: \"blast\" },\n \"graph:wide-function\": { label: \"Parameters\", key: \"paramCount\" },\n \"graph:cycle\": { label: \"Call Cycle\", key: \"sccSize\" }\n };\n var DIM2 = \"color:var(--text-dim)\";\n var FINDING_CELL_PAD = \"padding:6px 12px\";\n var EM_DASH2 = \"\\u2014\";\n function formatMetricValue(mv) {\n if (typeof mv === \"number\" || typeof mv === \"string\" || typeof mv === \"boolean\") {\n return String(mv);\n }\n return EM_DASH2;\n }\n function formatFindingFile(f) {\n if (!f.filePath) return EM_DASH2;\n return f.line ? f.filePath + \":\" + f.line : f.filePath;\n }\n function countSeverity(check, severity) {\n return check.findings ? check.findings.filter((f) => f.severity === severity).length : 0;\n }\n function sortChecksBySeverity(checks) {\n return [...checks].sort((a, b) => {\n const aErrors = countSeverity(a, \"error\");\n const bErrors = countSeverity(b, \"error\");\n if (bErrors !== aErrors) return bErrors - aErrors;\n return countSeverity(b, \"warning\") - countSeverity(a, \"warning\");\n });\n }\n function buildFindingsTable(check, metricColumn) {\n const fTable = el(\"table\", { class: \"data-table\", style: \"margin:0;border:none\" });\n const fHead = el(\"thead\");\n const fHeaderRow = el(\"tr\");\n const fHeaders = metricColumn ? [\"Severity\", \"File\", metricColumn.label, \"Suggestion\"] : [\"Severity\", \"Message\", \"File\", \"Suggestion\"];\n fHeaders.forEach((h) => {\n fHeaderRow.append(el(\"th\", { text: h, style: \"font-size:11px;padding:6px 12px\" }));\n });\n fHead.append(fHeaderRow);\n fTable.append(fHead);\n const fBody = el(\"tbody\");\n const sevWeight = { error: 0, warning: 1 };\n const sortedFindings = [...check.findings ?? []].sort(\n (a, b) => (sevWeight[a.severity ?? \"\"] ?? 2) - (sevWeight[b.severity ?? \"\"] ?? 2)\n );\n const fileCellStyle = FINDING_CELL_PAD + \";\" + DIM2 + \";font-size:12px\";\n sortedFindings.forEach((f) => {\n const fRow = el(\"tr\");\n const sevCell = el(\"td\", { style: FINDING_CELL_PAD });\n sevCell.append(el(\"span\", { class: \"finding-sev \" + f.severity, text: f.severity }));\n fRow.append(sevCell);\n const fileText = formatFindingFile(f);\n if (metricColumn) {\n fRow.append(el(\"td\", { text: fileText, style: fileCellStyle }));\n const mv = f.metadata ? f.metadata[metricColumn.key] : void 0;\n fRow.append(\n el(\"td\", { text: formatMetricValue(mv), style: FINDING_CELL_PAD + \";font-size:13px\" })\n );\n } else {\n fRow.append(el(\"td\", { text: f.message, style: FINDING_CELL_PAD + \";font-size:13px\" }));\n fRow.append(el(\"td\", { text: fileText, style: fileCellStyle }));\n }\n fRow.append(\n el(\"td\", {\n text: f.suggestion ?? EM_DASH2,\n style: FINDING_CELL_PAD + \";color:var(--accent);font-size:12px\"\n })\n );\n fBody.append(fRow);\n });\n fTable.append(fBody);\n return fTable;\n }\n function detailSubline(session, totalErrors, totalWarnings) {\n const sub = el(\"div\", { style: \"color:var(--text-dim);font-size:12px\" });\n const countParts = [];\n if (totalErrors > 0) countParts.push(totalErrors + \" error\" + (totalErrors === 1 ? \"\" : \"s\"));\n if (totalWarnings > 0)\n countParts.push(totalWarnings + \" warning\" + (totalWarnings === 1 ? \"\" : \"s\"));\n const countsStr = countParts.length > 0 ? \" \\u2014 \" + countParts.join(\", \") : \"\";\n sub.textContent = session.cwd + (session.recipe ? \" \\u2014 recipe: \" + session.recipe : \"\") + countsStr;\n return sub;\n }\n function buildDetailHeader(session, totalErrors, totalWarnings) {\n const headerRow = el(\"div\", {\n style: \"display:flex;align-items:center;justify-content:space-between;margin-bottom:16px\"\n });\n const headerLeft = el(\"div\");\n headerLeft.append(\n el(\"h3\", {\n text: \"Session Detail \\u2014 \" + new Date(session.startedAt).toLocaleString(),\n style: \"margin-bottom:4px\"\n })\n );\n headerLeft.append(detailSubline(session, totalErrors, totalWarnings));\n headerRow.append(headerLeft);\n return headerRow;\n }\n function buildCountCell(value, activeColor) {\n return el(\"td\", { text: \"\" + value, style: value > 0 ? activeColor : DIM2 });\n }\n function buildCheckExpanderRow(check, expanderId, checkStatusVal, itemHeadersLength) {\n const expRow = el(\"tr\", {\n id: expanderId,\n class: \"expander-row\",\n \"data-check-status\": checkStatusVal\n });\n const expCell = el(\"td\", { colspan: \"\" + itemHeadersLength, style: \"padding:0\" });\n const expContent = el(\"div\", { class: \"expander-content\" });\n const fTable = buildFindingsTable(check, RULE_METRIC_COLUMNS[check.checkSlug]);\n const fScroll = el(\"div\", { style: \"overflow-x:auto;max-width:100%\" }, [fTable]);\n expContent.append(fScroll);\n expCell.append(expContent);\n expRow.append(expCell);\n return expRow;\n }\n function buildDetailHead(itemHeaders) {\n const thead = el(\"thead\");\n const thRow = el(\"tr\");\n itemHeaders.forEach((h) => {\n thRow.append(el(\"th\", { text: h }));\n });\n thead.append(thRow);\n return thead;\n }\n function appendCheckRow(detailBody, check, i, ctx) {\n const checkErrors = countSeverity(check, \"error\");\n const checkWarnings = countSeverity(check, \"warning\");\n const findingsTotal = checkErrors + checkWarnings;\n const hasFindings = findingsTotal > 0;\n const expanderId = ctx.filterUid + \"-exp-\" + i;\n const checkStatusVal = check.passed ? \"pass\" : \"fail\";\n const arrowCell = el(\"td\", {\n style: \"width:24px;text-align:center;\" + DIM2 + \";font-size:12px\"\n });\n if (hasFindings) arrowCell.textContent = \"\\u25B6\";\n const row = el(\"tr\", {\n class: hasFindings ? \"clickable\" : \"\",\n \"data-check-status\": checkStatusVal,\n onclick: hasFindings ? () => {\n const exp = document.querySelector(\"#\" + expanderId);\n if (exp) {\n const isOpen = exp.classList.toggle(\"open\");\n exp.style.display = isOpen ? \"table-row\" : \"none\";\n arrowCell.textContent = isOpen ? \"\\u25BC\" : \"\\u25B6\";\n }\n row.classList.toggle(\"expanded\");\n } : void 0\n });\n row.append(arrowCell);\n row.append(el(\"td\", { text: check.checkSlug, style: \"font-weight:500\" }));\n const statusCell = el(\"td\");\n statusCell.append(\n el(\"span\", {\n class: \"badge \" + (check.passed ? \"badge-pass\" : \"badge-fail\"),\n text: check.passed ? \"PASS\" : \"FAIL\"\n })\n );\n row.append(statusCell);\n row.append(buildCountCell(checkErrors, \"color:var(--error)\"));\n row.append(buildCountCell(checkWarnings, \"color:var(--warning)\"));\n row.append(buildCountCell(findingsTotal, \"color:var(--text)\"));\n if (ctx.showDuration)\n row.append(\n el(\"td\", {\n text: (check.durationMs ?? 0) > 0 ? check.durationMs + \"ms\" : \"0ms\",\n style: DIM2\n })\n );\n detailBody.append(row);\n if (hasFindings) {\n detailBody.append(\n buildCheckExpanderRow(check, expanderId, checkStatusVal, ctx.itemHeadersLength)\n );\n }\n }\n function renderNoDetail(detailContainer, session) {\n detailContainer.append(\n el(\"h3\", {\n text: \"Session Detail \\u2014 \" + new Date(session.startedAt).toLocaleString(),\n style: \"margin-bottom:4px\"\n })\n );\n detailContainer.append(\n el(\"div\", { class: \"empty\", text: \"No detail recorded for this session.\" })\n );\n }\n function renderEmptyChecks(detailContainer, session) {\n detailContainer.append(\n el(\"h3\", {\n text: \"Session Detail \\u2014 \" + new Date(session.startedAt).toLocaleString(),\n style: \"margin-bottom:4px\"\n })\n );\n const sm = session.payload?.summary ?? {};\n const clean = (sm.errors ?? 0) === 0 && (sm.warnings ?? 0) === 0;\n const subline = el(\"div\", { style: DIM2 + \";font-size:12px;margin-bottom:12px\" });\n subline.textContent = session.cwd + (session.recipe ? \" \\u2014 recipe: \" + session.recipe : \"\");\n detailContainer.append(subline);\n detailContainer.append(\n el(\"div\", {\n class: \"empty\",\n text: clean ? \"No findings \\u2014 this run was clean. Every rule passed with zero violations.\" : \"No per-rule detail was recorded for this run.\"\n })\n );\n }\n function buildDetailTable(checks, tool, filterUid) {\n const itemColumnByTool = { graph: \"Rule\", yagni: \"Detector\" };\n const itemColumn = itemColumnByTool[tool] ?? \"Check\";\n const showDuration = tool !== \"graph\";\n const itemHeaders = [\"\", itemColumn, \"Status\", \"Errors\", \"Warnings\", \"Findings\"];\n if (showDuration) itemHeaders.push(\"Duration\");\n const table = el(\"table\", { class: \"data-table sortable\" });\n table.append(buildDetailHead(itemHeaders));\n const detailBody = el(\"tbody\");\n sortChecksBySeverity(checks).forEach((check, i) => {\n appendCheckRow(detailBody, check, i, {\n filterUid,\n itemHeadersLength: itemHeaders.length,\n showDuration\n });\n });\n table.append(detailBody);\n const detailPag = el(\"div\", { class: \"pagination\" });\n const card = el(\"div\", { class: \"card\" }, [table, detailPag]);\n makeSortable(table);\n paginateGroupedRows(detailBody, detailPag, 10);\n return card;\n }\n function renderSessionDetail(detailContainer, session, idx, tool) {\n detailContainer.style.display = \"block\";\n while (detailContainer.firstChild) detailContainer.firstChild.remove();\n if (!session.payload) {\n renderNoDetail(detailContainer, session);\n return;\n }\n const checks = session.payload.checks ?? [];\n if (checks.length === 0) {\n renderEmptyChecks(detailContainer, session);\n return;\n }\n let totalErrors = 0;\n let totalWarnings = 0;\n checks.forEach((c) => {\n totalErrors += countSeverity(c, \"error\");\n totalWarnings += countSeverity(c, \"warning\");\n });\n detailContainer.append(buildDetailHeader(session, totalErrors, totalWarnings));\n const filterUid = \"df-\" + tool + \"-\" + idx + \"-\" + Math.random().toString(36).slice(2, 6);\n detailContainer.append(buildDetailTable(checks, tool, filterUid));\n }\n\n // src/client/sessions.ts\n var DIM3 = \"color:var(--text-dim)\";\n function scoreColorStyle(score) {\n if (score >= 90) return \"color:var(--success)\";\n if (score >= 70) return \"color:var(--warning)\";\n return \"color:var(--error)\";\n }\n var EMPTY_SUMMARY = {\n total: 0,\n passed: 0,\n failed: 0,\n errors: 0,\n warnings: 0\n };\n function legacyRunOutcome(s) {\n return s.passed === false ? \"failed\" : \"passed\";\n }\n function resolvedRunOutcome(s) {\n const stored = s.runOutcome ?? legacyRunOutcome(s);\n if (stored === \"error\") return \"error\";\n if (stored === \"degraded\") return \"degraded\";\n if (stored === \"failed\") return \"fail\";\n if (stored === \"passed\") return \"pass\";\n const sm = s.payload?.summary ?? {};\n if ((sm.failed ?? 0) > 0) return \"fail\";\n if ((sm.warnings ?? 0) > 0) return \"warn\";\n return \"pass\";\n }\n function sessionStatus(s) {\n return resolvedRunOutcome(s);\n }\n function statusBadge(status) {\n const labels = {\n fail: \"FAIL\",\n warn: \"WARN\",\n pass: \"PASS\",\n error: \"ERROR\",\n degraded: \"DEGRADED\"\n };\n const classes = {\n fail: \"badge-fail\",\n warn: \"badge-warn\",\n pass: \"badge-pass\",\n error: \"badge-fail\",\n degraded: \"badge-warn\"\n };\n return el(\"span\", {\n class: \"badge \" + classes[status],\n text: labels[status]\n });\n }\n function sessionScoreStyle(s) {\n const outcome = resolvedRunOutcome(s);\n if (outcome === \"error\") return \"color:var(--error)\";\n if (outcome === \"degraded\") return \"color:var(--warning)\";\n return scoreColorStyle(s.score);\n }\n function buildSessionHead() {\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\n \"Timestamp\",\n \"Recipe\",\n \"Pass Rate\",\n \"Status\",\n \"Passed\",\n \"Failed\",\n \"Findings\",\n \"Duration\"\n ].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n return thead;\n }\n function renderSessionTable(panel, toolSessions, _accentColor) {\n if (toolSessions.length === 0) {\n panel.append(el(\"div\", { class: \"empty\", text: \"No sessions yet.\" }));\n return;\n }\n const tool = toolSessions[0].tool;\n const table = el(\"table\", { class: \"data-table sortable\" });\n table.append(buildSessionHead());\n const detailContainer = el(\"div\", {\n id: \"detail-\" + tool + \"-\" + Math.random().toString(36).slice(2, 8),\n class: \"section\",\n style: \"display:none\"\n });\n const tbody = el(\"tbody\");\n toolSessions.forEach((s, idx) => {\n const sc = sessionScoreStyle(s);\n const sm = s.payload?.summary ?? EMPTY_SUMMARY;\n const row = el(\"tr\", {\n class: \"clickable\",\n id: \"session-row-\" + tool + \"-\" + idx,\n \"data-session-id\": s.id,\n onclick: () => {\n tbody.querySelectorAll(\"tr.selected\").forEach((r) => r.classList.remove(\"selected\"));\n row.classList.add(\"selected\");\n renderSessionDetail(detailContainer, s, idx, tool);\n }\n });\n row.append(\n el(\"td\", {\n class: \"cell-nowrap\",\n text: new Date(s.startedAt).toLocaleString()\n })\n );\n row.append(\n el(\"td\", {\n text: s.recipe ?? \"default\",\n style: \"color:var(--text-muted)\"\n })\n );\n const scoreCell = el(\"td\", { style: \"font-weight:600;\" + sc });\n scoreCell.textContent = s.score + \"%\";\n row.append(scoreCell);\n const badgeCell = el(\"td\");\n badgeCell.append(statusBadge(sessionStatus(s)));\n row.append(badgeCell);\n row.append(el(\"td\", { text: \"\" + (sm.passed ?? 0), style: \"color:var(--success)\" }));\n row.append(\n el(\"td\", {\n text: \"\" + (sm.failed ?? 0),\n style: (sm.failed ?? 0) > 0 ? \"color:var(--error)\" : DIM3\n })\n );\n row.append(el(\"td\", { text: \"\" + ((sm.errors ?? 0) + (sm.warnings ?? 0)) }));\n row.append(el(\"td\", { text: (s.durationMs / 1e3).toFixed(1) + \"s\", style: DIM3 }));\n tbody.append(row);\n });\n table.append(tbody);\n const sessionPag = el(\"div\", { class: \"pagination\" });\n const sec = el(\"div\", { class: \"section\" }, [\n el(\"h3\", { text: \"Sessions (\" + toolSessions.length + \")\" }),\n el(\"div\", { class: \"card\" }, [table, sessionPag])\n ]);\n panel.append(sec);\n paginateTable(tbody, sessionPag, 10);\n panel.append(detailContainer);\n renderSessionDetail(detailContainer, toolSessions[0], 0, tool);\n const firstRow = tbody.querySelector(\"tr\");\n if (firstRow) firstRow.classList.add(\"selected\");\n }\n\n // src/client/subtab-bar.ts\n function renderSubtabBar(panel, subtabs) {\n const subtabBar = el(\"div\", { class: \"subtab-bar\" });\n const panels = {};\n subtabs.forEach((t, i) => {\n const subtab = el(\"div\", {\n class: \"subtab\" + (i === 0 ? \" active\" : \"\"),\n \"data-subtab\": t.id,\n text: t.label\n });\n subtabBar.append(subtab);\n const subpanel = el(\"div\", {\n class: \"subtab-panel\" + (i === 0 ? \" active\" : \"\"),\n id: panel.id + \"-\" + t.id\n });\n panels[t.id] = subpanel;\n });\n panel.append(subtabBar);\n subtabs.forEach((t) => panel.append(panels[t.id]));\n subtabBar.addEventListener(\"click\", (e) => {\n const tab = e.target?.closest(\".subtab\");\n if (!tab) return;\n subtabBar.querySelectorAll(\".subtab\").forEach((t) => t.classList.remove(\"active\"));\n tab.classList.add(\"active\");\n subtabs.forEach((t) => panels[t.id].classList.remove(\"active\"));\n panels[tab.dataset.subtab].classList.add(\"active\");\n });\n subtabs.forEach((t) => t.render(panels[t.id]));\n return panels;\n }\n\n // src/client/tab-activators.ts\n var tabActivators = {};\n function registerTabActivator(key, fn) {\n tabActivators[key] = fn;\n }\n function activateTabForSession(session) {\n if (!session) return false;\n const fn = tabActivators[session.tool];\n if (typeof fn !== \"function\") return false;\n fn(session.id);\n return true;\n }\n\n // src/client/views-registry.ts\n var views = [];\n var activeViewId = null;\n function getView(id) {\n for (const v of views) if (v.id === id) return v;\n return null;\n }\n function renderActiveView() {\n if (!activeViewId) return;\n const view = getView(activeViewId);\n if (!view) return;\n const container = document.querySelector(\"#code-paths-view-\" + view.id);\n if (!(container instanceof HTMLElement)) return;\n view.render(container, graphCatalog, graphIndexes, filterState);\n }\n function activateView(id, options = {}) {\n const view = getView(id);\n if (!view) return;\n activeViewId = id;\n document.querySelectorAll(\".code-paths-tab\").forEach((t) => {\n t.classList.toggle(\"active\", t.dataset.view === id);\n });\n document.querySelectorAll(\".code-paths-view\").forEach((p) => {\n p.classList.toggle(\"active\", p.id === \"code-paths-view-\" + id);\n });\n const updateHash = options.updateHash !== false;\n const next = \"#code-paths/\" + id;\n if (updateHash && globalThis.window !== void 0 && globalThis.location.hash !== next) {\n try {\n history.replaceState(null, \"\", next);\n } catch {\n }\n }\n renderActiveView();\n if (typeof view.onActivate === \"function\") {\n try {\n view.onActivate();\n } catch {\n }\n }\n }\n\n // src/client/help-drawer.ts\n function openHelpDrawer(viewId) {\n const view = getView(viewId);\n if (!view?.help) return;\n closeHelpDrawer();\n const overlay = el(\"div\", { class: \"help-drawer-overlay\", id: \"help-drawer-overlay\" });\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) closeHelpDrawer();\n });\n const drawer = el(\"aside\", {\n class: \"help-drawer\",\n role: \"dialog\",\n // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty-string title must also fall back to the label (byte-identical to the legacy emitter).\n \"aria-label\": view.help.title || view.label\n });\n const header = el(\"div\", { class: \"help-drawer-header\" });\n header.append(el(\"h3\", { text: view.help.title || view.label }));\n const closeBtn = el(\"button\", {\n class: \"help-drawer-close\",\n \"aria-label\": \"Close\",\n text: \"\\xD7\",\n onclick: closeHelpDrawer\n });\n header.append(closeBtn);\n drawer.append(header);\n const body = el(\"div\", { class: \"help-drawer-body\" });\n for (const section of view.help.sections ?? []) {\n body.append(el(\"h4\", { text: section.heading }));\n body.append(el(\"p\", { text: section.body }));\n }\n drawer.append(body);\n overlay.append(drawer);\n document.body.append(overlay);\n requestAnimationFrame(() => overlay.classList.add(\"open\"));\n closeBtn.focus();\n }\n function closeHelpDrawer() {\n const existing = document.querySelector(\"#help-drawer-overlay\");\n if (existing) existing.remove();\n }\n document.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\" && document.querySelector(\"#help-drawer-overlay\")) closeHelpDrawer();\n });\n\n // src/client/function-row.ts\n function makeSectionHeading(text, viewId) {\n const h3 = el(\"h3\");\n h3.append(text);\n if (viewId) {\n const info = el(\"button\", {\n class: \"section-info\",\n \"aria-label\": \"About this view\",\n title: \"About this view\",\n text: \"i\"\n });\n info.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n openHelpDrawer(viewId);\n });\n h3.append(info);\n }\n return h3;\n }\n function renderFunctionRows(container, options) {\n const { occurrences, columns, heading, viewId, skipHeading } = options;\n while (container.firstChild) container.firstChild.remove();\n if (!occurrences || occurrences.length === 0) {\n container.append(el(\"div\", { class: \"empty\", text: \"No functions to show.\" }));\n return;\n }\n const headingText = (heading || \"Results\") + \" (\" + occurrences.length + \")\";\n const section = el(\"div\", { class: \"section\" });\n if (!skipHeading) section.append(makeSectionHeading(headingText, viewId));\n const card = el(\"div\", { class: \"card\" });\n const table = el(\"table\", { class: \"data-table sortable\" });\n const thead = el(\"thead\");\n const headRow = el(\"tr\");\n for (const col of columns) headRow.append(el(\"th\", { text: col.label }));\n thead.append(headRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n for (const occ of occurrences) {\n const tr = el(\"tr\", { class: \"clickable\", \"data-body-hash\": occ.bodyHash });\n for (const col of columns) {\n const v = col.value(occ);\n tr.append(el(\"td\", { text: v == null ? \"\" : String(v) }));\n }\n tbody.append(tr);\n }\n table.append(tbody);\n const pag = el(\"div\", { class: \"pagination\" });\n card.append(table);\n card.append(pag);\n section.append(card);\n container.append(section);\n paginateTable(tbody, pag, 10);\n makeSortable(table);\n }\n\n // src/client/view-coupling.ts\n views.push({\n id: \"coupling\",\n label: \"Coupling\",\n help: {\n title: \"Package coupling heat map\",\n sections: [\n {\n heading: \"What this is\",\n body: \"A caller-by-callee matrix. Each cell counts the static call edges from one package into another. Darker shading = more calls. Click a cell to see the actual call sites.\"\n },\n {\n heading: \"Why you care\",\n body: \"Layered architectures want a clear flow of dependencies. Surprises in this matrix \\u2014 a leaf package calling into core, a kernel package calling a peer \\u2014 are usually layering violations or stale abstractions.\"\n },\n {\n heading: \"How to read it\",\n body: 'Read rows as \"this package calls\". Read columns as \"this package is called by\". The diagonal (a package calling itself) is normally densest. Off-diagonal density tells you which packages know about each other; absence of a cell means no call sites in that direction.'\n },\n {\n heading: \"What to do\",\n body: \"Cells you did not expect deserve investigation. If a package is called by everyone (a column with many filled cells), that is a hub \\u2014 make sure its API is intentional. If two peers both call into each other, you may have a circular dependency hiding in plain sight.\"\n }\n ]\n },\n render(container, catalog, indexes, filterState2) {\n while (container.firstChild) container.firstChild.remove();\n if (!catalog?.functions) {\n container.append(el(\"div\", { class: \"empty\", text: \"No catalog loaded.\" }));\n return;\n }\n const features = catalog.features;\n const edges = features?.edge ?? null;\n if (!edges) {\n container.append(\n el(\"div\", {\n class: \"empty\",\n text: \"No coupling data in this catalog. Re-run the graph for a dashboard to compute the package matrix.\"\n })\n );\n return;\n }\n const { counts, max } = buildCounts(edges);\n const pkgs = packageSet(counts);\n if (pkgs.length === 0) {\n container.append(el(\"div\", { class: \"empty\", text: \"No cross-package calls found.\" }));\n return;\n }\n const section = el(\"div\", { class: \"section\" });\n section.append(\n makeSectionHeading(\"Package coupling (\" + pkgs.length + \"\\xD7\" + pkgs.length + \")\", \"coupling\")\n );\n const toolbar = el(\"div\", { class: \"coupling-toolbar\" });\n toolbar.append(\n el(\"button\", {\n class: \"coupling-export-btn\",\n text: \"Export CSV\",\n onclick: () => downloadCouplingCsv(counts)\n })\n );\n section.append(toolbar);\n const card = el(\"div\", { class: \"card\" });\n const scroll = el(\"div\", { class: \"coupling-scroll\" });\n scroll.append(\n buildCouplingTable(\n pkgs,\n counts,\n max,\n (caller, callee) => openCouplingDrilldown(caller, callee, indexes, filterState2)\n )\n );\n card.append(scroll);\n section.append(card);\n container.append(section);\n }\n });\n function buildCounts(edges) {\n const counts = /* @__PURE__ */ new Map();\n let max = 0;\n for (const e of edges) {\n let row = counts.get(e.callerPackage);\n if (!row) {\n row = /* @__PURE__ */ new Map();\n counts.set(e.callerPackage, row);\n }\n row.set(e.calleePackage, e.count);\n if (e.count > max) max = e.count;\n }\n return { counts, max };\n }\n function buildCouplingTable(pkgs, counts, max, onCell) {\n const table = el(\"table\", { class: \"coupling-table\" });\n const thead = el(\"thead\");\n const headRow = el(\"tr\");\n headRow.append(el(\"th\", { class: \"row-label\", text: \"caller \\\\ callee\" }));\n for (const callee of pkgs) headRow.append(el(\"th\", { text: callee }));\n thead.append(headRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n for (const caller of pkgs) {\n const row = el(\"tr\");\n row.append(el(\"th\", { class: \"row-label\", text: caller }));\n const rowCounts = counts.get(caller);\n for (const callee of pkgs) {\n const c = rowCounts?.get(callee) ?? 0;\n if (c === 0) {\n row.append(el(\"td\", { class: \"coupling-cell empty\", text: \"\\xB7\" }));\n } else {\n const density = max > 0 ? (c / max).toFixed(2) : \"0\";\n row.append(\n el(\"td\", {\n class: \"coupling-cell\",\n style: \"--coupling-density: \" + density,\n text: String(c),\n \"data-caller\": caller,\n \"data-callee\": callee,\n onclick: () => onCell(caller, callee)\n })\n );\n }\n }\n tbody.append(row);\n }\n table.append(tbody);\n return table;\n }\n function packageSet(counts) {\n const callees = [];\n for (const m of counts.values()) for (const k of m.keys()) callees.push(k);\n return [.../* @__PURE__ */ new Set([...counts.keys(), ...callees])].sort();\n }\n function buildCouplingCsv(counts) {\n const pkgs = packageSet(counts);\n const header = [csvField(\"caller \\\\ callee\"), ...pkgs.map(csvField)].join(\",\");\n const rows = [header];\n for (const caller of pkgs) {\n const row = counts.get(caller);\n const cells = [csvField(caller)];\n for (const callee of pkgs) cells.push(String(row?.get(callee) ?? 0));\n rows.push(cells.join(\",\"));\n }\n return rows.join(\"\\n\");\n }\n function csvField(value) {\n let s = value;\n if (s.length > 0 && /^[=+\\-@\\t\\r]/.test(s)) s = \"'\" + s;\n if (/[\"\\r\\n,]/.test(s)) return '\"' + s.replaceAll('\"', '\"\"') + '\"';\n return s;\n }\n function downloadCouplingCsv(counts) {\n const csv = buildCouplingCsv(counts);\n try {\n const blob = new Blob([csv], { type: \"text/csv;charset=utf-8\" });\n const url = URL.createObjectURL(blob);\n const a = el(\"a\", { href: url, download: \"coupling.csv\", style: \"display:none\" });\n document.body.append(a);\n a.click();\n a.remove();\n setTimeout(() => {\n try {\n URL.revokeObjectURL(url);\n } catch {\n }\n }, 0);\n } catch {\n }\n }\n function callSitesFromOcc(occ, calleePkg, indexes) {\n const sites = [];\n for (const edge of occ.calls ?? []) {\n for (const target of edge.to ?? []) {\n const callee = resolveCalleeOcc(target, occ, indexes);\n if (callee && pkgOf(callee) === calleePkg) sites.push({ occ, callee, line: edge.line });\n }\n }\n return sites;\n }\n function collectCallSites(callerPkg, calleePkg, indexes, filterState2) {\n const sites = [];\n for (const occ of indexes.byBodyHash.values()) {\n if (!passesFilter(occ, filterState2)) continue;\n if (pkgOf(occ) !== callerPkg) continue;\n sites.push(...callSitesFromOcc(occ, calleePkg, indexes));\n if (sites.length > 200) return sites;\n }\n return sites;\n }\n function openCouplingDrilldown(callerPkg, calleePkg, indexes, filterState2) {\n let overlay = document.querySelector(\".function-card-overlay\");\n if (!overlay) {\n overlay = el(\"div\", { class: \"function-card-overlay\" });\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) closeFunctionCard();\n });\n document.body.append(overlay);\n }\n while (overlay.firstChild) overlay.firstChild.remove();\n const card = el(\"div\", { class: \"function-card\" });\n overlay.append(card);\n card.append(el(\"button\", { class: \"fc-close\", text: \"\\xD7\", onclick: closeFunctionCard }));\n card.append(el(\"h3\", { text: callerPkg + \" \\u2192 \" + calleePkg }));\n card.append(el(\"div\", { class: \"fc-loc\", text: \"Call sites between these packages\" }));\n const list = el(\"ul\", { class: \"fc-list\" });\n const sites = collectCallSites(callerPkg, calleePkg, indexes, filterState2);\n for (const { occ, callee, line } of sites) {\n const item = el(\"li\", {\n \"data-body-hash\": occ.bodyHash,\n text: displayName(occ.simpleName) + \" \\u2192 \" + displayName(callee.simpleName) + \" (\" + occ.filePath + \":\" + line + \")\"\n });\n const hash = occ.bodyHash;\n item.addEventListener(\"click\", () => openFunctionCard(hash));\n list.append(item);\n }\n if (sites.length === 0)\n list.append(el(\"li\", { class: \"external\", text: \"No call sites found.\" }));\n card.append(list);\n }\n\n // src/client/view-template.ts\n function defineRankedView(config) {\n const predicate = config.predicate ?? ((occ, filterState2) => passesFilter(occ, filterState2));\n const rowExtras = config.rowExtras ?? (() => ({}));\n const searchByName = config.searchByName === true;\n const filterByKP = config.filterByKindPackage === true;\n const toggle = config.filterToggle ?? null;\n const hasControls = searchByName || filterByKP || toggle !== null;\n const state = { query: \"\", kind: \"\", pkg: \"\", toggleOn: false };\n function shouldShowRow(occ) {\n if (filterByKP && state.kind && occ.kind !== state.kind) return false;\n if (filterByKP && state.pkg && pkgOf(occ) !== state.pkg) return false;\n const q = searchByName ? state.query.trim().toLowerCase() : \"\";\n if (q.length > 0 && !(occ.simpleName ?? \"\").toLowerCase().includes(q)) return false;\n return !(toggle && state.toggleOn && !toggle.predicate(occ));\n }\n function buildKindPackage(controlsRow, catalog, onChange) {\n controlsRow.append(el(\"span\", { class: \"code-paths-graph-toolbar-label\", text: \"Kind\" }));\n const fnKindSel = el(\"select\", {\n class: \"code-paths-graph-select\",\n \"data-control\": \"fn-kind\"\n });\n fnKindSel.append(el(\"option\", { value: \"\", text: \"All kinds\" }));\n for (const k of KIND_LIST) {\n const o = el(\"option\", { value: k, text: k });\n if (k === state.kind) o.selected = true;\n fnKindSel.append(o);\n }\n fnKindSel.addEventListener(\"change\", (e) => {\n state.kind = e.target.value || \"\";\n onChange();\n });\n controlsRow.append(fnKindSel);\n controlsRow.append(el(\"span\", { class: \"code-paths-graph-toolbar-label\", text: \"Package\" }));\n const fnPkgSel = el(\"select\", {\n class: \"code-paths-graph-select\",\n \"data-control\": \"fn-package\"\n });\n fnPkgSel.append(el(\"option\", { value: \"\", text: \"All packages\" }));\n for (const p of packagesInCatalog(catalog)) {\n const o = el(\"option\", { value: p, text: p });\n if (p === state.pkg) o.selected = true;\n fnPkgSel.append(o);\n }\n fnPkgSel.addEventListener(\"change\", (e) => {\n state.pkg = e.target.value || \"\";\n onChange();\n });\n controlsRow.append(fnPkgSel);\n }\n function buildSearchInput(controlsRow, onChange) {\n const searchInput = el(\"input\", {\n type: \"search\",\n class: \"search-input code-paths-search\",\n id: \"code-paths-search-\" + config.id,\n placeholder: \"Filter functions by name\\u2026\"\n });\n searchInput.value = state.query;\n searchInput.addEventListener(\"input\", (e) => {\n state.query = e.target.value || \"\";\n onChange();\n });\n controlsRow.append(searchInput);\n }\n function buildToggle(controlsRow, t, onChange) {\n const toggleLabel = el(\"label\", { class: \"code-paths-graph-checkbox\" });\n const toggleCb = el(\"input\", {\n type: \"checkbox\",\n \"data-control\": \"fn-toggle\"\n });\n toggleCb.checked = state.toggleOn;\n toggleCb.addEventListener(\"change\", () => {\n state.toggleOn = toggleCb.checked;\n onChange();\n });\n toggleLabel.append(toggleCb);\n toggleLabel.append(\" \" + t.label);\n controlsRow.append(toggleLabel);\n }\n views.push({\n id: config.id,\n label: config.label,\n help: config.help,\n render(container, catalog, indexes, filterState2) {\n while (container.firstChild) container.firstChild.remove();\n if (!catalog?.functions) {\n container.append(el(\"div\", { class: \"empty\", text: \"No catalog loaded.\" }));\n return;\n }\n const ranked = [];\n for (const occ of indexes.byBodyHash.values()) {\n if (!predicate(occ, filterState2)) continue;\n const metric = config.metric(occ, indexes);\n if (metric === false) continue;\n ranked.push({ occ, metric });\n }\n ranked.sort((a, b) => b.metric - a.metric);\n if (ranked.length === 0) {\n container.append(el(\"div\", { class: \"empty\", text: config.emptyMessage }));\n return;\n }\n const headingHost = el(\"div\");\n container.append(headingHost);\n const rowsHost = el(\"div\");\n function renderRows() {\n const filtered = ranked.filter((r) => shouldShowRow(r.occ));\n while (headingHost.firstChild) headingHost.firstChild.remove();\n headingHost.append(\n makeSectionHeading(config.headingText + \" (\" + filtered.length + \")\", config.id)\n );\n if (filtered.length === 0) {\n while (rowsHost.firstChild) rowsHost.firstChild.remove();\n rowsHost.append(el(\"div\", { class: \"empty\", text: config.emptyMessage }));\n return;\n }\n renderFunctionRows(rowsHost, {\n occurrences: filtered.map(\n (r) => ({ ...r.occ, __metric: r.metric, ...rowExtras(r.occ, r.metric) })\n ),\n columns: config.columns,\n heading: config.headingText,\n viewId: config.id,\n skipHeading: true\n });\n }\n if (hasControls) {\n const controlsRow = el(\"div\", { class: \"code-paths-ranked-controls\" });\n if (filterByKP) buildKindPackage(controlsRow, catalog, renderRows);\n if (searchByName) buildSearchInput(controlsRow, renderRows);\n if (toggle) buildToggle(controlsRow, toggle, renderRows);\n container.append(controlsRow);\n }\n container.append(rowsHost);\n renderRows();\n },\n onActivate: searchByName ? () => {\n const input = document.querySelector(\"#code-paths-search-\" + config.id);\n if (input instanceof HTMLInputElement && typeof input.focus === \"function\") input.focus();\n } : void 0\n });\n }\n\n // src/client/view-distribution.ts\n var currentIndexes;\n function lineCount(occ) {\n return Math.max(0, (occ.endLine ?? occ.line ?? 0) - (occ.line ?? 0) + 1);\n }\n function distCallerCount(occ, indexes) {\n return (indexes.callers.get(occ.bodyHash) ?? []).length;\n }\n function distParamCount(occ) {\n return (occ.params ?? []).length;\n }\n function distTestOnly(occ, indexes) {\n if (occ.inTestFile) return false;\n const callers = indexes.callers.get(occ.bodyHash) ?? [];\n if (callers.length === 0) return false;\n return callers.every((h) => {\n const c = indexes.byBodyHash.get(h);\n return c?.inTestFile === true;\n });\n }\n defineRankedView({\n id: \"distribution\",\n label: \"Functions\",\n help: {\n title: \"Functions (distribution)\",\n sections: [\n {\n heading: \"What this is\",\n body: \"Every function in the catalog in one sortable table. Columns cover the metrics the former single-metric tabs ranked individually: body length (lines), inbound callers, parameter count (width), and whether the function is reachable only from tests.\"\n },\n {\n heading: \"Why you care\",\n body: \"Findings surface the rules that actually fired; this table is the raw distribution behind them. It is where you triage the sub-threshold tail \\u2014 a 140-line function under a 150-line gate, a 3-param function just under a width rule \\u2014 that a pass/fail findings list cannot show.\"\n },\n {\n heading: \"How to read it\",\n body: \"Sort by any column (click the header). Lines descending is the default. Callers = 0 plus no test reachability is an orphan; Test-only = yes means production code reached only from tests. Use the filter chips above the tab bar to scope by package, kind, or test-file membership.\"\n },\n {\n heading: \"What to do\",\n body: \"Click a row to open the Function Card and inspect callers/callees. Long bodies split along callee boundaries; wide signatures often want an options object; test-only functions usually belong in a __tests__/ helper.\"\n }\n ]\n },\n // Default ranking metric: body length (lines). Re-sortable to any column.\n // Records the active `indexes` for the Callers column + Test-only toggle (see\n // the `currentIndexes` note above) — metric runs for every row before render.\n metric: (occ, indexes) => {\n currentIndexes = indexes;\n return lineCount(occ);\n },\n columns: [\n { label: \"Function\", value: (o) => displayName(o.simpleName) },\n { label: \"Lines\", value: (o) => lineCount(o) },\n // The indexes closure is captured per-render via the renderer; callers need\n // it, so these columns read it from the module-level binding set on render.\n { label: \"Callers\", value: (o) => distCallerCount(o, currentIndexes) },\n { label: \"Params\", value: (o) => distParamCount(o) },\n // The former 'Test-only' column moved to a \"Test-only\" filter toggle (below).\n { label: \"Kind\", value: (o) => o.kind },\n { label: \"Package\", value: (o) => pkgOf(o) },\n { label: \"File\", value: (o) => o.filePath + \":\" + o.line }\n ],\n headingText: \"Functions\",\n emptyMessage: \"No functions match the active filters.\",\n // Absorbs the former standalone Search subtab: a name filter above the table,\n // re-filtering rows in place by function simple-name.\n searchByName: true,\n // Kind (single-select) + Package (single-select) dropdowns in the same\n // controls row, before the search box: Kind · Package · search.\n filterByKindPackage: true,\n // A \"Test-only\" checkbox after the search box — when checked, narrows the\n // table to production functions reached only from tests (distTestOnly).\n filterToggle: { label: \"Test-only\", predicate: (occ) => distTestOnly(occ, currentIndexes) }\n });\n\n // src/client/search.ts\n function fuzzyMatch(query, names) {\n const q = (query || \"\").trim();\n if (q.length === 0) return [];\n const qLower = q.toLowerCase();\n const out = [];\n for (const name of names) {\n const score = fuzzyScore(qLower, q, name);\n if (score < 0) continue;\n out.push({ name, score });\n }\n out.sort((a, b) => b.score - a.score);\n return out;\n }\n function matchBonus(name, q, i, qi, lastMatchIdx) {\n let bonus = 0;\n if (name[i] === q[qi]) bonus += 1;\n if (i === lastMatchIdx + 1) bonus += 2;\n if (i === 0 && qi === 0) bonus += 50;\n return bonus;\n }\n function fuzzyScore(qLower, q, name) {\n if (typeof name !== \"string\" || name.length === 0) return -1;\n const nameLower = name.toLowerCase();\n let qi = 0;\n let score = 0;\n let lastMatchIdx = -2;\n for (let i = 0; i < name.length && qi < qLower.length; i++) {\n if (nameLower[i] === qLower[qi]) {\n score += matchBonus(name, q, i, qi, lastMatchIdx);\n lastMatchIdx = i;\n qi++;\n }\n }\n if (qi < qLower.length) return -1;\n score -= name.length * 0.01;\n return score;\n }\n\n // src/client/view-graph-state.ts\n var GV_LAYOUTS = [\n { id: \"dagre\", label: \"Dagre (layered)\" },\n { id: \"cose\", label: \"Cose (force)\" },\n { id: \"breadthfirst\", label: \"Breadthfirst\" }\n ];\n var gvState = {\n level: \"package\",\n includeTests: false,\n selectedPackage: null,\n kinds: [],\n crossPackage: false,\n currentLayout: \"dagre\",\n cy: null,\n sccHighlight: false,\n escHandler: null\n };\n\n // src/client/view-graph-controls.ts\n var SELECT_CLASS = \"code-paths-graph-select\";\n function gvAddOptions(sel, pairs, current) {\n for (const [value, label] of pairs) {\n const opt = el(\"option\", { value, text: label });\n if (value === current) opt.selected = true;\n sel.append(opt);\n }\n }\n function gvMultiSelect(opts) {\n const wrap = el(\"div\", { class: \"code-paths-graph-ms\" });\n const selected = [...opts.selected];\n function triggerLabel() {\n if (selected.length === 0) return opts.allLabel;\n if (selected.length === 1) return selected[0];\n return selected.length + \" selected\";\n }\n const trigger = el(\"button\", {\n class: \"code-paths-graph-select code-paths-graph-ms-trigger\",\n \"data-control\": opts.id,\n text: triggerLabel() + \" \\u25BE\"\n });\n trigger.disabled = !!opts.disabled;\n const panel = el(\"div\", { class: \"code-paths-graph-ms-panel\" });\n panel.style.display = \"none\";\n let open = false;\n let docHandler = null;\n function close() {\n if (!open) return;\n open = false;\n panel.style.display = \"none\";\n if (docHandler) {\n document.removeEventListener(\"mousedown\", docHandler);\n docHandler = null;\n }\n opts.onClose([...selected]);\n }\n function openPanel() {\n if (open || opts.disabled) return;\n open = true;\n panel.style.display = \"block\";\n docHandler = (e) => {\n if (!wrap.contains(e.target)) close();\n };\n document.addEventListener(\"mousedown\", docHandler);\n }\n trigger.addEventListener(\"click\", () => {\n if (open) close();\n else openPanel();\n });\n for (const item of opts.items) {\n const row = el(\"label\", { class: \"code-paths-graph-ms-item\" });\n const cb = el(\"input\", { type: \"checkbox\" });\n cb.checked = selected.includes(item);\n cb.addEventListener(\"change\", () => {\n const ix = selected.indexOf(item);\n if (cb.checked && ix === -1) selected.push(item);\n else if (!cb.checked && ix !== -1) selected.splice(ix, 1);\n trigger.textContent = triggerLabel() + \" \\u25BE\";\n });\n row.append(cb);\n row.append(\" \" + item);\n panel.append(row);\n }\n wrap.append(trigger);\n wrap.append(panel);\n return wrap;\n }\n function gvRenderCyclesToggle(host, applySccHighlight) {\n const sccToggle = el(\"label\", { class: \"code-paths-graph-checkbox\" });\n const sccCb = el(\"input\", { type: \"checkbox\", \"data-scc-toggle\": \"1\" });\n sccCb.checked = gvState.sccHighlight;\n sccCb.addEventListener(\"change\", () => {\n gvState.sccHighlight = sccCb.checked;\n applySccHighlight();\n });\n sccToggle.append(sccCb);\n sccToggle.append(\" Highlight cycles\");\n host.append(sccToggle);\n }\n function gvRenderControls(host, catalog, indexes, handlers) {\n const { rerender, runLayout, applySccHighlight, renderSearchBox } = handlers;\n const fnLevel = gvState.level === \"function\";\n const grid = el(\"div\", { class: \"code-paths-graph-grid\" });\n function cell(labelText, control) {\n const c = el(\"div\", { class: \"code-paths-graph-cell\" });\n if (labelText)\n c.append(el(\"span\", { class: \"code-paths-graph-toolbar-label\", text: labelText }));\n if (control) c.append(control);\n grid.append(c);\n return c;\n }\n const layoutSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"layout\"\n });\n gvAddOptions(\n layoutSel,\n GV_LAYOUTS.map((l) => [l.id, l.label]),\n gvState.currentLayout\n );\n layoutSel.addEventListener(\"change\", (e) => {\n runLayout(e.target.value);\n });\n cell(\"Layout\", layoutSel);\n const scopeSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"scope\"\n });\n gvAddOptions(\n scopeSel,\n [\n [\"prod\", \"Production only\"],\n [\"tests\", \"Include tests\"]\n ],\n gvState.includeTests ? \"tests\" : \"prod\"\n );\n scopeSel.addEventListener(\"change\", (e) => {\n gvState.includeTests = e.target.value === \"tests\";\n rerender();\n });\n cell(\"Scope\", scopeSel);\n const searchCell = el(\"div\", { class: \"code-paths-graph-cell code-paths-graph-cell-search\" });\n renderSearchBox(searchCell);\n grid.append(searchCell);\n const cyclesCell = el(\"div\", { class: \"code-paths-graph-cell\" });\n gvRenderCyclesToggle(cyclesCell, applySccHighlight);\n grid.append(cyclesCell);\n const levelSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"level\"\n });\n gvAddOptions(\n levelSel,\n [\n [\"package\", \"Package\"],\n [\"function\", \"Function\"]\n ],\n gvState.level\n );\n levelSel.addEventListener(\"change\", (e) => {\n gvState.level = e.target.value;\n rerender();\n });\n cell(\"Level\", levelSel);\n const pkgs = packagesInCatalog(catalog);\n const pkgSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"package\"\n });\n pkgSel.append(el(\"option\", { value: \"\", text: pkgs.length > 0 ? \"\\u2014 select \\u2014\" : \"\\u2014 none \\u2014\" }));\n gvAddOptions(\n pkgSel,\n pkgs.map((p) => [p, p]),\n gvState.selectedPackage\n );\n pkgSel.disabled = !fnLevel;\n pkgSel.addEventListener(\"change\", (e) => {\n gvState.selectedPackage = e.target.value || null;\n rerender();\n });\n cell(\"Package\", pkgSel);\n cell(\n \"Kind\",\n gvMultiSelect({\n id: \"kind\",\n items: KIND_LIST,\n selected: gvState.kinds,\n allLabel: \"All kinds\",\n disabled: !fnLevel,\n onClose: (sel) => {\n gvState.kinds = sel;\n rerender();\n }\n })\n );\n const edgeSel = el(\"select\", {\n class: SELECT_CLASS,\n \"data-control\": \"granularity\"\n });\n gvAddOptions(\n edgeSel,\n [\n [\"intra\", \"Intra-package\"],\n [\"cross\", \"+ cross-package\"]\n ],\n gvState.crossPackage ? \"cross\" : \"intra\"\n );\n edgeSel.disabled = !fnLevel;\n edgeSel.addEventListener(\"change\", (e) => {\n gvState.crossPackage = e.target.value === \"cross\";\n rerender();\n });\n cell(\"Edges\", edgeSel);\n host.append(grid);\n }\n function gvBuildFunctionElements(indexes, pkg, includeTests, kinds, crossPackage) {\n const elements = [];\n if (!indexes?.occurrencesByHash || !indexes.callees) return elements;\n const kindSet = kinds && kinds.length > 0 ? kinds : null;\n function passes(occ) {\n if (!includeTests && occ.inTestFile) return false;\n if (kindSet && !kindSet.includes(occ.kind ?? \"\")) return false;\n return true;\n }\n const seeds = [];\n const seenSeed = {};\n indexes.occurrencesByHash.forEach((occs) => {\n for (const occ of occs) {\n if (pkgOf(occ) === pkg && passes(occ)) {\n if (!seenSeed[occ.bodyHash]) {\n seenSeed[occ.bodyHash] = true;\n seeds.push(occ);\n }\n break;\n }\n }\n });\n const nodeIds = {};\n const degree = {};\n function addNode(occ, external) {\n if (nodeIds[occ.bodyHash]) return;\n nodeIds[occ.bodyHash] = true;\n degree[occ.bodyHash] ??= 0;\n elements.push({\n group: \"nodes\",\n data: {\n id: occ.bodyHash,\n label: displayName(occ.simpleName),\n external: external ? 1 : 0,\n totalCoupling: 0\n }\n });\n }\n for (const seed of seeds) addNode(seed, false);\n const edgeSeen = {};\n seeds.forEach((seed, s2) => {\n const targets = indexes.callees.get(seed.bodyHash) ?? [];\n targets.forEach((target, t) => {\n const callee = resolveCalleeOcc(target, seed, indexes);\n if (!callee) return;\n const external = pkgOf(callee) !== pkg;\n if (external && !crossPackage) return;\n if (!external && !passes(callee)) return;\n addNode(callee, external);\n const ekey = seed.bodyHash + \"\\n\" + callee.bodyHash;\n if (edgeSeen[ekey]) return;\n edgeSeen[ekey] = true;\n elements.push({\n group: \"edges\",\n data: {\n id: \"fe\" + s2 + \"_\" + t,\n source: seed.bodyHash,\n target: callee.bodyHash,\n weight: 1,\n isCycleEdge: false\n }\n });\n degree[seed.bodyHash] = (degree[seed.bodyHash] || 0) + 1;\n degree[callee.bodyHash] = (degree[callee.bodyHash] || 0) + 1;\n });\n });\n for (const elem of elements) {\n if (elem.group === \"nodes\") elem.data.totalCoupling = degree[elem.data.id] || 0;\n }\n return elements;\n }\n\n // src/client/view-graph-elements.ts\n function gvSccColor(sccId) {\n if (!sccId) return null;\n let h = 0;\n for (let i = 0; i < sccId.length; i++) {\n h = (h * 31 + (sccId.codePointAt(i) ?? 0)) % 360;\n }\n return \"hsl(\" + h + \", 70%, 55%)\";\n }\n function gvBuildElements(vm) {\n const elements = [];\n for (const n of vm.nodes) {\n elements.push({\n group: \"nodes\",\n data: {\n // totalCoupling/sccId are non-negative counts / a stable id; `??` mirrors\n // the legacy `||` fallback for these (a 0 coupling stays 0).\n id: n.id,\n label: n.label,\n totalCoupling: n.totalCoupling ?? 0,\n sccId: n.sccId ?? null,\n sccColor: gvSccColor(n.sccId)\n }\n });\n }\n vm.edges.forEach((e, j) => {\n elements.push({\n group: \"edges\",\n data: {\n id: \"e\" + j,\n source: e.source,\n target: e.target,\n // An edge always carries weight ≥ 1; default to 1 when the blob omits it.\n weight: e.weight ?? 1,\n isCycleEdge: !!e.isCycleEdge\n }\n });\n });\n return elements;\n }\n\n // src/client/view-graph-help.ts\n var GRAPH_VIEW_HELP = {\n title: \"Visualization\",\n sections: [\n {\n heading: \"What this is\",\n body: \"A node-link visualization of the call graph, rendered with Cytoscape.js. At Package level each node is a package and each edge is the directed coupling from one package into another; node size reflects total coupling (calls in + calls out) and edge thickness reflects the number of call edges. At Function level it shows the functions of one selected package and the calls among them.\"\n },\n {\n heading: \"Levels\",\n body: 'Use the Level control to switch between Package (the whole-repo package rollup, the same data as the Coupling matrix) and Function (one package at a time). Function level enables the Package picker (which package to show) and the Kind multi-select, plus an Edges toggle: \"Intra-package\" shows only calls inside the package; \"+ cross-package\" also draws calls leaving the package to faded external nodes. The Scope control (production only vs include tests) applies at both levels.'\n },\n {\n heading: \"Why you care\",\n body: \"The table views project the graph into rankings and lists, and the Coupling matrix shows the same package data as a grid. This view shows that topology directly \\u2014 hub packages, tightly-coupled clusters, and circular package dependencies at package level; the internal call structure of a single package at function level.\"\n },\n {\n heading: \"How to read it\",\n body: \"Bigger nodes are more coupled; thicker edges carry more calls. Use the layout selector to switch between layered (dagre), force (cose), and hierarchical (breadthfirst). The matrix on the Coupling tab is the package-level data in tabular form.\"\n },\n {\n heading: \"What to do\",\n body: \"Pan and zoom to explore. Type in the search box to center and highlight a node by name; non-matches fade. Click a node to trace its direct callers (upstream) and callees (downstream).\"\n },\n {\n heading: \"Cross-package cycles\",\n body: 'Strongly-connected components are groups of packages that can all reach each other through call edges (found via Tarjan\\u2019s algorithm). Click \"Highlight cycles\" in the toolbar to emphasize cycle members and cycle edges while dimming the acyclic remainder. A cycle between packages is usually a layering smell. Break it by extracting the shared protocol into a third package both sides depend on, or by inverting one call into a callback/event.'\n }\n ]\n };\n\n // src/client/view-graph-stylesheet.ts\n function gvStylesheet() {\n return [\n {\n selector: \"node\",\n style: {\n \"background-color\": \"#c4956a\",\n \"border-color\": (ele) => ele.data(\"sccColor\") || \"#8a8a8a\",\n \"border-width\": (ele) => ele.data(\"sccId\") ? 3 : 1,\n shape: \"round-rectangle\",\n // Size by total coupling degree (fan-in + fan-out call count). The\n // log-ish clamp keeps a megabus package from dwarfing the canvas.\n width: (ele) => 22 + Math.min(56, Math.sqrt(ele.data(\"totalCoupling\") || 0) * 6),\n height: (ele) => 22 + Math.min(56, Math.sqrt(ele.data(\"totalCoupling\") || 0) * 6),\n label: (ele) => ele.data(\"label\") || \"\",\n \"font-size\": 9,\n color: \"#ddd\",\n \"text-valign\": \"bottom\",\n \"text-halign\": \"center\",\n \"text-margin-y\": 2,\n \"text-wrap\": \"none\"\n }\n },\n {\n selector: \"edge\",\n style: {\n // Thickness by call-count weight (clamped). A solid uniform style —\n // resolution/confidence encoding is function-level and not meaningful\n // once edges are aggregated to packages.\n width: (ele) => 1 + Math.min(7, Math.sqrt(ele.data(\"weight\") || 1) * 1.2),\n \"line-color\": \"#5a5a5a\",\n \"target-arrow-color\": \"#5a5a5a\",\n \"target-arrow-shape\": \"triangle\",\n \"arrow-scale\": 0.8,\n \"curve-style\": \"bezier\"\n }\n },\n {\n selector: \"edge[?isCycleEdge]\",\n style: { \"line-color\": \"#d46a6a\", \"target-arrow-color\": \"#d46a6a\" }\n },\n // Function-level \"+ cross-package\" mode only: a callee that lives OUTSIDE\n // the selected package is drawn as a faded ellipse so the boundary reads\n // at a glance. Package-level view-models never set 'external', so this\n // selector is inert there.\n {\n selector: \"node[?external]\",\n style: {\n \"background-color\": \"#3a3a3a\",\n \"border-color\": \"#666\",\n color: \"#9a9a9a\",\n shape: \"ellipse\",\n opacity: 0.55\n }\n },\n {\n selector: \"node.gv-search-hit\",\n style: {\n \"background-color\": \"#e0a96d\",\n \"border-color\": \"#fff\",\n \"border-width\": 3,\n opacity: 1\n }\n },\n {\n selector: \"node.gv-search-fade\",\n style: { opacity: 0.12 }\n },\n {\n selector: \"edge.gv-search-fade\",\n style: { opacity: 0.05 }\n },\n // Impact highlight (adapted to packages): clicking a package lights its\n // direct caller packages (upstream) and callee packages (downstream).\n // Accent palette mirrors the dashboard theme: --accent (selected),\n // --accent-fitness (downstream), --accent-sim (upstream). Hard-coded\n // because the Cytoscape canvas can't read CSS custom properties.\n {\n selector: \"node.gv-selected\",\n style: {\n \"background-color\": \"#e0a96d\",\n \"border-color\": \"#fff\",\n \"border-width\": 4,\n opacity: 1\n }\n },\n {\n selector: \"node.gv-upstream\",\n style: { \"background-color\": \"#6a9bd4\", opacity: 1 }\n },\n {\n selector: \"node.gv-downstream\",\n style: { \"background-color\": \"#7ec47e\", opacity: 1 }\n },\n {\n selector: \".gv-dimmed\",\n style: { opacity: 0.1 }\n },\n // Cross-package cycle highlight (folded-in \"Cycles / SCCs\" affordance).\n // Cycle members get a bright accent fill; cycle edges turn red and\n // thicken; the acyclic remainder fades so multi-package cycles stand out.\n {\n selector: \"node.gv-scc-member\",\n style: {\n \"background-color\": \"#d46a6a\",\n \"border-color\": \"#fff\",\n \"border-width\": 3,\n opacity: 1\n }\n },\n {\n selector: \"edge.gv-scc-edge\",\n style: {\n \"line-color\": \"#d46a6a\",\n \"target-arrow-color\": \"#d46a6a\",\n width: 3,\n opacity: 1\n }\n },\n {\n selector: \".gv-scc-dimmed\",\n style: { opacity: 0.08 }\n }\n ];\n }\n\n // src/client/view-graph.ts\n function gvRegisterGraphLayouts() {\n try {\n if (typeof cytoscape === \"function\" && typeof cytoscapeDagre !== \"undefined\" && !cytoscape.__gvDagreRegistered) {\n cytoscape.use(cytoscapeDagre);\n cytoscape.__gvDagreRegistered = true;\n }\n } catch {\n }\n }\n function gvLoadViewModel() {\n const blob = document.querySelector(\"#graph-view-model\");\n if (!blob?.textContent) return null;\n try {\n return JSON.parse(blob.textContent);\n } catch {\n return null;\n }\n }\n function gvPanelHidden(container) {\n return !!(container?.classList?.contains(\"code-paths-view\") && !container.classList.contains(\"active\"));\n }\n function gvLayoutOptions(layoutId) {\n if (layoutId === \"dagre\") {\n return { name: \"dagre\", rankDir: \"LR\", nodeSep: 24, rankSep: 64, fit: true, padding: 24 };\n }\n if (layoutId === \"breadthfirst\") {\n return { name: \"breadthfirst\", directed: true, spacingFactor: 1.2, fit: true, padding: 24 };\n }\n return { name: \"cose\", animate: false, fit: true, padding: 24, nodeRepulsion: 6e3 };\n }\n function gvRunLayout(layoutId) {\n if (!gvState.cy) return;\n gvState.currentLayout = layoutId;\n const layout = gvState.cy.layout(gvLayoutOptions(layoutId));\n layout.run();\n }\n function gvApplySccHighlight() {\n const cy = gvState.cy;\n if (!cy) return;\n cy.batch(() => {\n cy.elements().removeClass(\"gv-scc-member gv-scc-edge gv-scc-dimmed\");\n if (!gvState.sccHighlight) return;\n cy.nodes().forEach((n) => {\n if (n.data(\"sccId\")) n.addClass(\"gv-scc-member\");\n else n.addClass(\"gv-scc-dimmed\");\n });\n cy.edges().forEach((ed) => {\n if (ed.data(\"isCycleEdge\")) ed.addClass(\"gv-scc-edge\");\n else ed.addClass(\"gv-scc-dimmed\");\n });\n });\n }\n function gvClearImpact() {\n if (!gvState.cy) return;\n gvState.cy.elements().removeClass(\"gv-selected gv-upstream gv-downstream gv-dimmed\");\n }\n function gvApplyImpact(seedId) {\n const cy = gvState.cy;\n if (!cy) return;\n const seed = cy.getElementById(seedId);\n if (!seed || seed.length === 0) return;\n const upstream = {};\n const downstream = {};\n seed.incomers(\"node\").forEach((n) => {\n if (n.id() !== seedId) upstream[n.id()] = true;\n });\n seed.outgoers(\"node\").forEach((n) => {\n if (n.id() !== seedId) downstream[n.id()] = true;\n });\n cy.batch(() => {\n cy.elements().removeClass(\"gv-selected gv-upstream gv-downstream\");\n cy.elements().addClass(\"gv-dimmed\");\n cy.nodes().forEach((n) => {\n const id = n.id();\n if (id === seedId) {\n n.removeClass(\"gv-dimmed\").addClass(\"gv-selected\");\n } else if (upstream[id]) {\n n.removeClass(\"gv-dimmed\").addClass(\"gv-upstream\");\n } else if (downstream[id]) {\n n.removeClass(\"gv-dimmed\").addClass(\"gv-downstream\");\n }\n });\n cy.edges().forEach((ed) => {\n const s = ed.source().id();\n const t = ed.target().id();\n if (s === seedId || t === seedId) ed.removeClass(\"gv-dimmed\");\n });\n });\n }\n function gvRenderSearchBox(host) {\n const input = el(\"input\", {\n type: \"search\",\n class: \"search-input code-paths-graph-search\",\n id: \"code-paths-graph-search-input\",\n // Labels are package names at package level, function names at function\n // level; the search matches whatever the live node labels are.\n placeholder: gvState.level === \"function\" ? \"Find a function by name\\u2026\" : \"Find a package by name\\u2026\"\n });\n input.addEventListener(\"input\", (e) => {\n gvApplySearch(e.target.value ?? \"\");\n });\n host.append(input);\n }\n function gvApplySearch(query) {\n const cy = gvState.cy;\n if (!cy) return;\n const q = (query ?? \"\").trim();\n cy.nodes().removeClass(\"gv-search-hit gv-search-fade\");\n if (q.length === 0) return;\n const labels = cy.nodes().map((n) => n.data(\"label\") ?? \"\");\n const matches = fuzzyMatch(q, labels);\n const hitLabels = {};\n for (const m of matches) hitLabels[m.name] = true;\n let hitCollection = cy.collection();\n cy.nodes().forEach((n) => {\n if (hitLabels[n.data(\"label\")]) {\n n.addClass(\"gv-search-hit\");\n hitCollection = hitCollection.union(n);\n } else {\n n.addClass(\"gv-search-fade\");\n }\n });\n if (hitCollection.length > 0) {\n try {\n cy.center(hitCollection);\n cy.fit(hitCollection, 120);\n } catch {\n }\n }\n }\n function gvEmpty(container, text) {\n container.append(el(\"div\", { class: \"empty\", text }));\n return null;\n }\n function gvResolveElements(container, indexes) {\n if (gvState.level === \"function\") {\n if (typeof cytoscape !== \"function\") return gvEmpty(container, \"Graph renderer unavailable.\");\n if (!gvState.selectedPackage) {\n return gvEmpty(container, \"Select a package to view its functions.\");\n }\n const elements2 = gvBuildFunctionElements(\n indexes,\n gvState.selectedPackage,\n gvState.includeTests,\n gvState.kinds,\n gvState.crossPackage\n );\n if (!elements2 || elements2.length === 0) {\n return gvEmpty(container, \"No functions in this package.\");\n }\n return elements2;\n }\n const vm = gvLoadViewModel();\n if (!vm?.nodes || vm.nodes.length === 0) return gvEmpty(container, \"No graph to display.\");\n if (typeof cytoscape !== \"function\") return gvEmpty(container, \"Graph renderer unavailable.\");\n const elements = gvBuildElements(vm);\n if (elements.length === 0) return gvEmpty(container, \"No packages to display.\");\n return elements;\n }\n function gvMountCanvas(container, elements) {\n const canvas = el(\"div\", { class: \"code-paths-graph-canvas\", id: \"code-paths-graph-canvas\" });\n container.append(canvas);\n try {\n gvState.cy = cytoscape({\n container: canvas,\n elements,\n style: gvStylesheet(),\n layout: gvLayoutOptions(gvState.currentLayout),\n wheelSensitivity: 0.2,\n minZoom: 0.05,\n maxZoom: 4\n });\n } catch {\n gvState.cy = null;\n canvas.append(\n el(\"div\", {\n class: \"empty\",\n text: \"Graph renderer could not initialize in this environment.\"\n })\n );\n return false;\n }\n return true;\n }\n function gvWireInteractions() {\n const cy = gvState.cy;\n if (!cy) return;\n cy.on(\"tap\", \"node\", (evt) => {\n gvApplyImpact(evt.target.id());\n });\n cy.on(\"tap\", (evt) => {\n if (evt.target === cy) gvClearImpact();\n });\n if (gvState.escHandler) {\n try {\n document.removeEventListener(\"keydown\", gvState.escHandler);\n } catch {\n }\n }\n gvState.escHandler = (e) => {\n if (e.key === \"Escape\") gvClearImpact();\n };\n document.addEventListener(\"keydown\", gvState.escHandler);\n }\n function gvRenderGraph(container, catalog, indexes) {\n while (container.firstChild) container.firstChild.remove();\n if (gvPanelHidden(container)) return;\n gvRegisterGraphLayouts();\n container.append(makeSectionHeading(\"Visualization\", \"graph\"));\n gvRenderControls(container, catalog, indexes, {\n rerender: () => gvRenderGraph(container, catalog, indexes),\n runLayout: gvRunLayout,\n applySccHighlight: gvApplySccHighlight,\n renderSearchBox: gvRenderSearchBox\n });\n const elements = gvResolveElements(container, indexes);\n if (!elements) return;\n if (!gvMountCanvas(container, elements)) return;\n gvWireInteractions();\n gvApplySccHighlight();\n }\n views.push({\n id: \"graph\",\n label: \"Visualization\",\n help: GRAPH_VIEW_HELP,\n render(container, catalog, indexes) {\n gvRenderGraph(container, catalog, indexes);\n },\n onActivate() {\n if (!gvState.cy) return;\n setTimeout(() => {\n try {\n gvState.cy?.resize();\n gvState.cy?.fit(void 0, 24);\n } catch {\n }\n }, 0);\n }\n });\n\n // src/client/code-paths-panel.ts\n var cg = globalThis;\n cg.graphCatalog = null;\n cg.graphIndexes = {\n byBodyHash: /* @__PURE__ */ new Map(),\n occurrencesByHash: /* @__PURE__ */ new Map(),\n bySimpleName: /* @__PURE__ */ new Map(),\n callees: /* @__PURE__ */ new Map(),\n callers: /* @__PURE__ */ new Map()\n };\n function loadGraphCatalogFromBlob() {\n const blob = document.querySelector(\"#graph-catalog\");\n if (!blob?.textContent) return null;\n try {\n return JSON.parse(blob.textContent);\n } catch {\n return null;\n }\n }\n function renderCodePathsTab() {\n const panel = document.querySelector(\"#panel-code-paths\");\n if (!panel) return;\n while (panel.firstChild) panel.firstChild.remove();\n const graphSessions = sessions.filter((s) => s.tool === \"graph\");\n cg.graphCatalog = loadGraphCatalogFromBlob();\n renderSubtabBar(panel, [\n {\n id: \"sessions\",\n label: \"Sessions\",\n render(p) {\n if (graphSessions.length > 0) {\n renderSessionTable(p, graphSessions, \"var(--accent)\");\n } else {\n p.append(el(\"div\", { class: \"empty\", text: \"No sessions yet.\" }));\n }\n }\n },\n {\n id: \"catalog\",\n label: \"Catalog\",\n render(p) {\n renderGraphRuleCatalog(\n p,\n typeof graphRuleCatalog === \"undefined\" ? [] : graphRuleCatalog\n );\n }\n },\n {\n id: \"recipes\",\n label: \"Recipes\",\n render(p) {\n renderGraphRecipeCatalog(\n p,\n typeof graphRecipeCatalog === \"undefined\" ? [] : graphRecipeCatalog\n );\n }\n },\n {\n id: \"explore\",\n label: \"Explore\",\n render(p) {\n if (cg.graphCatalog) {\n renderCodePathsExplore(p);\n } else {\n p.append(el(\"div\", { class: \"empty\", text: \"No catalog yet.\" }));\n }\n }\n }\n ]);\n }\n function renderCodePathsExplore(host) {\n cg.graphIndexes = buildIndexes(cg.graphCatalog);\n renderCatalogProvenance(host, cg.graphCatalog);\n const tabBar = el(\"div\", { class: \"code-paths-tabs\", id: \"code-paths-tabs\" });\n for (const view of views) {\n const tab = el(\"div\", {\n class: \"code-paths-tab\",\n \"data-view\": view.id,\n text: view.label,\n onclick: () => activateView(view.id)\n });\n tabBar.append(tab);\n }\n host.append(tabBar);\n const stack = el(\"div\", { class: \"code-paths-view-container\", id: \"code-paths-view-container\" });\n for (const view of views) {\n const c = el(\"div\", { class: \"code-paths-view\", id: \"code-paths-view-\" + view.id });\n stack.append(c);\n }\n host.append(stack);\n stack.addEventListener(\"click\", (e) => {\n const target = e.target;\n const row = target?.closest?.(\"[data-body-hash]\");\n if (!row) return;\n openFunctionCard(row.dataset.bodyHash ?? \"\");\n });\n document.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") closeFunctionCard();\n });\n for (const view of views) {\n const container = document.querySelector(\"#code-paths-view-\" + view.id);\n if (container) view.render(container, cg.graphCatalog, cg.graphIndexes, filterState);\n }\n const hashId = readViewIdFromHash();\n const initialId = hashId ?? views[0]?.id;\n if (initialId) activateView(initialId, { updateHash: hashId !== null });\n }\n function readViewIdFromHash() {\n const m = /^#code-paths\\/([a-z]+)/.exec(globalThis.location.hash || \"\");\n return m ? m[1] : null;\n }\n function openCodePathsSession(sessionId) {\n const tab = document.querySelector('.tab[data-tab=\"code-paths\"]');\n const panel = document.querySelector(\"#panel-code-paths\");\n if (!tab || !panel) return;\n document.querySelectorAll(\".tab\").forEach((t) => t.classList.remove(\"active\"));\n document.querySelectorAll(\".tab-panel\").forEach((p) => p.classList.remove(\"active\"));\n tab.classList.add(\"active\");\n panel.classList.add(\"active\");\n const sessionsSub = panel.querySelector('.subtab[data-subtab=\"sessions\"]');\n const exploreSub = panel.querySelector('.subtab[data-subtab=\"explore\"]');\n const sessionsPanel = document.querySelector(\"#panel-code-paths-sessions\");\n const explorePanel = document.querySelector(\"#panel-code-paths-explore\");\n if (sessionsSub) sessionsSub.classList.add(\"active\");\n if (exploreSub) exploreSub.classList.remove(\"active\");\n if (sessionsPanel) sessionsPanel.classList.add(\"active\");\n if (explorePanel) explorePanel.classList.remove(\"active\");\n const row = sessionsPanel?.querySelector('tr[data-session-id=\"' + sessionId + '\"]');\n if (row) row.click();\n }\n registerTabActivator(\"graph\", openCodePathsSession);\n\n // src/client/overview.ts\n function renderOverview() {\n const panel = document.querySelector(\"#panel-overview\");\n if (!panel) return;\n if (sessions.length === 0) {\n panel.append(el(\"div\", { class: \"empty\", text: \"No sessions yet.\" }));\n return;\n }\n const sec = el(\"div\", { class: \"section\" }, [el(\"h3\", { text: \"Recent Activity\" })]);\n const table = el(\"table\", { class: \"data-table sortable\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"Timestamp\", \"Tool\", \"Recipe\", \"Pass Rate\", \"Status\", \"Checks\", \"Findings\", \"Duration\"].forEach(\n (h) => {\n headerRow.append(el(\"th\", { text: h }));\n }\n );\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n sessions.forEach((s) => {\n const sc2 = scoreColorStyle(s.score);\n const sm = s.payload?.summary ?? { total: 0, passed: 0, failed: 0, errors: 0, warnings: 0 };\n const row = el(\"tr\", {\n class: \"clickable\",\n onclick: () => {\n if (activateTabForSession(s)) return;\n const tabName = tabMap[s.tool] ?? s.tool;\n document.querySelectorAll(\".tab\").forEach((t) => t.classList.remove(\"active\"));\n document.querySelectorAll(\".tab-panel\").forEach((p) => p.classList.remove(\"active\"));\n const tab = document.querySelector('.tab[data-tab=\"' + tabName + '\"]');\n if (tab) tab.classList.add(\"active\");\n const activePanel = document.querySelector(\"#panel-\" + tabName);\n if (activePanel) activePanel.classList.add(\"active\");\n }\n });\n row.append(\n el(\"td\", {\n class: \"cell-nowrap\",\n text: new Date(s.startedAt).toLocaleString(),\n style: \"color:var(--text-dim)\"\n })\n );\n const toolCell = el(\"td\");\n toolCell.append(\n el(\"span\", {\n class: \"badge\",\n style: toolBadgeStyles[s.tool] ?? \"\",\n text: s.tool.toUpperCase()\n })\n );\n row.append(toolCell);\n row.append(el(\"td\", { text: s.recipe ?? \"default\", style: \"color:var(--text-muted)\" }));\n row.append(el(\"td\", { text: s.score + \"%\", style: \"font-weight:600;\" + sc2 }));\n const statusCell = el(\"td\");\n statusCell.append(statusBadge(sessionStatus(s)));\n row.append(statusCell);\n row.append(el(\"td\", { text: (sm.passed ?? 0) + \"/\" + (sm.total ?? 0) }));\n row.append(el(\"td\", { text: \"\" + ((sm.errors ?? 0) + (sm.warnings ?? 0)) }));\n row.append(\n el(\"td\", { text: (s.durationMs / 1e3).toFixed(1) + \"s\", style: \"color:var(--text-dim)\" })\n );\n tbody.append(row);\n });\n table.append(tbody);\n const pag = el(\"div\", { class: \"pagination\" });\n sec.append(el(\"div\", { class: \"card\" }, [table, pag]));\n panel.append(sec);\n paginateTable(tbody, pag, 10);\n }\n\n // src/client/recipes.ts\n function renderRecipesPanel(container, recipesData) {\n const recipes = recipesData;\n if (!recipes?.length) {\n container.append(el(\"div\", { class: \"empty\", text: \"No recipes available.\" }));\n return;\n }\n const table = el(\"table\", { class: \"data-table\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"Recipe\", \"Description\", \"Selector\", \"Mode\", \"Timeout\", \"Tags\"].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n recipes.forEach((recipe) => {\n const row = el(\"tr\");\n const nameCell = el(\"td\", { style: \"font-weight:500\" });\n nameCell.append(el(\"div\", { text: recipe.displayName }));\n nameCell.append(\n el(\"div\", {\n text: recipe.name,\n style: \"font-size:11px;color:var(--text-dim);font-weight:400\"\n })\n );\n row.append(nameCell);\n row.append(el(\"td\", { text: recipe.description, style: \"color:var(--text-muted)\" }));\n const selCell = el(\"td\");\n selCell.append(\n el(\"span\", {\n class: \"badge\",\n style: \"background:var(--bg-hover);color:var(--text-muted)\",\n text: recipe.selectorType\n })\n );\n row.append(selCell);\n const modeCell = el(\"td\");\n const modeColor = recipe.mode === \"parallel\" ? \"color:var(--success)\" : \"color:var(--warning)\";\n modeCell.append(el(\"span\", { text: recipe.mode, style: modeColor + \";font-size:12px\" }));\n row.append(modeCell);\n row.append(\n el(\"td\", {\n text: recipe.timeout / 1e3 + \"s\",\n style: \"color:var(--text-dim);font-size:12px\"\n })\n );\n const tagsCell = el(\"td\");\n (recipe.tags ?? []).forEach((t) => {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: t }));\n });\n row.append(tagsCell);\n tbody.append(row);\n });\n table.append(tbody);\n container.append(el(\"div\", { class: \"card\" }, [table]));\n }\n\n // src/client/tool-tabs.ts\n function renderToolTab(options) {\n const {\n panelId,\n toolSessions,\n accentColor,\n catalogLabel,\n catalogData,\n renderCatalogFn,\n recipesData\n } = options;\n const panel = document.querySelector(\"#\" + panelId);\n if (!panel) return;\n renderSubtabBar(panel, [\n {\n id: \"overview\",\n label: \"Sessions\",\n render: (p) => {\n renderSessionTable(p, toolSessions, accentColor);\n }\n },\n {\n id: \"catalog\",\n label: catalogLabel,\n render: (p) => {\n if (catalogData && catalogData.length > 0) {\n renderCatalogFn(p, catalogData);\n } else {\n p.append(\n el(\"div\", {\n class: \"empty\",\n text: \"No \" + catalogLabel.toLowerCase() + \" available yet.\"\n })\n );\n }\n }\n },\n {\n id: \"recipes\",\n label: \"Recipes\",\n render: (p) => {\n renderRecipesPanel(p, recipesData);\n }\n }\n ]);\n }\n function renderFitnessTab() {\n renderToolTab({\n panelId: \"panel-fitness\",\n toolSessions: fitSessions,\n accentColor: \"var(--accent-fitness)\",\n catalogLabel: \"Checks\",\n catalogData: checkCatalog,\n renderCatalogFn: (container, data) => renderChecksCatalog(container, data),\n recipesData: recipeCatalog\n });\n }\n function renderScenariosCatalog(container, catalogData) {\n const scenarios = catalogData;\n const table = el(\"table\", { class: \"session-table\" });\n const tbody = el(\"tbody\");\n [...scenarios].sort((a, b) => a.name.localeCompare(b.name)).forEach((s) => {\n const row = el(\"tr\");\n const nameCell = el(\"td\");\n nameCell.append(el(\"strong\", { text: s.name }));\n if (s.kind)\n nameCell.append(el(\"span\", { class: \"badge\", text: s.kind, style: \"margin-left:8px\" }));\n if (s.description)\n nameCell.append(\n el(\"div\", { class: \"muted\", style: \"font-size:12px\", text: s.description })\n );\n row.append(nameCell);\n const tagsCell = el(\"td\");\n (s.tags ?? []).slice(0, 4).forEach((t) => {\n tagsCell.append(el(\"span\", { class: \"tag-badge\", text: t }));\n });\n row.append(tagsCell);\n tbody.append(row);\n });\n table.append(tbody);\n container.append(table);\n }\n function renderSimulationTab() {\n renderToolTab({\n panelId: \"panel-simulation\",\n toolSessions: simSessions,\n accentColor: \"var(--accent-sim)\",\n catalogLabel: \"Scenarios\",\n catalogData: simScenarioCatalog,\n renderCatalogFn: (container, data) => renderScenariosCatalog(container, data),\n recipesData: simRecipeCatalog\n });\n }\n var NEUTRAL_BADGE = \"background:var(--bg-hover);color:var(--text-muted)\";\n function renderYagniDetectorsCatalog(container, catalogData) {\n if (yagniSummary && typeof yagniSummary.detectorCount === \"number\") {\n container.append(\n el(\"div\", {\n class: \"muted\",\n style: \"margin-bottom:12px\",\n text: yagniSummary.detectorCount + \" detectors\"\n })\n );\n }\n const detectors = catalogData;\n const table = el(\"table\", { class: \"data-table\" });\n const thead = el(\"thead\");\n const headerRow = el(\"tr\");\n [\"Detector\", \"Description\", \"Evidence\", \"Source\"].forEach((h) => {\n headerRow.append(el(\"th\", { text: h }));\n });\n thead.append(headerRow);\n table.append(thead);\n const tbody = el(\"tbody\");\n [...detectors].sort((a, b) => a.slug.localeCompare(b.slug)).forEach((d) => {\n const row = el(\"tr\");\n row.append(el(\"td\", { text: d.slug, style: \"font-weight:500\" }));\n row.append(el(\"td\", { text: d.description ?? \"\", style: \"color:var(--text-muted)\" }));\n const evidenceCell = el(\"td\");\n evidenceCell.append(\n el(\"span\", {\n class: \"badge\",\n style: NEUTRAL_BADGE,\n text: \"static\"\n })\n );\n row.append(evidenceCell);\n const sourceCell = el(\"td\");\n sourceCell.append(el(\"span\", { class: \"badge\", style: NEUTRAL_BADGE, text: \"built-in\" }));\n row.append(sourceCell);\n tbody.append(row);\n });\n table.append(tbody);\n container.append(el(\"div\", { class: \"card\" }, [table]));\n }\n function renderYagniTab() {\n const panel = document.querySelector(\"#panel-yagni\");\n if (!panel) return;\n renderSubtabBar(panel, [\n {\n id: \"overview\",\n label: \"Sessions\",\n render: (p) => {\n renderSessionTable(p, yagniSessions, \"var(--accent-yagni)\");\n }\n },\n {\n id: \"catalog\",\n label: \"Detectors\",\n render: (p) => {\n if (yagniCatalog && yagniCatalog.length > 0) {\n renderYagniDetectorsCatalog(p, yagniCatalog);\n } else {\n p.append(el(\"div\", { class: \"empty\", text: \"No detectors available yet.\" }));\n }\n }\n }\n ]);\n }\n\n // src/client/tab-bar.ts\n document.querySelector(\"#tab-bar\")?.addEventListener(\"click\", (e) => {\n const tab = e.target?.closest(\".tab\");\n if (!tab) return;\n document.querySelectorAll(\".tab\").forEach((t) => t.classList.remove(\"active\"));\n document.querySelectorAll(\".tab-panel\").forEach((p) => p.classList.remove(\"active\"));\n tab.classList.add(\"active\");\n document.querySelector(\"#panel-\" + tab.dataset.tab)?.classList.add(\"active\");\n });\n\n // src/client/index.ts\n var g = globalThis;\n g.el = el;\n g.paginateTable = paginateTable;\n g.makeSortable = makeSortable;\n g.registerTabActivator = registerTabActivator;\n g.activateTabForSession = activateTabForSession;\n g.renderSubtabBar = renderSubtabBar;\n g.renderSessionTable = renderSessionTable;\n g.renderChecksCatalog = renderChecksCatalog;\n g.renderRecipesPanel = renderRecipesPanel;\n g.renderOverview = renderOverview;\n g.renderFitnessTab = renderFitnessTab;\n g.renderSimulationTab = renderSimulationTab;\n g.renderYagniTab = renderYagniTab;\n g.renderCodePathsTab = renderCodePathsTab;\n g.openCodePathsSession = openCodePathsSession;\n g.packageOfPath = packageOfPath;\n g.shortPkg = shortPkg;\n g.pkgOf = pkgOf;\n g.displayName = displayName;\n g.buildIndexes = buildIndexes;\n g.resolveCalleeOcc = resolveCalleeOcc;\n g.filterState = filterState;\n g.KIND_LIST = KIND_LIST;\n g.packagesInCatalog = packagesInCatalog;\n g.passesFilter = passesFilter;\n g.fuzzyMatch = fuzzyMatch;\n g.makeSectionHeading = makeSectionHeading;\n g.renderFunctionRows = renderFunctionRows;\n g.openFunctionCard = openFunctionCard;\n g.closeFunctionCard = closeFunctionCard;\n g.views = views;\n g.activateView = activateView;\n g.defineRankedView = defineRankedView;\n g.openHelpDrawer = openHelpDrawer;\n g.renderCatalogProvenance = renderCatalogProvenance;\n g.renderGraphRuleCatalog = renderGraphRuleCatalog;\n g.renderGraphRecipeCatalog = renderGraphRecipeCatalog;\n})();\n";
3
3
  //# sourceMappingURL=client-bundle.generated.js.map