@mastra/playground-ui 67.0.0 → 68.0.0-alpha.0

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 (244) hide show
  1. package/dist/KnowledgePageGraph.css +11 -0
  2. package/dist/components/ai/tool-approval.cjs.js +1 -1
  3. package/dist/components/ai/tool-approval.es.js +1 -1
  4. package/dist/components/ai/tool-call.cjs.js +2 -2
  5. package/dist/components/ai/tool-call.es.js +2 -2
  6. package/dist/domains/chat/activity/model/activity-segments.cjs.js +1 -1
  7. package/dist/domains/chat/activity/model/activity-segments.es.js +1 -1
  8. package/dist/domains/chat/activity/model/activity-words.cjs.js +2 -2
  9. package/dist/domains/chat/activity/model/activity-words.es.js +2 -2
  10. package/dist/domains/chat/knowledge/knowledge-links.cjs.js +40 -0
  11. package/dist/domains/chat/knowledge/knowledge-links.cjs.js.map +1 -0
  12. package/dist/domains/chat/knowledge/knowledge-links.d.ts +1 -0
  13. package/dist/domains/chat/knowledge/knowledge-links.es.js +38 -0
  14. package/dist/domains/chat/knowledge/knowledge-links.es.js.map +1 -0
  15. package/dist/domains/chat/knowledge/pinned-knowledge-data.cjs.js +79 -0
  16. package/dist/domains/chat/knowledge/pinned-knowledge-data.cjs.js.map +1 -0
  17. package/dist/domains/chat/knowledge/pinned-knowledge-data.d.ts +1 -0
  18. package/dist/domains/chat/knowledge/pinned-knowledge-data.es.js +77 -0
  19. package/dist/domains/chat/knowledge/pinned-knowledge-data.es.js.map +1 -0
  20. package/dist/domains/chat/knowledge/pinned-knowledge.cjs.js +124 -0
  21. package/dist/domains/chat/knowledge/pinned-knowledge.cjs.js.map +1 -0
  22. package/dist/domains/chat/knowledge/pinned-knowledge.d.ts +1 -0
  23. package/dist/domains/chat/knowledge/pinned-knowledge.es.js +123 -0
  24. package/dist/domains/chat/knowledge/pinned-knowledge.es.js.map +1 -0
  25. package/dist/domains/chat/knowledge.cjs.js +12 -0
  26. package/dist/domains/chat/knowledge.d.ts +1 -0
  27. package/dist/domains/chat/knowledge.es.js +5 -0
  28. package/dist/domains/chat/messages/message-row.cjs.js +2 -2
  29. package/dist/domains/chat/messages/message-row.es.js +2 -2
  30. package/dist/domains/chat/messages/signal-badge.cjs.js +7 -0
  31. package/dist/domains/chat/messages/signal-badge.cjs.js.map +1 -1
  32. package/dist/domains/chat/messages/signal-badge.es.js +7 -0
  33. package/dist/domains/chat/messages/signal-badge.es.js.map +1 -1
  34. package/dist/domains/chat/tools/badges/agent-badge-wrapper.cjs.js +1 -1
  35. package/dist/domains/chat/tools/badges/agent-badge-wrapper.es.js +1 -1
  36. package/dist/domains/chat/tools/badges/agent-badge.cjs.js +1 -1
  37. package/dist/domains/chat/tools/badges/agent-badge.es.js +1 -1
  38. package/dist/domains/chat/tools/badges/file-tree-badge.cjs.js +2 -2
  39. package/dist/domains/chat/tools/badges/file-tree-badge.es.js +2 -2
  40. package/dist/domains/chat/tools/badges/knowledge-tool-badge.cjs.js +273 -0
  41. package/dist/domains/chat/tools/badges/knowledge-tool-badge.cjs.js.map +1 -0
  42. package/dist/domains/chat/tools/badges/knowledge-tool-badge.d.ts +1 -0
  43. package/dist/domains/chat/tools/badges/knowledge-tool-badge.es.js +270 -0
  44. package/dist/domains/chat/tools/badges/knowledge-tool-badge.es.js.map +1 -0
  45. package/dist/domains/chat/tools/badges/observation-marker-badge.cjs.js +1 -1
  46. package/dist/domains/chat/tools/badges/observation-marker-badge.es.js +1 -1
  47. package/dist/domains/chat/tools/badges/sandbox-execution-badge.cjs.js +2 -2
  48. package/dist/domains/chat/tools/badges/sandbox-execution-badge.es.js +2 -2
  49. package/dist/domains/chat/tools/badges/tool-approval-badge.cjs.js +1 -1
  50. package/dist/domains/chat/tools/badges/tool-approval-badge.es.js +1 -1
  51. package/dist/domains/chat/tools/badges/tool-badge.cjs.js +3 -3
  52. package/dist/domains/chat/tools/badges/tool-badge.es.js +3 -3
  53. package/dist/domains/chat/tools/badges/workflow-badge.cjs.js +2 -2
  54. package/dist/domains/chat/tools/badges/workflow-badge.es.js +2 -2
  55. package/dist/domains/chat/tools/tool-card-kind.cjs.js +4 -2
  56. package/dist/domains/chat/tools/tool-card-kind.cjs.js.map +1 -1
  57. package/dist/domains/chat/tools/tool-card-kind.es.js +4 -2
  58. package/dist/domains/chat/tools/tool-card-kind.es.js.map +1 -1
  59. package/dist/domains/chat/tools/tool-card.cjs.js +1 -1
  60. package/dist/domains/chat/tools/tool-card.es.js +1 -1
  61. package/dist/domains/chat/tools/tool-groups.cjs.js +3 -2
  62. package/dist/domains/chat/tools/tool-groups.cjs.js.map +1 -1
  63. package/dist/domains/chat/tools/tool-groups.es.js +3 -2
  64. package/dist/domains/chat/tools/tool-groups.es.js.map +1 -1
  65. package/dist/domains/chat.cjs.js +11 -0
  66. package/dist/domains/chat.es.js +5 -1
  67. package/dist/domains/knowledge/components/KnowledgePageDetail.cjs.js +438 -0
  68. package/dist/domains/knowledge/components/KnowledgePageDetail.cjs.js.map +1 -0
  69. package/dist/domains/knowledge/components/KnowledgePageDetail.d.ts +1 -0
  70. package/dist/domains/knowledge/components/KnowledgePageDetail.es.js +436 -0
  71. package/dist/domains/knowledge/components/KnowledgePageDetail.es.js.map +1 -0
  72. package/dist/domains/knowledge/components/KnowledgePageExplorer.cjs.js +521 -0
  73. package/dist/domains/knowledge/components/KnowledgePageExplorer.cjs.js.map +1 -0
  74. package/dist/domains/knowledge/components/KnowledgePageExplorer.d.ts +1 -0
  75. package/dist/domains/knowledge/components/KnowledgePageExplorer.es.js +520 -0
  76. package/dist/domains/knowledge/components/KnowledgePageExplorer.es.js.map +1 -0
  77. package/dist/domains/knowledge/components/KnowledgePageGraph.cjs.js +626 -0
  78. package/dist/domains/knowledge/components/KnowledgePageGraph.cjs.js.map +1 -0
  79. package/dist/domains/knowledge/components/KnowledgePageGraph.d.ts +1 -0
  80. package/dist/domains/knowledge/components/KnowledgePageGraph.es.js +625 -0
  81. package/dist/domains/knowledge/components/KnowledgePageGraph.es.js.map +1 -0
  82. package/dist/domains/knowledge/components/KnowledgePageTree.cjs.js +170 -0
  83. package/dist/domains/knowledge/components/KnowledgePageTree.cjs.js.map +1 -0
  84. package/dist/domains/knowledge/components/KnowledgePageTree.d.ts +1 -0
  85. package/dist/domains/knowledge/components/KnowledgePageTree.es.js +168 -0
  86. package/dist/domains/knowledge/components/KnowledgePageTree.es.js.map +1 -0
  87. package/dist/domains/knowledge/hooks/useInteractionIdle.cjs.js +37 -0
  88. package/dist/domains/knowledge/hooks/useInteractionIdle.cjs.js.map +1 -0
  89. package/dist/domains/knowledge/hooks/useInteractionIdle.d.ts +1 -0
  90. package/dist/domains/knowledge/hooks/useInteractionIdle.es.js +35 -0
  91. package/dist/domains/knowledge/hooks/useInteractionIdle.es.js.map +1 -0
  92. package/dist/domains/knowledge/lib/layout.cjs.js +93 -0
  93. package/dist/domains/knowledge/lib/layout.cjs.js.map +1 -0
  94. package/dist/domains/knowledge/lib/layout.d.ts +1 -0
  95. package/dist/domains/knowledge/lib/layout.es.js +92 -0
  96. package/dist/domains/knowledge/lib/layout.es.js.map +1 -0
  97. package/dist/domains/knowledge/lib/pageGraph.cjs.js +135 -0
  98. package/dist/domains/knowledge/lib/pageGraph.cjs.js.map +1 -0
  99. package/dist/domains/knowledge/lib/pageGraph.d.ts +1 -0
  100. package/dist/domains/knowledge/lib/pageGraph.es.js +128 -0
  101. package/dist/domains/knowledge/lib/pageGraph.es.js.map +1 -0
  102. package/dist/domains/knowledge/lib/pageLayout.cjs.js +49 -0
  103. package/dist/domains/knowledge/lib/pageLayout.cjs.js.map +1 -0
  104. package/dist/domains/knowledge/lib/pageLayout.d.ts +1 -0
  105. package/dist/domains/knowledge/lib/pageLayout.es.js +48 -0
  106. package/dist/domains/knowledge/lib/pageLayout.es.js.map +1 -0
  107. package/dist/domains/knowledge/lib/pageTree.cjs.js +98 -0
  108. package/dist/domains/knowledge/lib/pageTree.cjs.js.map +1 -0
  109. package/dist/domains/knowledge/lib/pageTree.d.ts +1 -0
  110. package/dist/domains/knowledge/lib/pageTree.es.js +94 -0
  111. package/dist/domains/knowledge/lib/pageTree.es.js.map +1 -0
  112. package/dist/domains/knowledge/lib/popupPlacement.cjs.js +22 -0
  113. package/dist/domains/knowledge/lib/popupPlacement.cjs.js.map +1 -0
  114. package/dist/domains/knowledge/lib/popupPlacement.d.ts +1 -0
  115. package/dist/domains/knowledge/lib/popupPlacement.es.js +21 -0
  116. package/dist/domains/knowledge/lib/popupPlacement.es.js.map +1 -0
  117. package/dist/domains/knowledge/lib/recordText.cjs.js +26 -0
  118. package/dist/domains/knowledge/lib/recordText.cjs.js.map +1 -0
  119. package/dist/domains/knowledge/lib/recordText.d.ts +1 -0
  120. package/dist/domains/knowledge/lib/recordText.es.js +25 -0
  121. package/dist/domains/knowledge/lib/recordText.es.js.map +1 -0
  122. package/dist/domains/knowledge/pageTypes.cjs.js +0 -0
  123. package/dist/domains/knowledge/pageTypes.d.ts +1 -0
  124. package/dist/domains/knowledge/pageTypes.es.js +0 -0
  125. package/dist/domains/logs/components/logs-saved-views.cjs.js +1 -1
  126. package/dist/domains/logs/components/logs-saved-views.es.js +1 -1
  127. package/dist/domains/observability/saved-views/observability-saved-views.cjs.js +1 -1
  128. package/dist/domains/observability/saved-views/observability-saved-views.es.js +1 -1
  129. package/dist/domains/observability/saved-views/saved-views-provider.cjs.js +3 -2
  130. package/dist/domains/observability/saved-views/saved-views-provider.cjs.js.map +1 -1
  131. package/dist/domains/observability/saved-views/saved-views-provider.es.js +3 -2
  132. package/dist/domains/observability/saved-views/saved-views-provider.es.js.map +1 -1
  133. package/dist/domains/observability/saved-views/saved-views-source.cjs.js.map +1 -1
  134. package/dist/domains/observability/saved-views/saved-views-source.es.js.map +1 -1
  135. package/dist/domains/observability/saved-views/use-hosted-saved-views.cjs.js +3 -2
  136. package/dist/domains/observability/saved-views/use-hosted-saved-views.cjs.js.map +1 -1
  137. package/dist/domains/observability/saved-views/use-hosted-saved-views.es.js +3 -2
  138. package/dist/domains/observability/saved-views/use-hosted-saved-views.es.js.map +1 -1
  139. package/dist/domains/observability/saved-views.cjs.js +1 -1
  140. package/dist/domains/observability/saved-views.es.js +1 -1
  141. package/dist/domains/observability.cjs.js +1 -1
  142. package/dist/domains/observability.es.js +1 -1
  143. package/dist/domains/skills/skill-simple-form.cjs.js +2 -2
  144. package/dist/domains/skills/skill-simple-form.cjs.js.map +1 -1
  145. package/dist/domains/skills/skill-simple-form.es.js +2 -2
  146. package/dist/domains/skills/skill-simple-form.es.js.map +1 -1
  147. package/dist/domains/spans/components/spans-explorer-body.cjs.js +1 -1
  148. package/dist/domains/spans/components/spans-explorer-body.es.js +1 -1
  149. package/dist/domains/threads/components/threads-explorer-body.cjs.js +1 -1
  150. package/dist/domains/threads/components/threads-explorer-body.es.js +1 -1
  151. package/dist/domains/traces/components/span-payload/span-error-renderer.cjs.js +1 -1
  152. package/dist/domains/traces/components/span-payload/span-error-renderer.es.js +1 -1
  153. package/dist/domains/traces/components/span-payload/span-input-renderers.cjs.js +1 -1
  154. package/dist/domains/traces/components/span-payload/span-input-renderers.es.js +1 -1
  155. package/dist/domains/traces/components/span-payload/span-output-renderers.cjs.js +1 -1
  156. package/dist/domains/traces/components/span-payload/span-output-renderers.es.js +1 -1
  157. package/dist/domains/traces/components/span-payload/span-payload-messages.cjs.js +1 -1
  158. package/dist/domains/traces/components/span-payload/span-payload-messages.es.js +1 -1
  159. package/dist/domains/traces/components/span-payload/span-payload-primitives.cjs.js +1 -1
  160. package/dist/domains/traces/components/span-payload/span-payload-primitives.es.js +1 -1
  161. package/dist/domains/traces/components/span-payload/span-payload-processor.cjs.js +1 -1
  162. package/dist/domains/traces/components/span-payload/span-payload-processor.es.js +1 -1
  163. package/dist/domains/traces/components/span-payload/span-payload-tool.cjs.js +1 -1
  164. package/dist/domains/traces/components/span-payload/span-payload-tool.es.js +1 -1
  165. package/dist/domains/traces/components/span-payload/span-processor-mutations.cjs.js +1 -1
  166. package/dist/domains/traces/components/span-payload/span-processor-mutations.es.js +1 -1
  167. package/dist/domains/traces/components/span-payload/span-tools-section.cjs.js +1 -1
  168. package/dist/domains/traces/components/span-payload/span-tools-section.es.js +1 -1
  169. package/dist/domains/traces/components/traces-explorer/traces-explorer-saved-views.cjs.js +1 -1
  170. package/dist/domains/traces/components/traces-explorer/traces-explorer-saved-views.es.js +1 -1
  171. package/dist/domains/workflows/workflow/use-workflow-trigger.cjs.js +2 -1
  172. package/dist/domains/workflows/workflow/use-workflow-trigger.cjs.js.map +1 -1
  173. package/dist/domains/workflows/workflow/use-workflow-trigger.es.js +2 -1
  174. package/dist/domains/workflows/workflow/use-workflow-trigger.es.js.map +1 -1
  175. package/dist/lib/form/dynamic-form.cjs.js +2 -2
  176. package/dist/lib/form/dynamic-form.cjs.js.map +1 -1
  177. package/dist/lib/form/dynamic-form.es.js +2 -2
  178. package/dist/lib/form/dynamic-form.es.js.map +1 -1
  179. package/dist/lib/form/json-draft.cjs.js +1 -1
  180. package/dist/lib/form/json-draft.es.js +1 -1
  181. package/dist/lib/form/zod-provider/compat.cjs.js +8 -0
  182. package/dist/lib/form/zod-provider/compat.cjs.js.map +1 -1
  183. package/dist/lib/form/zod-provider/compat.es.js +8 -1
  184. package/dist/lib/form/zod-provider/compat.es.js.map +1 -1
  185. package/dist/lib/form/zod-provider.cjs.js +1 -1
  186. package/dist/lib/form/zod-provider.es.js +1 -1
  187. package/dist/{observability-saved-views-CwCii7hh.cjs → observability-saved-views-BNU3GkK6.cjs} +8 -6
  188. package/dist/observability-saved-views-BNU3GkK6.cjs.map +1 -0
  189. package/dist/{observability-saved-views-Bo5kLN20.js → observability-saved-views-Zlz74syw.js} +8 -6
  190. package/dist/observability-saved-views-Zlz74syw.js.map +1 -0
  191. package/dist/{span-payload-primitives-Dyhbqaa7.js → span-payload-primitives-ChvD2RrN.js} +2 -2
  192. package/dist/{span-payload-primitives-Dyhbqaa7.js.map → span-payload-primitives-ChvD2RrN.js.map} +1 -1
  193. package/dist/{span-payload-primitives-B10Xxmx7.cjs → span-payload-primitives-w1vtAXAw.cjs} +2 -2
  194. package/dist/{span-payload-primitives-B10Xxmx7.cjs.map → span-payload-primitives-w1vtAXAw.cjs.map} +1 -1
  195. package/dist/src/domains/chat/index.d.ts +1 -0
  196. package/dist/src/domains/chat/knowledge/index.d.ts +5 -0
  197. package/dist/src/domains/chat/knowledge/knowledge-links.d.ts +21 -0
  198. package/dist/src/domains/chat/knowledge/pinned-knowledge-data.d.ts +22 -0
  199. package/dist/src/domains/chat/knowledge/pinned-knowledge.d.ts +9 -0
  200. package/dist/src/domains/chat/tools/badges/knowledge-tool-badge.d.ts +19 -0
  201. package/dist/src/domains/chat/tools/tool-card-kind.d.ts +2 -2
  202. package/dist/src/domains/knowledge/components/KnowledgePageDetail.d.ts +42 -0
  203. package/dist/src/domains/knowledge/components/KnowledgePageExplorer.d.ts +80 -0
  204. package/dist/src/domains/knowledge/components/KnowledgePageGraph.d.ts +35 -0
  205. package/dist/src/domains/knowledge/components/KnowledgePageTree.d.ts +27 -0
  206. package/dist/src/domains/knowledge/hooks/useInteractionIdle.d.ts +12 -0
  207. package/dist/src/domains/knowledge/lib/layout.d.ts +35 -0
  208. package/dist/src/domains/knowledge/lib/pageGraph.d.ts +45 -0
  209. package/dist/src/domains/knowledge/lib/pageLayout.d.ts +24 -0
  210. package/dist/src/domains/knowledge/lib/pageTree.d.ts +28 -0
  211. package/dist/src/domains/knowledge/lib/popupPlacement.d.ts +12 -0
  212. package/dist/src/domains/knowledge/lib/recordText.d.ts +9 -0
  213. package/dist/src/domains/knowledge/pageTypes.d.ts +96 -0
  214. package/dist/src/domains/observability/saved-views/saved-views-provider.d.ts +6 -1
  215. package/dist/src/domains/observability/saved-views/saved-views-source.d.ts +1 -0
  216. package/dist/src/domains/observability/saved-views/use-hosted-saved-views.d.ts +1 -1
  217. package/dist/src/domains/skills/skill-simple-form.d.ts +3 -1
  218. package/dist/src/ds/components/SavedViews/saved-view-picker.d.ts +3 -1
  219. package/dist/src/ds/components/ai/tool-call/tool-phrases.d.ts +30 -0
  220. package/dist/src/lib/form/zod-provider/compat.d.ts +5 -0
  221. package/dist/style.css +63 -1
  222. package/dist/{tool-approval-FeuyUUcL.js → tool-approval-DHeCkWHT.js} +3 -3
  223. package/dist/{tool-approval-FeuyUUcL.js.map → tool-approval-DHeCkWHT.js.map} +1 -1
  224. package/dist/{tool-approval-BfjoSNOC.cjs → tool-approval-Dfi0uAKU.cjs} +3 -3
  225. package/dist/{tool-approval-BfjoSNOC.cjs.map → tool-approval-Dfi0uAKU.cjs.map} +1 -1
  226. package/dist/{tool-call-CvilN9ti.cjs → tool-call-BCsDhIhs.cjs} +32 -2
  227. package/dist/{tool-call-CvilN9ti.cjs.map → tool-call-BCsDhIhs.cjs.map} +1 -1
  228. package/dist/{tool-call-BxYXs9w0.js → tool-call-CILZa3tn.js} +32 -2
  229. package/dist/{tool-call-BxYXs9w0.js.map → tool-call-CILZa3tn.js.map} +1 -1
  230. package/dist/{tool-card-ELW898uw.js → tool-card-C3sDFUWc.js} +18 -4
  231. package/dist/tool-card-C3sDFUWc.js.map +1 -0
  232. package/dist/{tool-card-B7O8AsRl.cjs → tool-card-CJ7Kxi-U.cjs} +18 -4
  233. package/dist/tool-card-CJ7Kxi-U.cjs.map +1 -0
  234. package/dist/{tool-presentation-BLErsb7c.cjs → tool-presentation-CQlDEwgS.cjs} +26 -1
  235. package/dist/tool-presentation-CQlDEwgS.cjs.map +1 -0
  236. package/dist/{tool-presentation-BSZYIWx2.js → tool-presentation-DvCsb_kz.js} +26 -1
  237. package/dist/tool-presentation-DvCsb_kz.js.map +1 -0
  238. package/package.json +11 -9
  239. package/dist/observability-saved-views-Bo5kLN20.js.map +0 -1
  240. package/dist/observability-saved-views-CwCii7hh.cjs.map +0 -1
  241. package/dist/tool-card-B7O8AsRl.cjs.map +0 -1
  242. package/dist/tool-card-ELW898uw.js.map +0 -1
  243. package/dist/tool-presentation-BLErsb7c.cjs.map +0 -1
  244. package/dist/tool-presentation-BSZYIWx2.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pageLayout.es.js","names":[],"sources":["../../../../src/domains/knowledge/lib/pageLayout.ts"],"sourcesContent":["import { runLayout } from './layout';\nimport type { LayoutEdgeInput } from './layout';\n\ntype Point = { x: number; y: number };\n\nexport interface PageLayout {\n /** Node ids, sizes and edges the positions were solved for. */\n key: string;\n positions: Map<string, Point>;\n}\n\n/** Below this share of already-placed nodes the graph is laid out from scratch. */\nconst WARM_SHARE = 0.5;\n\nfunction layoutKey(nodes: { id: string; size: number }[], edges: LayoutEdgeInput[]): string {\n const nodePart = nodes.map(node => `${node.id}:${node.size}`).sort();\n const edgePart = edges.map(edge => `${edge.source}>${edge.target}`).sort();\n return `${nodePart.join(',')}|${edgePart.join(',')}`;\n}\n\n/**\n * Lays out the page graph, reusing the previous layout. The same nodes and\n * edges return the previous positions as they are, so a refetch or live update\n * that changes nothing in the graph never moves a node or re-runs the solve.\n * When most nodes were already placed, they start where they sat, new pages\n * start beside a placed neighbour, and a short run only pushes apart pages that\n * overlap, so the rest stay put.\n * Otherwise the layout is solved from the deterministic cold start.\n */\nexport function layoutPageGraph(\n previous: PageLayout | undefined,\n nodes: { id: string; size: number }[],\n edges: LayoutEdgeInput[],\n): PageLayout {\n const key = layoutKey(nodes, edges);\n if (previous?.key === key) return previous;\n\n const last = previous?.positions;\n const known = last ? nodes.filter(node => last.has(node.id)).length : 0;\n if (!last || nodes.length === 0 || known / nodes.length < WARM_SHARE) {\n return { key, positions: runLayout(nodes, edges) };\n }\n\n const besideNeighbour = (id: string): Point | undefined => {\n for (const edge of edges) {\n const other = edge.source === id ? edge.target : edge.target === id ? edge.source : undefined;\n const at = other ? last.get(other) : undefined;\n if (at) return { x: at.x + 40, y: at.y + 40 };\n }\n return undefined;\n };\n // No force ticks: any force run drifts a settled layout. Only the overlap\n // pass runs, pushing apart the pages a new page landed on.\n const positions = runLayout(\n nodes.map(node => ({ ...node, initial: last.get(node.id) ?? besideNeighbour(node.id) })),\n edges,\n { ticks: 0 },\n );\n return { key, positions };\n}\n"],"mappings":";;;AAYA,IAAM,aAAa;AAEnB,SAAS,UAAU,OAAuC,OAAkC;CAC1F,MAAM,WAAW,MAAM,KAAI,SAAQ,GAAG,KAAK,GAAG,GAAG,KAAK,MAAM,CAAC,CAAC,KAAK;CACnE,MAAM,WAAW,MAAM,KAAI,SAAQ,GAAG,KAAK,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,KAAK;CACzE,OAAO,GAAG,SAAS,KAAK,GAAG,EAAE,GAAG,SAAS,KAAK,GAAG;AACnD;;;;;;;;;;AAWA,SAAgB,gBACd,UACA,OACA,OACY;CACZ,MAAM,MAAM,UAAU,OAAO,KAAK;CAClC,IAAI,UAAU,QAAQ,KAAK,OAAO;CAElC,MAAM,OAAO,UAAU;CACvB,MAAM,QAAQ,OAAO,MAAM,QAAO,SAAQ,KAAK,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS;CACtE,IAAI,CAAC,QAAQ,MAAM,WAAW,KAAK,QAAQ,MAAM,SAAS,YACxD,OAAO;EAAE;EAAK,WAAW,UAAU,OAAO,KAAK;CAAE;CAGnD,MAAM,mBAAmB,OAAkC;EACzD,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,QAAQ,KAAK,WAAW,KAAK,KAAK,SAAS,KAAK,WAAW,KAAK,KAAK,SAAS,KAAA;GACpF,MAAM,KAAK,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAA;GACrC,IAAI,IAAI,OAAO;IAAE,GAAG,GAAG,IAAI;IAAI,GAAG,GAAG,IAAI;GAAG;EAC9C;CAEF;CAQA,OAAO;EAAE;EAAK,WALI,UAChB,MAAM,KAAI,UAAS;GAAE,GAAG;GAAM,SAAS,KAAK,IAAI,KAAK,EAAE,KAAK,gBAAgB,KAAK,EAAE;EAAE,EAAE,GACvF,OACA,EAAE,OAAO,EAAE,CAEC;CAAU;AAC1B"}
@@ -0,0 +1,98 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ // src/domains/knowledge/lib/pageTree.ts
3
+ /** Sibling order: position by UTF-16 code unit (as the store defines it), then id. */
4
+ function comparePagePosition(a, b) {
5
+ if (a.position !== b.position) return a.position < b.position ? -1 : 1;
6
+ if (a.id === b.id) return 0;
7
+ return a.id < b.id ? -1 : 1;
8
+ }
9
+ function isLive(page) {
10
+ return !page.deletedAt && !page.mergedInto;
11
+ }
12
+ /**
13
+ * Builds the tree from whatever pages are loaded. A page whose parent isn't
14
+ * loaded (or is deleted/merged) becomes a root, and so does the first page (in
15
+ * sibling order) of any parent cycle, so no loaded live page is ever lost.
16
+ */
17
+ function buildPageTree(pages) {
18
+ const nodes = /* @__PURE__ */ new Map();
19
+ for (const page of pages) if (isLive(page) && !nodes.has(page.id)) nodes.set(page.id, {
20
+ page,
21
+ children: []
22
+ });
23
+ const roots = [];
24
+ const parentOf = /* @__PURE__ */ new Map();
25
+ for (const node of nodes.values()) {
26
+ const parent = node.page.parentId ? nodes.get(node.page.parentId) : void 0;
27
+ if (parent && parent !== node) {
28
+ parent.children.push(node);
29
+ parentOf.set(node, parent);
30
+ } else roots.push(node);
31
+ }
32
+ const reached = /* @__PURE__ */ new Set();
33
+ const reach = (node) => {
34
+ if (reached.has(node)) return;
35
+ reached.add(node);
36
+ for (const child of node.children) reach(child);
37
+ };
38
+ roots.forEach(reach);
39
+ const stranded = [...nodes.values()].filter((node) => !reached.has(node));
40
+ stranded.sort((a, b) => comparePagePosition(a.page, b.page));
41
+ for (const node of stranded) {
42
+ if (reached.has(node)) continue;
43
+ const path = [];
44
+ let at = node;
45
+ while (at && !path.includes(at)) {
46
+ path.push(at);
47
+ at = parentOf.get(at);
48
+ }
49
+ const cut = (at ? path.slice(path.indexOf(at)) : [node]).reduce((first, member) => comparePagePosition(member.page, first.page) < 0 ? member : first);
50
+ const parent = parentOf.get(cut);
51
+ if (parent) parent.children.splice(parent.children.indexOf(cut), 1);
52
+ roots.push(cut);
53
+ reach(cut);
54
+ }
55
+ const sort = (list) => {
56
+ list.sort((a, b) => comparePagePosition(a.page, b.page));
57
+ for (const node of list) sort(node.children);
58
+ };
59
+ sort(roots);
60
+ return roots;
61
+ }
62
+ /** Depth-first rows for rendering, descending only into expanded pages. */
63
+ function visiblePageRows(roots, expanded) {
64
+ const rows = [];
65
+ const walk = (list, depth) => {
66
+ for (const node of list) {
67
+ const open = expanded.has(node.page.id);
68
+ rows.push({
69
+ page: node.page,
70
+ depth,
71
+ hasChildren: node.children.length > 0,
72
+ expanded: open
73
+ });
74
+ if (open) walk(node.children, depth + 1);
75
+ }
76
+ };
77
+ walk(roots, 0);
78
+ return rows;
79
+ }
80
+ /** Ids from the root down to (not including) `pageId`; stops at unloaded parents and cycles. */
81
+ function pageAncestorIds(pages, pageId) {
82
+ const byId = new Map(pages.map((page) => [page.id, page]));
83
+ const path = [];
84
+ const seen = /* @__PURE__ */ new Set([pageId]);
85
+ let parentId = byId.get(pageId)?.parentId ?? null;
86
+ while (parentId && byId.has(parentId) && !seen.has(parentId)) {
87
+ seen.add(parentId);
88
+ path.unshift(parentId);
89
+ parentId = byId.get(parentId)?.parentId ?? null;
90
+ }
91
+ return path;
92
+ }
93
+ exports.buildPageTree = buildPageTree;
94
+ exports.comparePagePosition = comparePagePosition;
95
+ exports.pageAncestorIds = pageAncestorIds;
96
+ exports.visiblePageRows = visiblePageRows;
97
+
98
+ //# sourceMappingURL=pageTree.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pageTree.cjs.js","names":[],"sources":["../../../../src/domains/knowledge/lib/pageTree.ts"],"sourcesContent":["/**\n * Pure page-tree logic for the schema-4 explorer: loaded pages → a sorted\n * tree, the visible rows for a set of expanded ids, and the ancestor path\n * that must open to reveal a selection. Free of React so it unit-tests alone.\n */\n\nimport type { KnowledgePagePayload } from '../pageTypes';\n\nexport interface KnowledgePageTreeNode {\n page: KnowledgePagePayload;\n children: KnowledgePageTreeNode[];\n}\n\nexport interface KnowledgePageTreeRow {\n page: KnowledgePagePayload;\n depth: number;\n hasChildren: boolean;\n expanded: boolean;\n}\n\n/** Sibling order: position by UTF-16 code unit (as the store defines it), then id. */\nexport function comparePagePosition(a: KnowledgePagePayload, b: KnowledgePagePayload): number {\n if (a.position !== b.position) return a.position < b.position ? -1 : 1;\n if (a.id === b.id) return 0;\n return a.id < b.id ? -1 : 1;\n}\n\nfunction isLive(page: KnowledgePagePayload): boolean {\n return !page.deletedAt && !page.mergedInto;\n}\n\n/**\n * Builds the tree from whatever pages are loaded. A page whose parent isn't\n * loaded (or is deleted/merged) becomes a root, and so does the first page (in\n * sibling order) of any parent cycle, so no loaded live page is ever lost.\n */\nexport function buildPageTree(pages: KnowledgePagePayload[]): KnowledgePageTreeNode[] {\n const nodes = new Map<string, KnowledgePageTreeNode>();\n for (const page of pages) {\n if (isLive(page) && !nodes.has(page.id)) nodes.set(page.id, { page, children: [] });\n }\n const roots: KnowledgePageTreeNode[] = [];\n const parentOf = new Map<KnowledgePageTreeNode, KnowledgePageTreeNode>();\n for (const node of nodes.values()) {\n const parent = node.page.parentId ? nodes.get(node.page.parentId) : undefined;\n if (parent && parent !== node) {\n parent.children.push(node);\n parentOf.set(node, parent);\n } else roots.push(node);\n }\n const reached = new Set<KnowledgePageTreeNode>();\n const reach = (node: KnowledgePageTreeNode) => {\n if (reached.has(node)) return;\n reached.add(node);\n for (const child of node.children) reach(child);\n };\n roots.forEach(reach);\n // Whatever no root reaches sits in a parent cycle (or under one): cut each cycle at its first page.\n const stranded = [...nodes.values()].filter(node => !reached.has(node));\n stranded.sort((a, b) => comparePagePosition(a.page, b.page));\n for (const node of stranded) {\n if (reached.has(node)) continue;\n // Climb to the cycle this page hangs from, then cut it at the cycle's first page.\n const path: KnowledgePageTreeNode[] = [];\n let at: KnowledgePageTreeNode | undefined = node;\n while (at && !path.includes(at)) {\n path.push(at);\n at = parentOf.get(at);\n }\n const cycle = at ? path.slice(path.indexOf(at)) : [node];\n const cut = cycle.reduce((first, member) => (comparePagePosition(member.page, first.page) < 0 ? member : first));\n const parent = parentOf.get(cut);\n if (parent) parent.children.splice(parent.children.indexOf(cut), 1);\n roots.push(cut);\n reach(cut);\n }\n const sort = (list: KnowledgePageTreeNode[]) => {\n list.sort((a, b) => comparePagePosition(a.page, b.page));\n for (const node of list) sort(node.children);\n };\n sort(roots);\n return roots;\n}\n\n/** Depth-first rows for rendering, descending only into expanded pages. */\nexport function visiblePageRows(roots: KnowledgePageTreeNode[], expanded: ReadonlySet<string>): KnowledgePageTreeRow[] {\n const rows: KnowledgePageTreeRow[] = [];\n const walk = (list: KnowledgePageTreeNode[], depth: number) => {\n for (const node of list) {\n const open = expanded.has(node.page.id);\n rows.push({ page: node.page, depth, hasChildren: node.children.length > 0, expanded: open });\n if (open) walk(node.children, depth + 1);\n }\n };\n walk(roots, 0);\n return rows;\n}\n\n/** Ids from the root down to (not including) `pageId`; stops at unloaded parents and cycles. */\nexport function pageAncestorIds(pages: KnowledgePagePayload[], pageId: string): string[] {\n const byId = new Map(pages.map(page => [page.id, page]));\n const path: string[] = [];\n const seen = new Set<string>([pageId]);\n let parentId = byId.get(pageId)?.parentId ?? null;\n while (parentId && byId.has(parentId) && !seen.has(parentId)) {\n seen.add(parentId);\n path.unshift(parentId);\n parentId = byId.get(parentId)?.parentId ?? null;\n }\n return path;\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export * from '../../../src/domains/knowledge/lib/pageTree';
@@ -0,0 +1,94 @@
1
+ // src/domains/knowledge/lib/pageTree.ts
2
+ /** Sibling order: position by UTF-16 code unit (as the store defines it), then id. */
3
+ function comparePagePosition(a, b) {
4
+ if (a.position !== b.position) return a.position < b.position ? -1 : 1;
5
+ if (a.id === b.id) return 0;
6
+ return a.id < b.id ? -1 : 1;
7
+ }
8
+ function isLive(page) {
9
+ return !page.deletedAt && !page.mergedInto;
10
+ }
11
+ /**
12
+ * Builds the tree from whatever pages are loaded. A page whose parent isn't
13
+ * loaded (or is deleted/merged) becomes a root, and so does the first page (in
14
+ * sibling order) of any parent cycle, so no loaded live page is ever lost.
15
+ */
16
+ function buildPageTree(pages) {
17
+ const nodes = /* @__PURE__ */ new Map();
18
+ for (const page of pages) if (isLive(page) && !nodes.has(page.id)) nodes.set(page.id, {
19
+ page,
20
+ children: []
21
+ });
22
+ const roots = [];
23
+ const parentOf = /* @__PURE__ */ new Map();
24
+ for (const node of nodes.values()) {
25
+ const parent = node.page.parentId ? nodes.get(node.page.parentId) : void 0;
26
+ if (parent && parent !== node) {
27
+ parent.children.push(node);
28
+ parentOf.set(node, parent);
29
+ } else roots.push(node);
30
+ }
31
+ const reached = /* @__PURE__ */ new Set();
32
+ const reach = (node) => {
33
+ if (reached.has(node)) return;
34
+ reached.add(node);
35
+ for (const child of node.children) reach(child);
36
+ };
37
+ roots.forEach(reach);
38
+ const stranded = [...nodes.values()].filter((node) => !reached.has(node));
39
+ stranded.sort((a, b) => comparePagePosition(a.page, b.page));
40
+ for (const node of stranded) {
41
+ if (reached.has(node)) continue;
42
+ const path = [];
43
+ let at = node;
44
+ while (at && !path.includes(at)) {
45
+ path.push(at);
46
+ at = parentOf.get(at);
47
+ }
48
+ const cut = (at ? path.slice(path.indexOf(at)) : [node]).reduce((first, member) => comparePagePosition(member.page, first.page) < 0 ? member : first);
49
+ const parent = parentOf.get(cut);
50
+ if (parent) parent.children.splice(parent.children.indexOf(cut), 1);
51
+ roots.push(cut);
52
+ reach(cut);
53
+ }
54
+ const sort = (list) => {
55
+ list.sort((a, b) => comparePagePosition(a.page, b.page));
56
+ for (const node of list) sort(node.children);
57
+ };
58
+ sort(roots);
59
+ return roots;
60
+ }
61
+ /** Depth-first rows for rendering, descending only into expanded pages. */
62
+ function visiblePageRows(roots, expanded) {
63
+ const rows = [];
64
+ const walk = (list, depth) => {
65
+ for (const node of list) {
66
+ const open = expanded.has(node.page.id);
67
+ rows.push({
68
+ page: node.page,
69
+ depth,
70
+ hasChildren: node.children.length > 0,
71
+ expanded: open
72
+ });
73
+ if (open) walk(node.children, depth + 1);
74
+ }
75
+ };
76
+ walk(roots, 0);
77
+ return rows;
78
+ }
79
+ /** Ids from the root down to (not including) `pageId`; stops at unloaded parents and cycles. */
80
+ function pageAncestorIds(pages, pageId) {
81
+ const byId = new Map(pages.map((page) => [page.id, page]));
82
+ const path = [];
83
+ const seen = /* @__PURE__ */ new Set([pageId]);
84
+ let parentId = byId.get(pageId)?.parentId ?? null;
85
+ while (parentId && byId.has(parentId) && !seen.has(parentId)) {
86
+ seen.add(parentId);
87
+ path.unshift(parentId);
88
+ parentId = byId.get(parentId)?.parentId ?? null;
89
+ }
90
+ return path;
91
+ }
92
+ export { buildPageTree, comparePagePosition, pageAncestorIds, visiblePageRows };
93
+
94
+ //# sourceMappingURL=pageTree.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pageTree.es.js","names":[],"sources":["../../../../src/domains/knowledge/lib/pageTree.ts"],"sourcesContent":["/**\n * Pure page-tree logic for the schema-4 explorer: loaded pages → a sorted\n * tree, the visible rows for a set of expanded ids, and the ancestor path\n * that must open to reveal a selection. Free of React so it unit-tests alone.\n */\n\nimport type { KnowledgePagePayload } from '../pageTypes';\n\nexport interface KnowledgePageTreeNode {\n page: KnowledgePagePayload;\n children: KnowledgePageTreeNode[];\n}\n\nexport interface KnowledgePageTreeRow {\n page: KnowledgePagePayload;\n depth: number;\n hasChildren: boolean;\n expanded: boolean;\n}\n\n/** Sibling order: position by UTF-16 code unit (as the store defines it), then id. */\nexport function comparePagePosition(a: KnowledgePagePayload, b: KnowledgePagePayload): number {\n if (a.position !== b.position) return a.position < b.position ? -1 : 1;\n if (a.id === b.id) return 0;\n return a.id < b.id ? -1 : 1;\n}\n\nfunction isLive(page: KnowledgePagePayload): boolean {\n return !page.deletedAt && !page.mergedInto;\n}\n\n/**\n * Builds the tree from whatever pages are loaded. A page whose parent isn't\n * loaded (or is deleted/merged) becomes a root, and so does the first page (in\n * sibling order) of any parent cycle, so no loaded live page is ever lost.\n */\nexport function buildPageTree(pages: KnowledgePagePayload[]): KnowledgePageTreeNode[] {\n const nodes = new Map<string, KnowledgePageTreeNode>();\n for (const page of pages) {\n if (isLive(page) && !nodes.has(page.id)) nodes.set(page.id, { page, children: [] });\n }\n const roots: KnowledgePageTreeNode[] = [];\n const parentOf = new Map<KnowledgePageTreeNode, KnowledgePageTreeNode>();\n for (const node of nodes.values()) {\n const parent = node.page.parentId ? nodes.get(node.page.parentId) : undefined;\n if (parent && parent !== node) {\n parent.children.push(node);\n parentOf.set(node, parent);\n } else roots.push(node);\n }\n const reached = new Set<KnowledgePageTreeNode>();\n const reach = (node: KnowledgePageTreeNode) => {\n if (reached.has(node)) return;\n reached.add(node);\n for (const child of node.children) reach(child);\n };\n roots.forEach(reach);\n // Whatever no root reaches sits in a parent cycle (or under one): cut each cycle at its first page.\n const stranded = [...nodes.values()].filter(node => !reached.has(node));\n stranded.sort((a, b) => comparePagePosition(a.page, b.page));\n for (const node of stranded) {\n if (reached.has(node)) continue;\n // Climb to the cycle this page hangs from, then cut it at the cycle's first page.\n const path: KnowledgePageTreeNode[] = [];\n let at: KnowledgePageTreeNode | undefined = node;\n while (at && !path.includes(at)) {\n path.push(at);\n at = parentOf.get(at);\n }\n const cycle = at ? path.slice(path.indexOf(at)) : [node];\n const cut = cycle.reduce((first, member) => (comparePagePosition(member.page, first.page) < 0 ? member : first));\n const parent = parentOf.get(cut);\n if (parent) parent.children.splice(parent.children.indexOf(cut), 1);\n roots.push(cut);\n reach(cut);\n }\n const sort = (list: KnowledgePageTreeNode[]) => {\n list.sort((a, b) => comparePagePosition(a.page, b.page));\n for (const node of list) sort(node.children);\n };\n sort(roots);\n return roots;\n}\n\n/** Depth-first rows for rendering, descending only into expanded pages. */\nexport function visiblePageRows(roots: KnowledgePageTreeNode[], expanded: ReadonlySet<string>): KnowledgePageTreeRow[] {\n const rows: KnowledgePageTreeRow[] = [];\n const walk = (list: KnowledgePageTreeNode[], depth: number) => {\n for (const node of list) {\n const open = expanded.has(node.page.id);\n rows.push({ page: node.page, depth, hasChildren: node.children.length > 0, expanded: open });\n if (open) walk(node.children, depth + 1);\n }\n };\n walk(roots, 0);\n return rows;\n}\n\n/** Ids from the root down to (not including) `pageId`; stops at unloaded parents and cycles. */\nexport function pageAncestorIds(pages: KnowledgePagePayload[], pageId: string): string[] {\n const byId = new Map(pages.map(page => [page.id, page]));\n const path: string[] = [];\n const seen = new Set<string>([pageId]);\n let parentId = byId.get(pageId)?.parentId ?? null;\n while (parentId && byId.has(parentId) && !seen.has(parentId)) {\n seen.add(parentId);\n path.unshift(parentId);\n parentId = byId.get(parentId)?.parentId ?? null;\n }\n return path;\n}\n"],"mappings":";;AAqBA,SAAgB,oBAAoB,GAAyB,GAAiC;CAC5F,IAAI,EAAE,aAAa,EAAE,UAAU,OAAO,EAAE,WAAW,EAAE,WAAW,KAAK;CACrE,IAAI,EAAE,OAAO,EAAE,IAAI,OAAO;CAC1B,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK;AAC5B;AAEA,SAAS,OAAO,MAAqC;CACnD,OAAO,CAAC,KAAK,aAAa,CAAC,KAAK;AAClC;;;;;;AAOA,SAAgB,cAAc,OAAwD;CACpF,MAAM,wBAAQ,IAAI,IAAmC;CACrD,KAAK,MAAM,QAAQ,OACjB,IAAI,OAAO,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,EAAE,GAAG,MAAM,IAAI,KAAK,IAAI;EAAE;EAAM,UAAU,CAAC;CAAE,CAAC;CAEpF,MAAM,QAAiC,CAAC;CACxC,MAAM,2BAAW,IAAI,IAAkD;CACvE,KAAK,MAAM,QAAQ,MAAM,OAAO,GAAG;EACjC,MAAM,SAAS,KAAK,KAAK,WAAW,MAAM,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAA;EACpE,IAAI,UAAU,WAAW,MAAM;GAC7B,OAAO,SAAS,KAAK,IAAI;GACzB,SAAS,IAAI,MAAM,MAAM;EAC3B,OAAO,MAAM,KAAK,IAAI;CACxB;CACA,MAAM,0BAAU,IAAI,IAA2B;CAC/C,MAAM,SAAS,SAAgC;EAC7C,IAAI,QAAQ,IAAI,IAAI,GAAG;EACvB,QAAQ,IAAI,IAAI;EAChB,KAAK,MAAM,SAAS,KAAK,UAAU,MAAM,KAAK;CAChD;CACA,MAAM,QAAQ,KAAK;CAEnB,MAAM,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,QAAO,SAAQ,CAAC,QAAQ,IAAI,IAAI,CAAC;CACtE,SAAS,MAAM,GAAG,MAAM,oBAAoB,EAAE,MAAM,EAAE,IAAI,CAAC;CAC3D,KAAK,MAAM,QAAQ,UAAU;EAC3B,IAAI,QAAQ,IAAI,IAAI,GAAG;EAEvB,MAAM,OAAgC,CAAC;EACvC,IAAI,KAAwC;EAC5C,OAAO,MAAM,CAAC,KAAK,SAAS,EAAE,GAAG;GAC/B,KAAK,KAAK,EAAE;GACZ,KAAK,SAAS,IAAI,EAAE;EACtB;EAEA,MAAM,OADQ,KAAK,KAAK,MAAM,KAAK,QAAQ,EAAE,CAAC,IAAI,CAAC,IAAI,EAAA,CACrC,QAAQ,OAAO,WAAY,oBAAoB,OAAO,MAAM,MAAM,IAAI,IAAI,IAAI,SAAS,KAAM;EAC/G,MAAM,SAAS,SAAS,IAAI,GAAG;EAC/B,IAAI,QAAQ,OAAO,SAAS,OAAO,OAAO,SAAS,QAAQ,GAAG,GAAG,CAAC;EAClE,MAAM,KAAK,GAAG;EACd,MAAM,GAAG;CACX;CACA,MAAM,QAAQ,SAAkC;EAC9C,KAAK,MAAM,GAAG,MAAM,oBAAoB,EAAE,MAAM,EAAE,IAAI,CAAC;EACvD,KAAK,MAAM,QAAQ,MAAM,KAAK,KAAK,QAAQ;CAC7C;CACA,KAAK,KAAK;CACV,OAAO;AACT;;AAGA,SAAgB,gBAAgB,OAAgC,UAAuD;CACrH,MAAM,OAA+B,CAAC;CACtC,MAAM,QAAQ,MAA+B,UAAkB;EAC7D,KAAK,MAAM,QAAQ,MAAM;GACvB,MAAM,OAAO,SAAS,IAAI,KAAK,KAAK,EAAE;GACtC,KAAK,KAAK;IAAE,MAAM,KAAK;IAAM;IAAO,aAAa,KAAK,SAAS,SAAS;IAAG,UAAU;GAAK,CAAC;GAC3F,IAAI,MAAM,KAAK,KAAK,UAAU,QAAQ,CAAC;EACzC;CACF;CACA,KAAK,OAAO,CAAC;CACb,OAAO;AACT;;AAGA,SAAgB,gBAAgB,OAA+B,QAA0B;CACvF,MAAM,OAAO,IAAI,IAAI,MAAM,KAAI,SAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;CACvD,MAAM,OAAiB,CAAC;CACxB,MAAM,uBAAO,IAAI,IAAY,CAAC,MAAM,CAAC;CACrC,IAAI,WAAW,KAAK,IAAI,MAAM,CAAC,EAAE,YAAY;CAC7C,OAAO,YAAY,KAAK,IAAI,QAAQ,KAAK,CAAC,KAAK,IAAI,QAAQ,GAAG;EAC5D,KAAK,IAAI,QAAQ;EACjB,KAAK,QAAQ,QAAQ;EACrB,WAAW,KAAK,IAAI,QAAQ,CAAC,EAAE,YAAY;CAC7C;CACA,OAAO;AACT"}
@@ -0,0 +1,22 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ // src/domains/knowledge/lib/popupPlacement.ts
3
+ var POPUP_GAP = 12;
4
+ var POPUP_MAX_HEIGHT = 320;
5
+ /** Opens below the click, or above it when the click is in the lower half, never taller than the room on that side. */
6
+ function mentionPopupPlacement({ point, pane }) {
7
+ const left = `clamp(0.5rem, ${point.x}px - 10rem, calc(100% - 20.5rem))`;
8
+ if (pane.height > 0 && point.y > pane.height / 2) return {
9
+ left,
10
+ bottom: pane.height - point.y + POPUP_GAP,
11
+ maxHeight: Math.min(POPUP_MAX_HEIGHT, point.y - POPUP_GAP - 8)
12
+ };
13
+ const room = pane.height > 0 ? pane.height - point.y - POPUP_GAP - 8 : POPUP_MAX_HEIGHT;
14
+ return {
15
+ left,
16
+ top: point.y + POPUP_GAP,
17
+ maxHeight: Math.min(POPUP_MAX_HEIGHT, room)
18
+ };
19
+ }
20
+ exports.mentionPopupPlacement = mentionPopupPlacement;
21
+
22
+ //# sourceMappingURL=popupPlacement.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popupPlacement.cjs.js","names":[],"sources":["../../../../src/domains/knowledge/lib/popupPlacement.ts"],"sourcesContent":["import type { CSSProperties } from 'react';\n\nconst POPUP_GAP = 12;\nconst POPUP_MAX_HEIGHT = 320;\n\n/** Opens below the click, or above it when the click is in the lower half, never taller than the room on that side. */\nexport function mentionPopupPlacement({\n point,\n pane,\n}: {\n point: { x: number; y: number };\n pane: { width: number; height: number };\n}): CSSProperties {\n const left = `clamp(0.5rem, ${point.x}px - 10rem, calc(100% - 20.5rem))`;\n if (pane.height > 0 && point.y > pane.height / 2) {\n return {\n left,\n bottom: pane.height - point.y + POPUP_GAP,\n maxHeight: Math.min(POPUP_MAX_HEIGHT, point.y - POPUP_GAP - 8),\n };\n }\n const room = pane.height > 0 ? pane.height - point.y - POPUP_GAP - 8 : POPUP_MAX_HEIGHT;\n return { left, top: point.y + POPUP_GAP, maxHeight: Math.min(POPUP_MAX_HEIGHT, room) };\n}\n"],"mappings":";;AAEA,IAAM,YAAY;AAClB,IAAM,mBAAmB;;AAGzB,SAAgB,sBAAsB,EACpC,OACA,QAIgB;CAChB,MAAM,OAAO,iBAAiB,MAAM,EAAE;CACtC,IAAI,KAAK,SAAS,KAAK,MAAM,IAAI,KAAK,SAAS,GAC7C,OAAO;EACL;EACA,QAAQ,KAAK,SAAS,MAAM,IAAI;EAChC,WAAW,KAAK,IAAI,kBAAkB,MAAM,IAAI,YAAY,CAAC;CAC/D;CAEF,MAAM,OAAO,KAAK,SAAS,IAAI,KAAK,SAAS,MAAM,IAAI,YAAY,IAAI;CACvE,OAAO;EAAE;EAAM,KAAK,MAAM,IAAI;EAAW,WAAW,KAAK,IAAI,kBAAkB,IAAI;CAAE;AACvF"}
@@ -0,0 +1 @@
1
+ export * from '../../../src/domains/knowledge/lib/popupPlacement';
@@ -0,0 +1,21 @@
1
+ // src/domains/knowledge/lib/popupPlacement.ts
2
+ var POPUP_GAP = 12;
3
+ var POPUP_MAX_HEIGHT = 320;
4
+ /** Opens below the click, or above it when the click is in the lower half, never taller than the room on that side. */
5
+ function mentionPopupPlacement({ point, pane }) {
6
+ const left = `clamp(0.5rem, ${point.x}px - 10rem, calc(100% - 20.5rem))`;
7
+ if (pane.height > 0 && point.y > pane.height / 2) return {
8
+ left,
9
+ bottom: pane.height - point.y + POPUP_GAP,
10
+ maxHeight: Math.min(POPUP_MAX_HEIGHT, point.y - POPUP_GAP - 8)
11
+ };
12
+ const room = pane.height > 0 ? pane.height - point.y - POPUP_GAP - 8 : POPUP_MAX_HEIGHT;
13
+ return {
14
+ left,
15
+ top: point.y + POPUP_GAP,
16
+ maxHeight: Math.min(POPUP_MAX_HEIGHT, room)
17
+ };
18
+ }
19
+ export { mentionPopupPlacement };
20
+
21
+ //# sourceMappingURL=popupPlacement.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popupPlacement.es.js","names":[],"sources":["../../../../src/domains/knowledge/lib/popupPlacement.ts"],"sourcesContent":["import type { CSSProperties } from 'react';\n\nconst POPUP_GAP = 12;\nconst POPUP_MAX_HEIGHT = 320;\n\n/** Opens below the click, or above it when the click is in the lower half, never taller than the room on that side. */\nexport function mentionPopupPlacement({\n point,\n pane,\n}: {\n point: { x: number; y: number };\n pane: { width: number; height: number };\n}): CSSProperties {\n const left = `clamp(0.5rem, ${point.x}px - 10rem, calc(100% - 20.5rem))`;\n if (pane.height > 0 && point.y > pane.height / 2) {\n return {\n left,\n bottom: pane.height - point.y + POPUP_GAP,\n maxHeight: Math.min(POPUP_MAX_HEIGHT, point.y - POPUP_GAP - 8),\n };\n }\n const room = pane.height > 0 ? pane.height - point.y - POPUP_GAP - 8 : POPUP_MAX_HEIGHT;\n return { left, top: point.y + POPUP_GAP, maxHeight: Math.min(POPUP_MAX_HEIGHT, room) };\n}\n"],"mappings":";AAEA,IAAM,YAAY;AAClB,IAAM,mBAAmB;;AAGzB,SAAgB,sBAAsB,EACpC,OACA,QAIgB;CAChB,MAAM,OAAO,iBAAiB,MAAM,EAAE;CACtC,IAAI,KAAK,SAAS,KAAK,MAAM,IAAI,KAAK,SAAS,GAC7C,OAAO;EACL;EACA,QAAQ,KAAK,SAAS,MAAM,IAAI;EAChC,WAAW,KAAK,IAAI,kBAAkB,MAAM,IAAI,YAAY,CAAC;CAC/D;CAEF,MAAM,OAAO,KAAK,SAAS,IAAI,KAAK,SAAS,MAAM,IAAI,YAAY,IAAI;CACvE,OAAO;EAAE;EAAM,KAAK,MAAM,IAAI;EAAW,WAAW,KAAK,IAAI,kBAAkB,IAAI;CAAE;AACvF"}
@@ -0,0 +1,26 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ // src/domains/knowledge/lib/recordText.ts
3
+ var WIKILINK = /\[\[([^\]]+)\]\]/g;
4
+ function parseRecordSegments(text) {
5
+ const segments = [];
6
+ let last = 0;
7
+ for (const match of text.matchAll(WIKILINK)) {
8
+ if (match.index > last) segments.push({
9
+ type: "text",
10
+ value: text.slice(last, match.index)
11
+ });
12
+ segments.push({
13
+ type: "wikilink",
14
+ value: match[1] ?? ""
15
+ });
16
+ last = match.index + match[0].length;
17
+ }
18
+ if (last < text.length) segments.push({
19
+ type: "text",
20
+ value: text.slice(last)
21
+ });
22
+ return segments;
23
+ }
24
+ exports.parseRecordSegments = parseRecordSegments;
25
+
26
+ //# sourceMappingURL=recordText.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordText.cjs.js","names":[],"sources":["../../../../src/domains/knowledge/lib/recordText.ts"],"sourcesContent":["/** Split knowledge record text into plain-text and [[wikilink]] segments for rendering. */\n\nexport type RecordSegment = { type: 'text'; value: string } | { type: 'wikilink'; value: string };\n\nconst WIKILINK = /\\[\\[([^\\]]+)\\]\\]/g;\n\nexport function parseRecordSegments(text: string): RecordSegment[] {\n const segments: RecordSegment[] = [];\n let last = 0;\n for (const match of text.matchAll(WIKILINK)) {\n if (match.index > last) segments.push({ type: 'text', value: text.slice(last, match.index) });\n segments.push({ type: 'wikilink', value: match[1] ?? '' });\n last = match.index + match[0].length;\n }\n if (last < text.length) segments.push({ type: 'text', value: text.slice(last) });\n return segments;\n}\n"],"mappings":";;AAIA,IAAM,WAAW;AAEjB,SAAgB,oBAAoB,MAA+B;CACjE,MAAM,WAA4B,CAAC;CACnC,IAAI,OAAO;CACX,KAAK,MAAM,SAAS,KAAK,SAAS,QAAQ,GAAG;EAC3C,IAAI,MAAM,QAAQ,MAAM,SAAS,KAAK;GAAE,MAAM;GAAQ,OAAO,KAAK,MAAM,MAAM,MAAM,KAAK;EAAE,CAAC;EAC5F,SAAS,KAAK;GAAE,MAAM;GAAY,OAAO,MAAM,MAAM;EAAG,CAAC;EACzD,OAAO,MAAM,QAAQ,MAAM,EAAE,CAAC;CAChC;CACA,IAAI,OAAO,KAAK,QAAQ,SAAS,KAAK;EAAE,MAAM;EAAQ,OAAO,KAAK,MAAM,IAAI;CAAE,CAAC;CAC/E,OAAO;AACT"}
@@ -0,0 +1 @@
1
+ export * from '../../../src/domains/knowledge/lib/recordText';
@@ -0,0 +1,25 @@
1
+ // src/domains/knowledge/lib/recordText.ts
2
+ var WIKILINK = /\[\[([^\]]+)\]\]/g;
3
+ function parseRecordSegments(text) {
4
+ const segments = [];
5
+ let last = 0;
6
+ for (const match of text.matchAll(WIKILINK)) {
7
+ if (match.index > last) segments.push({
8
+ type: "text",
9
+ value: text.slice(last, match.index)
10
+ });
11
+ segments.push({
12
+ type: "wikilink",
13
+ value: match[1] ?? ""
14
+ });
15
+ last = match.index + match[0].length;
16
+ }
17
+ if (last < text.length) segments.push({
18
+ type: "text",
19
+ value: text.slice(last)
20
+ });
21
+ return segments;
22
+ }
23
+ export { parseRecordSegments };
24
+
25
+ //# sourceMappingURL=recordText.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordText.es.js","names":[],"sources":["../../../../src/domains/knowledge/lib/recordText.ts"],"sourcesContent":["/** Split knowledge record text into plain-text and [[wikilink]] segments for rendering. */\n\nexport type RecordSegment = { type: 'text'; value: string } | { type: 'wikilink'; value: string };\n\nconst WIKILINK = /\\[\\[([^\\]]+)\\]\\]/g;\n\nexport function parseRecordSegments(text: string): RecordSegment[] {\n const segments: RecordSegment[] = [];\n let last = 0;\n for (const match of text.matchAll(WIKILINK)) {\n if (match.index > last) segments.push({ type: 'text', value: text.slice(last, match.index) });\n segments.push({ type: 'wikilink', value: match[1] ?? '' });\n last = match.index + match[0].length;\n }\n if (last < text.length) segments.push({ type: 'text', value: text.slice(last) });\n return segments;\n}\n"],"mappings":";AAIA,IAAM,WAAW;AAEjB,SAAgB,oBAAoB,MAA+B;CACjE,MAAM,WAA4B,CAAC;CACnC,IAAI,OAAO;CACX,KAAK,MAAM,SAAS,KAAK,SAAS,QAAQ,GAAG;EAC3C,IAAI,MAAM,QAAQ,MAAM,SAAS,KAAK;GAAE,MAAM;GAAQ,OAAO,KAAK,MAAM,MAAM,MAAM,KAAK;EAAE,CAAC;EAC5F,SAAS,KAAK;GAAE,MAAM;GAAY,OAAO,MAAM,MAAM;EAAG,CAAC;EACzD,OAAO,MAAM,QAAQ,MAAM,EAAE,CAAC;CAChC;CACA,IAAI,OAAO,KAAK,QAAQ,SAAS,KAAK;EAAE,MAAM;EAAQ,OAAO,KAAK,MAAM,IAAI;CAAE,CAAC;CAC/E,OAAO;AACT"}
File without changes
@@ -0,0 +1 @@
1
+ export * from '../../src/domains/knowledge/pageTypes';
File without changes
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_observability_saved_views = require("../../../observability-saved-views-CwCii7hh.cjs");
2
+ const require_observability_saved_views = require("../../../observability-saved-views-BNU3GkK6.cjs");
3
3
  let react_jsx_runtime = require("react/jsx-runtime");
4
4
  // src/domains/logs/components/logs-saved-views.tsx
5
5
  /** Saved views of the logs page, placed by the host next to `LogsToolbar`. Renders nothing without a `SavedViewsProvider`. */
@@ -1,4 +1,4 @@
1
- import { t as ObservabilitySavedViews } from "../../../observability-saved-views-Bo5kLN20.js";
1
+ import { t as ObservabilitySavedViews } from "../../../observability-saved-views-Zlz74syw.js";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  // src/domains/logs/components/logs-saved-views.tsx
4
4
  /** Saved views of the logs page, placed by the host next to `LogsToolbar`. Renders nothing without a `SavedViewsProvider`. */
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_observability_saved_views = require("../../../observability-saved-views-CwCii7hh.cjs");
2
+ const require_observability_saved_views = require("../../../observability-saved-views-BNU3GkK6.cjs");
3
3
  exports.ObservabilitySavedViews = require_observability_saved_views.ObservabilitySavedViews;
@@ -1 +1 @@
1
- export { t as ObservabilitySavedViews } from "../../../observability-saved-views-Bo5kLN20.js";
1
+ export { t as ObservabilitySavedViews } from "../../../observability-saved-views-Zlz74syw.js";
@@ -3,12 +3,13 @@ const require_domains_observability_saved_views_saved_views_source = require("./
3
3
  let react_jsx_runtime = require("react/jsx-runtime");
4
4
  // src/domains/observability/saved-views/saved-views-provider.tsx
5
5
  /** Turns saved views on for the observability pages below it. Without it, the pages show none. */
6
- function SavedViewsProvider({ source, queryKeyScope = [], canEdit, children }) {
6
+ function SavedViewsProvider({ source, queryKeyScope = [], canEdit, canCreate, children }) {
7
7
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_domains_observability_saved_views_saved_views_source.SavedViewsContext.Provider, {
8
8
  value: {
9
9
  source,
10
10
  queryKeyScope,
11
- canEdit
11
+ canEdit,
12
+ canCreate
12
13
  },
13
14
  children
14
15
  });
@@ -1 +1 @@
1
- {"version":3,"file":"saved-views-provider.cjs.js","names":["SavedViewsContext"],"sources":["../../../../src/domains/observability/saved-views/saved-views-provider.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { SavedViewsContext } from './saved-views-source';\nimport type { SavedViewRecord, SavedViewsSource } from './saved-views-source';\n\nexport type SavedViewsProviderProps = {\n source: SavedViewsSource;\n /** Keeps the cached views of one scope (e.g. `['project', projectId]`) apart from another's. */\n queryKeyScope?: readonly unknown[];\n /**\n * Whether a view can be renamed, deleted or overwritten here, e.g. `view => view.scope !== 'org'` where\n * org-wide views are read-only. Read-only views can still be applied and duplicated. Defaults to all.\n */\n canEdit?: (view: SavedViewRecord) => boolean;\n children: ReactNode;\n};\n\n/** Turns saved views on for the observability pages below it. Without it, the pages show none. */\nexport function SavedViewsProvider({ source, queryKeyScope = [], canEdit, children }: SavedViewsProviderProps) {\n return <SavedViewsContext.Provider value={{ source, queryKeyScope, canEdit }}>{children}</SavedViewsContext.Provider>;\n}\n"],"mappings":";;;;;AAiBA,SAAgB,mBAAmB,EAAE,QAAQ,gBAAgB,CAAC,GAAG,SAAS,YAAqC;CAC7G,OAAO,iBAAA,GAAA,kBAAA,IAAA,CAACA,6DAAAA,kBAAkB,UAAnB;EAA4B,OAAO;GAAE;GAAQ;GAAe;EAAQ;EAAI;CAAqC,CAAA;AACtH"}
1
+ {"version":3,"file":"saved-views-provider.cjs.js","names":["SavedViewsContext"],"sources":["../../../../src/domains/observability/saved-views/saved-views-provider.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { SavedViewsContext } from './saved-views-source';\nimport type { SavedViewRecord, SavedViewsSource } from './saved-views-source';\n\nexport type SavedViewsProviderProps = {\n source: SavedViewsSource;\n /** Keeps the cached views of one scope (e.g. `['project', projectId]`) apart from another's. */\n queryKeyScope?: readonly unknown[];\n /**\n * Whether a view can be renamed, deleted or overwritten here, e.g. `view => view.scope !== 'org'` where\n * org-wide views are read-only. Read-only views can still be applied and duplicated. Defaults to all.\n */\n canEdit?: (view: SavedViewRecord) => boolean;\n /**\n * Whether new views can be saved here (save as new view, duplicate), e.g. `false` for a user who may only\n * read views. With `canEdit={() => false}` the views can only be applied. Defaults to true.\n */\n canCreate?: boolean;\n children: ReactNode;\n};\n\n/** Turns saved views on for the observability pages below it. Without it, the pages show none. */\nexport function SavedViewsProvider({\n source,\n queryKeyScope = [],\n canEdit,\n canCreate,\n children,\n}: SavedViewsProviderProps) {\n return (\n <SavedViewsContext.Provider value={{ source, queryKeyScope, canEdit, canCreate }}>\n {children}\n </SavedViewsContext.Provider>\n );\n}\n"],"mappings":";;;;;AAsBA,SAAgB,mBAAmB,EACjC,QACA,gBAAgB,CAAC,GACjB,SACA,WACA,YAC0B;CAC1B,OACE,iBAAA,GAAA,kBAAA,IAAA,CAACA,6DAAAA,kBAAkB,UAAnB;EAA4B,OAAO;GAAE;GAAQ;GAAe;GAAS;EAAU;EAC5E;CACyB,CAAA;AAEhC"}
@@ -2,12 +2,13 @@ import { SavedViewsContext } from "./saved-views-source.es.js";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  // src/domains/observability/saved-views/saved-views-provider.tsx
4
4
  /** Turns saved views on for the observability pages below it. Without it, the pages show none. */
5
- function SavedViewsProvider({ source, queryKeyScope = [], canEdit, children }) {
5
+ function SavedViewsProvider({ source, queryKeyScope = [], canEdit, canCreate, children }) {
6
6
  return /* @__PURE__ */ jsx(SavedViewsContext.Provider, {
7
7
  value: {
8
8
  source,
9
9
  queryKeyScope,
10
- canEdit
10
+ canEdit,
11
+ canCreate
11
12
  },
12
13
  children
13
14
  });
@@ -1 +1 @@
1
- {"version":3,"file":"saved-views-provider.es.js","names":[],"sources":["../../../../src/domains/observability/saved-views/saved-views-provider.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { SavedViewsContext } from './saved-views-source';\nimport type { SavedViewRecord, SavedViewsSource } from './saved-views-source';\n\nexport type SavedViewsProviderProps = {\n source: SavedViewsSource;\n /** Keeps the cached views of one scope (e.g. `['project', projectId]`) apart from another's. */\n queryKeyScope?: readonly unknown[];\n /**\n * Whether a view can be renamed, deleted or overwritten here, e.g. `view => view.scope !== 'org'` where\n * org-wide views are read-only. Read-only views can still be applied and duplicated. Defaults to all.\n */\n canEdit?: (view: SavedViewRecord) => boolean;\n children: ReactNode;\n};\n\n/** Turns saved views on for the observability pages below it. Without it, the pages show none. */\nexport function SavedViewsProvider({ source, queryKeyScope = [], canEdit, children }: SavedViewsProviderProps) {\n return <SavedViewsContext.Provider value={{ source, queryKeyScope, canEdit }}>{children}</SavedViewsContext.Provider>;\n}\n"],"mappings":";;;;AAiBA,SAAgB,mBAAmB,EAAE,QAAQ,gBAAgB,CAAC,GAAG,SAAS,YAAqC;CAC7G,OAAO,oBAAC,kBAAkB,UAAnB;EAA4B,OAAO;GAAE;GAAQ;GAAe;EAAQ;EAAI;CAAqC,CAAA;AACtH"}
1
+ {"version":3,"file":"saved-views-provider.es.js","names":[],"sources":["../../../../src/domains/observability/saved-views/saved-views-provider.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { SavedViewsContext } from './saved-views-source';\nimport type { SavedViewRecord, SavedViewsSource } from './saved-views-source';\n\nexport type SavedViewsProviderProps = {\n source: SavedViewsSource;\n /** Keeps the cached views of one scope (e.g. `['project', projectId]`) apart from another's. */\n queryKeyScope?: readonly unknown[];\n /**\n * Whether a view can be renamed, deleted or overwritten here, e.g. `view => view.scope !== 'org'` where\n * org-wide views are read-only. Read-only views can still be applied and duplicated. Defaults to all.\n */\n canEdit?: (view: SavedViewRecord) => boolean;\n /**\n * Whether new views can be saved here (save as new view, duplicate), e.g. `false` for a user who may only\n * read views. With `canEdit={() => false}` the views can only be applied. Defaults to true.\n */\n canCreate?: boolean;\n children: ReactNode;\n};\n\n/** Turns saved views on for the observability pages below it. Without it, the pages show none. */\nexport function SavedViewsProvider({\n source,\n queryKeyScope = [],\n canEdit,\n canCreate,\n children,\n}: SavedViewsProviderProps) {\n return (\n <SavedViewsContext.Provider value={{ source, queryKeyScope, canEdit, canCreate }}>\n {children}\n </SavedViewsContext.Provider>\n );\n}\n"],"mappings":";;;;AAsBA,SAAgB,mBAAmB,EACjC,QACA,gBAAgB,CAAC,GACjB,SACA,WACA,YAC0B;CAC1B,OACE,oBAAC,kBAAkB,UAAnB;EAA4B,OAAO;GAAE;GAAQ;GAAe;GAAS;EAAU;EAC5E;CACyB,CAAA;AAEhC"}
@@ -1 +1 @@
1
- {"version":3,"file":"saved-views-source.cjs.js","names":["createContext","useContext"],"sources":["../../../../src/domains/observability/saved-views/saved-views-source.ts"],"sourcesContent":["import type { SavedViewPage, SavedViewState } from '@mastra/core/storage';\nimport { createContext, useContext } from 'react';\n\n/** A saved view as the host stores it. */\nexport type SavedViewRecord = {\n id: string;\n name: string;\n state: SavedViewState;\n updatedAt: string;\n /** `org`: shared by every project of the organization. Omitted: saved where it is listed. */\n scope?: 'org' | 'project';\n};\n\n/**\n * Where saved views are stored. The host supplies it (e.g. backed by its API), so the observability\n * pages show saved views only where the host supports them.\n */\nexport type SavedViewsSource = {\n /** The views of one page, newest-updated first. */\n list: (page: SavedViewPage) => Promise<SavedViewRecord[]>;\n /** Rejects with `SavedViewNameTakenError` when the name is already used. */\n create: (input: { name: string; state: SavedViewState }) => Promise<SavedViewRecord>;\n /** Rejects with `SavedViewNameTakenError` when the new name is already used. */\n update: (id: string, patch: { name?: string; state?: SavedViewState }) => Promise<SavedViewRecord>;\n remove: (id: string) => Promise<void>;\n};\n\n/** Thrown by a `SavedViewsSource` when a view with the same name already exists (e.g. on an HTTP 409). */\nexport class SavedViewNameTakenError extends Error {\n constructor(message = 'A saved view with this name already exists') {\n super(message);\n this.name = 'SavedViewNameTakenError';\n }\n}\n\nexport type SavedViewsContextValue = {\n source: SavedViewsSource;\n queryKeyScope: readonly unknown[];\n canEdit?: (view: SavedViewRecord) => boolean;\n};\n\nexport const SavedViewsContext = createContext<SavedViewsContextValue | undefined>(undefined);\n\nexport const useSavedViewsSource = () => useContext(SavedViewsContext);\n\nexport const savedViewsQueryKey = (queryKeyScope: readonly unknown[], page: SavedViewPage) => [\n 'saved-views',\n ...queryKeyScope,\n page,\n];\n"],"mappings":";;;;AA4BA,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,UAAU,8CAA8C;EAClE,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAQA,IAAa,qBAAA,GAAoBA,MAAAA,cAAAA,CAAkD,KAAA,CAAS;AAE5F,IAAa,6BAAA,GAA4BC,MAAAA,WAAAA,CAAW,iBAAiB;AAErE,IAAa,sBAAsB,eAAmC,SAAwB;CAC5F;CACA,GAAG;CACH;AACF"}
1
+ {"version":3,"file":"saved-views-source.cjs.js","names":["createContext","useContext"],"sources":["../../../../src/domains/observability/saved-views/saved-views-source.ts"],"sourcesContent":["import type { SavedViewPage, SavedViewState } from '@mastra/core/storage';\nimport { createContext, useContext } from 'react';\n\n/** A saved view as the host stores it. */\nexport type SavedViewRecord = {\n id: string;\n name: string;\n state: SavedViewState;\n updatedAt: string;\n /** `org`: shared by every project of the organization. Omitted: saved where it is listed. */\n scope?: 'org' | 'project';\n};\n\n/**\n * Where saved views are stored. The host supplies it (e.g. backed by its API), so the observability\n * pages show saved views only where the host supports them.\n */\nexport type SavedViewsSource = {\n /** The views of one page, newest-updated first. */\n list: (page: SavedViewPage) => Promise<SavedViewRecord[]>;\n /** Rejects with `SavedViewNameTakenError` when the name is already used. */\n create: (input: { name: string; state: SavedViewState }) => Promise<SavedViewRecord>;\n /** Rejects with `SavedViewNameTakenError` when the new name is already used. */\n update: (id: string, patch: { name?: string; state?: SavedViewState }) => Promise<SavedViewRecord>;\n remove: (id: string) => Promise<void>;\n};\n\n/** Thrown by a `SavedViewsSource` when a view with the same name already exists (e.g. on an HTTP 409). */\nexport class SavedViewNameTakenError extends Error {\n constructor(message = 'A saved view with this name already exists') {\n super(message);\n this.name = 'SavedViewNameTakenError';\n }\n}\n\nexport type SavedViewsContextValue = {\n source: SavedViewsSource;\n queryKeyScope: readonly unknown[];\n canEdit?: (view: SavedViewRecord) => boolean;\n canCreate?: boolean;\n};\n\nexport const SavedViewsContext = createContext<SavedViewsContextValue | undefined>(undefined);\n\nexport const useSavedViewsSource = () => useContext(SavedViewsContext);\n\nexport const savedViewsQueryKey = (queryKeyScope: readonly unknown[], page: SavedViewPage) => [\n 'saved-views',\n ...queryKeyScope,\n page,\n];\n"],"mappings":";;;;AA4BA,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,UAAU,8CAA8C;EAClE,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AASA,IAAa,qBAAA,GAAoBA,MAAAA,cAAAA,CAAkD,KAAA,CAAS;AAE5F,IAAa,6BAAA,GAA4BC,MAAAA,WAAAA,CAAW,iBAAiB;AAErE,IAAa,sBAAsB,eAAmC,SAAwB;CAC5F;CACA,GAAG;CACH;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"saved-views-source.es.js","names":[],"sources":["../../../../src/domains/observability/saved-views/saved-views-source.ts"],"sourcesContent":["import type { SavedViewPage, SavedViewState } from '@mastra/core/storage';\nimport { createContext, useContext } from 'react';\n\n/** A saved view as the host stores it. */\nexport type SavedViewRecord = {\n id: string;\n name: string;\n state: SavedViewState;\n updatedAt: string;\n /** `org`: shared by every project of the organization. Omitted: saved where it is listed. */\n scope?: 'org' | 'project';\n};\n\n/**\n * Where saved views are stored. The host supplies it (e.g. backed by its API), so the observability\n * pages show saved views only where the host supports them.\n */\nexport type SavedViewsSource = {\n /** The views of one page, newest-updated first. */\n list: (page: SavedViewPage) => Promise<SavedViewRecord[]>;\n /** Rejects with `SavedViewNameTakenError` when the name is already used. */\n create: (input: { name: string; state: SavedViewState }) => Promise<SavedViewRecord>;\n /** Rejects with `SavedViewNameTakenError` when the new name is already used. */\n update: (id: string, patch: { name?: string; state?: SavedViewState }) => Promise<SavedViewRecord>;\n remove: (id: string) => Promise<void>;\n};\n\n/** Thrown by a `SavedViewsSource` when a view with the same name already exists (e.g. on an HTTP 409). */\nexport class SavedViewNameTakenError extends Error {\n constructor(message = 'A saved view with this name already exists') {\n super(message);\n this.name = 'SavedViewNameTakenError';\n }\n}\n\nexport type SavedViewsContextValue = {\n source: SavedViewsSource;\n queryKeyScope: readonly unknown[];\n canEdit?: (view: SavedViewRecord) => boolean;\n};\n\nexport const SavedViewsContext = createContext<SavedViewsContextValue | undefined>(undefined);\n\nexport const useSavedViewsSource = () => useContext(SavedViewsContext);\n\nexport const savedViewsQueryKey = (queryKeyScope: readonly unknown[], page: SavedViewPage) => [\n 'saved-views',\n ...queryKeyScope,\n page,\n];\n"],"mappings":";;;AA4BA,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,UAAU,8CAA8C;EAClE,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAQA,IAAa,oBAAoB,cAAkD,KAAA,CAAS;AAE5F,IAAa,4BAA4B,WAAW,iBAAiB;AAErE,IAAa,sBAAsB,eAAmC,SAAwB;CAC5F;CACA,GAAG;CACH;AACF"}
1
+ {"version":3,"file":"saved-views-source.es.js","names":[],"sources":["../../../../src/domains/observability/saved-views/saved-views-source.ts"],"sourcesContent":["import type { SavedViewPage, SavedViewState } from '@mastra/core/storage';\nimport { createContext, useContext } from 'react';\n\n/** A saved view as the host stores it. */\nexport type SavedViewRecord = {\n id: string;\n name: string;\n state: SavedViewState;\n updatedAt: string;\n /** `org`: shared by every project of the organization. Omitted: saved where it is listed. */\n scope?: 'org' | 'project';\n};\n\n/**\n * Where saved views are stored. The host supplies it (e.g. backed by its API), so the observability\n * pages show saved views only where the host supports them.\n */\nexport type SavedViewsSource = {\n /** The views of one page, newest-updated first. */\n list: (page: SavedViewPage) => Promise<SavedViewRecord[]>;\n /** Rejects with `SavedViewNameTakenError` when the name is already used. */\n create: (input: { name: string; state: SavedViewState }) => Promise<SavedViewRecord>;\n /** Rejects with `SavedViewNameTakenError` when the new name is already used. */\n update: (id: string, patch: { name?: string; state?: SavedViewState }) => Promise<SavedViewRecord>;\n remove: (id: string) => Promise<void>;\n};\n\n/** Thrown by a `SavedViewsSource` when a view with the same name already exists (e.g. on an HTTP 409). */\nexport class SavedViewNameTakenError extends Error {\n constructor(message = 'A saved view with this name already exists') {\n super(message);\n this.name = 'SavedViewNameTakenError';\n }\n}\n\nexport type SavedViewsContextValue = {\n source: SavedViewsSource;\n queryKeyScope: readonly unknown[];\n canEdit?: (view: SavedViewRecord) => boolean;\n canCreate?: boolean;\n};\n\nexport const SavedViewsContext = createContext<SavedViewsContextValue | undefined>(undefined);\n\nexport const useSavedViewsSource = () => useContext(SavedViewsContext);\n\nexport const savedViewsQueryKey = (queryKeyScope: readonly unknown[], page: SavedViewPage) => [\n 'saved-views',\n ...queryKeyScope,\n page,\n];\n"],"mappings":";;;AA4BA,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,UAAU,8CAA8C;EAClE,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AASA,IAAa,oBAAoB,cAAkD,KAAA,CAAS;AAE5F,IAAa,4BAA4B,WAAW,iBAAiB;AAErE,IAAa,sBAAsB,eAAmC,SAAwB;CAC5F;CACA,GAAG;CACH;AACF"}
@@ -60,7 +60,7 @@ var validName = (name) => {
60
60
  * the draft: picking a view writes its state to the URL (and its columns to the column picker), and
61
61
  * the view shows unsaved changes while the URL differs from it.
62
62
  */
63
- function useHostedSavedViews({ page, source, queryKeyScope, searchParams, setSearchParams, columns, defaultDatePreset, canEdit = () => true }) {
63
+ function useHostedSavedViews({ page, source, queryKeyScope, searchParams, setSearchParams, columns, defaultDatePreset, canEdit = () => true, canCreate = true }) {
64
64
  const queryClient = (0, _tanstack_react_query.useQueryClient)();
65
65
  const queryKey = require_domains_observability_saved_views_saved_views_source.savedViewsQueryKey(queryKeyScope, page);
66
66
  const { data: records = [] } = (0, _tanstack_react_query.useQuery)({
@@ -151,6 +151,7 @@ function useHostedSavedViews({ page, source, queryKeyScope, searchParams, setSea
151
151
  applied: draft ?? activeView,
152
152
  select,
153
153
  create: () => {
154
+ if (!canCreate) return;
154
155
  setError(void 0);
155
156
  setCreatingName(NEW_VIEW_NAME);
156
157
  },
@@ -201,7 +202,7 @@ function useHostedSavedViews({ page, source, queryKeyScope, searchParams, setSea
201
202
  },
202
203
  duplicate: async (viewId) => {
203
204
  const original = records.find((item) => item.id === viewId);
204
- if (!original || pending || !fitsSizeLimit(original.state)) return;
205
+ if (!canCreate || !original || pending || !fitsSizeLimit(original.state)) return;
205
206
  const name = validName(`${original.name} copy`) ?? original.name;
206
207
  const choice = choiceRef.current;
207
208
  const copy = await run(() => source.create({
@@ -1 +1 @@
1
- {"version":3,"file":"use-hosted-saved-views.cjs.js","names":["SavedViewNameTakenError","savedViewNameSchema","useQueryClient","savedViewsQueryKey","useQuery","useState","useRef","savedViewFromSearchParams","applySavedView"],"sources":["../../../../src/domains/observability/saved-views/use-hosted-saved-views.ts"],"sourcesContent":["import type { SavedViewPage, SavedViewState } from '@mastra/core/storage';\nimport { useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useRef, useState } from 'react';\nimport { applySavedView, savedViewFromSearchParams } from '../saved-view-state';\nimport { SavedViewNameTakenError, savedViewsQueryKey } from './saved-views-source';\nimport type { SavedViewRecord, SavedViewsContextValue } from './saved-views-source';\nimport type { TraceColumnPreferences } from '@/domains/traces/trace-list-columns';\nimport type { FilterBarItem } from '@/ds/components/FilterBar/types';\nimport { savedViewNameSchema } from '@/ds/components/SavedViews/saved-view-schema';\nimport type { SavedView } from '@/ds/components/SavedViews/saved-view-schema';\nimport type { SavedViewDraft, SavedViewsController } from '@/ds/components/SavedViews/use-saved-views';\nimport type { SetURLSearchParamsLike } from '@/lib/sort/use-url-sort';\n\n/** Same limit as the saved view schema in `@mastra/core`, checked before a request is made. */\nexport const SAVED_VIEW_STATE_MAX_BYTES = 16 * 1024;\n\nconst NEW_VIEW_NAME = 'Untitled view';\nexport const SAVED_VIEW_TOO_LARGE_MESSAGE =\n 'This view is too large to save (16 KiB max). Remove some filters or columns.';\nconst SAVE_FAILED_MESSAGE = 'Could not save the view. Try again.';\nconst DELETE_FAILED_MESSAGE = 'Could not delete the view. Try again.';\n\n/** A view's state, and for a stored view its record. */\nexport type HostedSavedViewSettings = { state: SavedViewState; record?: SavedViewRecord };\n\nexport type SavedViewColumnsControl = {\n preferences: TraceColumnPreferences;\n setPreferences: (next: TraceColumnPreferences) => void;\n /** Back to the page's default columns, when the page default is picked. */\n resetColumns: () => void;\n};\n\nexport type UseHostedSavedViewsArgs = SavedViewsContextValue & {\n page: SavedViewPage;\n searchParams: URLSearchParams;\n setSearchParams: SetURLSearchParamsLike;\n /** The page's column picker; omitted on pages with fixed columns. */\n columns?: SavedViewColumnsControl;\n /** The range the page shows when the URL names none, when the host's differs from the page's own. */\n defaultDatePreset?: string;\n};\n\nconst stateBytes = (state: SavedViewState) => new TextEncoder().encode(JSON.stringify(state)).length;\n\n/** The filters a view's hover summary lists: its top-level filters (groups are counted separately). */\nfunction summaryFilters(state: SavedViewState): FilterBarItem[] {\n return state.filters.flatMap((node, index) =>\n 'nodes' in node\n ? []\n : [\n {\n id: `${index}`,\n fieldId: node.fieldId,\n operatorId: node.operatorId ?? (Array.isArray(node.value) ? 'in' : 'is'),\n value: node.value,\n },\n ],\n );\n}\n\n/** The parts of a view that compare equal when they set the same URL and columns. */\nfunction comparable(state: SavedViewState, withColumns: boolean): string {\n const { filters, dateRange, sort, options, columns } = state;\n return JSON.stringify({ filters, dateRange, sort, options, columns: withColumns ? columns : undefined });\n}\n\nfunction errorMessage(error: unknown, name: string): string {\n return error instanceof SavedViewNameTakenError ? `A view named \"${name}\" already exists.` : SAVE_FAILED_MESSAGE;\n}\n\n/** What the user picked: a saved view, or the page default when `id` is undefined. */\ntype Picked = { id: string | undefined };\n\n/** The unsaved state shown in the bar: a view being created, or edits to the active view. */\nfunction draftOf(\n current: SavedViewState,\n creatingName: string | undefined,\n editedRecord: SavedViewRecord | undefined,\n): SavedViewDraft<HostedSavedViewSettings> | undefined {\n const unsaved = { filters: summaryFilters(current), settings: { state: current } };\n if (creatingName !== undefined) return { name: creatingName, ...unsaved };\n if (editedRecord) return { viewId: editedRecord.id, name: editedRecord.name, ...unsaved };\n return undefined;\n}\n\nconst validName = (name: string) => {\n const parsed = savedViewNameSchema.safeParse(name);\n return parsed.success ? parsed.data : undefined;\n};\n\n/**\n * Saved views of one observability page, stored by the host's `SavedViewsSource`. The page URL is\n * the draft: picking a view writes its state to the URL (and its columns to the column picker), and\n * the view shows unsaved changes while the URL differs from it.\n */\nexport function useHostedSavedViews({\n page,\n source,\n queryKeyScope,\n searchParams,\n setSearchParams,\n columns,\n defaultDatePreset,\n canEdit = () => true,\n}: UseHostedSavedViewsArgs): SavedViewsController<HostedSavedViewSettings> {\n const queryClient = useQueryClient();\n const queryKey = savedViewsQueryKey(queryKeyScope, page);\n const { data: records = [] } = useQuery({ queryKey, queryFn: () => source.list(page) });\n\n // Nothing picked yet: the view matching the URL (e.g. after a reload) is active.\n // `{ id: undefined }`: the page default was picked.\n const [picked, setPicked] = useState<Picked>();\n const [creatingName, setCreatingName] = useState<string>();\n const [error, setError] = useState<string>();\n const [pending, setPending] = useState(false);\n // Bumped whenever the user picks or leaves a view, so a request that settles afterwards does not\n // switch the page back to the view it created.\n const choiceRef = useRef(0);\n // The search params a view was applied over. A host can render the new URL a render after the\n // navigation that set it (a data router does), so until it does the old URL says nothing about\n // whether the view has unsaved changes.\n const [appliedOver, setAppliedOver] = useState<URLSearchParams>();\n\n const current = savedViewFromSearchParams(page, searchParams, { columns: columns?.preferences });\n\n // A view as the page would hold it once applied, so a view saved by an older page (or with stale\n // parts) still compares equal to the URL it sets.\n const matchesUrl = (state: SavedViewState) => {\n const applied = applySavedView(state, new URLSearchParams(), { defaultDatePreset });\n const normalized = savedViewFromSearchParams(page, applied.params, { columns: applied.columns });\n const withColumns = applied.columns !== undefined;\n return comparable(normalized, withColumns) === comparable(current, withColumns);\n };\n\n const activeRecord = picked\n ? records.find(record => record.id === picked.id)\n : records.find(record => matchesUrl(record.state));\n const activeMatches = activeRecord !== undefined && matchesUrl(activeRecord.state);\n const awaitingUrl = appliedOver !== undefined && appliedOver === searchParams && !activeMatches;\n if (appliedOver !== undefined && !awaitingUrl) setAppliedOver(undefined);\n const activeUnsaved = activeRecord !== undefined && !activeMatches && !awaitingUrl;\n\n const toView = (record: SavedViewRecord): SavedView<HostedSavedViewSettings> => ({\n id: record.id,\n name: record.name,\n filters: summaryFilters(record.state),\n settings: { state: record.state, record },\n });\n const views = records.map(toView);\n const activeView = activeRecord ? toView(activeRecord) : undefined;\n\n const draft = draftOf(current, creatingName, activeUnsaved ? activeRecord : undefined);\n\n const applyState = (state: SavedViewState) => {\n const applied = applySavedView(state, searchParams, { defaultDatePreset });\n setAppliedOver(searchParams);\n setSearchParams(previous => applySavedView(state, previous, { defaultDatePreset }).params);\n if (applied.columns && columns) columns.setPreferences(applied.columns);\n };\n\n const storeRecord = (record: SavedViewRecord) =>\n queryClient.setQueryData<SavedViewRecord[]>(queryKey, (list = []) =>\n list.some(item => item.id === record.id)\n ? list.map(item => (item.id === record.id ? record : item))\n : [record, ...list],\n );\n\n const run = async <T>(task: () => Promise<T>, onError: (error: unknown) => string): Promise<T | undefined> => {\n setPending(true);\n setError(undefined);\n try {\n return await task();\n } catch (cause) {\n setError(onError(cause));\n return undefined;\n } finally {\n setPending(false);\n void queryClient.invalidateQueries({ queryKey });\n }\n };\n\n const fitsSizeLimit = (state: SavedViewState) => {\n if (stateBytes(state) <= SAVED_VIEW_STATE_MAX_BYTES) return true;\n setError(SAVED_VIEW_TOO_LARGE_MESSAGE);\n return false;\n };\n\n const select = (viewId: string | undefined) => {\n choiceRef.current += 1;\n setCreatingName(undefined);\n setError(undefined);\n if (viewId === undefined) {\n setPicked({ id: undefined });\n applyState({ schemaVersion: 1, page, filters: [] });\n columns?.resetColumns();\n return;\n }\n const record = records.find(item => item.id === viewId);\n if (!record) return;\n setPicked({ id: record.id });\n applyState(record.state);\n };\n\n return {\n views,\n activeView,\n draft,\n unsaved: creatingName !== undefined || activeUnsaved,\n error,\n pending,\n applied: draft ?? activeView,\n select,\n create: () => {\n setError(undefined);\n setCreatingName(NEW_VIEW_NAME);\n },\n edit: viewId => {\n if (viewId !== activeRecord?.id) select(viewId);\n },\n change: ({ name }) => {\n if (name === undefined || creatingName === undefined) return;\n setError(undefined);\n setCreatingName(name);\n },\n save: async () => {\n if (pending || !fitsSizeLimit(current)) return;\n if (creatingName !== undefined) {\n const name = validName(creatingName) ?? NEW_VIEW_NAME;\n const choice = choiceRef.current;\n const record = await run(\n () => source.create({ name, state: current }),\n cause => errorMessage(cause, name),\n );\n if (!record) return;\n storeRecord(record);\n if (choice !== choiceRef.current) return;\n setCreatingName(undefined);\n setPicked({ id: record.id });\n return;\n }\n if (!activeRecord || !activeUnsaved || !canEdit(activeRecord)) return;\n const { id, name } = activeRecord;\n const record = await run(\n () => source.update(id, { state: current }),\n cause => errorMessage(cause, name),\n );\n if (record) storeRecord(record);\n },\n discard: () => {\n choiceRef.current += 1;\n setError(undefined);\n if (creatingName !== undefined) setCreatingName(undefined);\n else if (activeRecord) applyState(activeRecord.state);\n },\n rename: async (viewId, rawName) => {\n const name = validName(rawName);\n const record = records.find(item => item.id === viewId);\n if (!name || !record || pending || !canEdit(record)) return false;\n if (name === record.name) return true;\n const renamed = await run(\n () => source.update(viewId, { name }),\n cause => errorMessage(cause, name),\n );\n if (!renamed) return false;\n storeRecord(renamed);\n return true;\n },\n duplicate: async viewId => {\n const original = records.find(item => item.id === viewId);\n if (!original || pending || !fitsSizeLimit(original.state)) return;\n const name = validName(`${original.name} copy`) ?? original.name;\n const choice = choiceRef.current;\n const copy = await run(\n () => source.create({ name, state: original.state }),\n cause => errorMessage(cause, name),\n );\n if (!copy) return;\n storeRecord(copy);\n if (choice !== choiceRef.current) return;\n setCreatingName(undefined);\n setPicked({ id: copy.id });\n applyState(copy.state);\n },\n remove: async viewId => {\n const record = records.find(item => item.id === viewId);\n if (!record || pending || !canEdit(record)) return;\n choiceRef.current += 1;\n const removed = await run(\n async () => {\n await source.remove(viewId);\n return true;\n },\n () => DELETE_FAILED_MESSAGE,\n );\n if (!removed) return;\n queryClient.setQueryData<SavedViewRecord[]>(queryKey, (list = []) => list.filter(item => item.id !== viewId));\n // The page keeps its filters; the default tab is picked so the deleted view is not re-matched.\n if (activeRecord?.id === viewId) setPicked({ id: undefined });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-hosted-saved-views.cjs.js","names":["SavedViewNameTakenError","savedViewNameSchema","useQueryClient","savedViewsQueryKey","useQuery","useState","useRef","savedViewFromSearchParams","applySavedView"],"sources":["../../../../src/domains/observability/saved-views/use-hosted-saved-views.ts"],"sourcesContent":["import type { SavedViewPage, SavedViewState } from '@mastra/core/storage';\nimport { useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useRef, useState } from 'react';\nimport { applySavedView, savedViewFromSearchParams } from '../saved-view-state';\nimport { SavedViewNameTakenError, savedViewsQueryKey } from './saved-views-source';\nimport type { SavedViewRecord, SavedViewsContextValue } from './saved-views-source';\nimport type { TraceColumnPreferences } from '@/domains/traces/trace-list-columns';\nimport type { FilterBarItem } from '@/ds/components/FilterBar/types';\nimport { savedViewNameSchema } from '@/ds/components/SavedViews/saved-view-schema';\nimport type { SavedView } from '@/ds/components/SavedViews/saved-view-schema';\nimport type { SavedViewDraft, SavedViewsController } from '@/ds/components/SavedViews/use-saved-views';\nimport type { SetURLSearchParamsLike } from '@/lib/sort/use-url-sort';\n\n/** Same limit as the saved view schema in `@mastra/core`, checked before a request is made. */\nexport const SAVED_VIEW_STATE_MAX_BYTES = 16 * 1024;\n\nconst NEW_VIEW_NAME = 'Untitled view';\nexport const SAVED_VIEW_TOO_LARGE_MESSAGE =\n 'This view is too large to save (16 KiB max). Remove some filters or columns.';\nconst SAVE_FAILED_MESSAGE = 'Could not save the view. Try again.';\nconst DELETE_FAILED_MESSAGE = 'Could not delete the view. Try again.';\n\n/** A view's state, and for a stored view its record. */\nexport type HostedSavedViewSettings = { state: SavedViewState; record?: SavedViewRecord };\n\nexport type SavedViewColumnsControl = {\n preferences: TraceColumnPreferences;\n setPreferences: (next: TraceColumnPreferences) => void;\n /** Back to the page's default columns, when the page default is picked. */\n resetColumns: () => void;\n};\n\nexport type UseHostedSavedViewsArgs = SavedViewsContextValue & {\n page: SavedViewPage;\n searchParams: URLSearchParams;\n setSearchParams: SetURLSearchParamsLike;\n /** The page's column picker; omitted on pages with fixed columns. */\n columns?: SavedViewColumnsControl;\n /** The range the page shows when the URL names none, when the host's differs from the page's own. */\n defaultDatePreset?: string;\n};\n\nconst stateBytes = (state: SavedViewState) => new TextEncoder().encode(JSON.stringify(state)).length;\n\n/** The filters a view's hover summary lists: its top-level filters (groups are counted separately). */\nfunction summaryFilters(state: SavedViewState): FilterBarItem[] {\n return state.filters.flatMap((node, index) =>\n 'nodes' in node\n ? []\n : [\n {\n id: `${index}`,\n fieldId: node.fieldId,\n operatorId: node.operatorId ?? (Array.isArray(node.value) ? 'in' : 'is'),\n value: node.value,\n },\n ],\n );\n}\n\n/** The parts of a view that compare equal when they set the same URL and columns. */\nfunction comparable(state: SavedViewState, withColumns: boolean): string {\n const { filters, dateRange, sort, options, columns } = state;\n return JSON.stringify({ filters, dateRange, sort, options, columns: withColumns ? columns : undefined });\n}\n\nfunction errorMessage(error: unknown, name: string): string {\n return error instanceof SavedViewNameTakenError ? `A view named \"${name}\" already exists.` : SAVE_FAILED_MESSAGE;\n}\n\n/** What the user picked: a saved view, or the page default when `id` is undefined. */\ntype Picked = { id: string | undefined };\n\n/** The unsaved state shown in the bar: a view being created, or edits to the active view. */\nfunction draftOf(\n current: SavedViewState,\n creatingName: string | undefined,\n editedRecord: SavedViewRecord | undefined,\n): SavedViewDraft<HostedSavedViewSettings> | undefined {\n const unsaved = { filters: summaryFilters(current), settings: { state: current } };\n if (creatingName !== undefined) return { name: creatingName, ...unsaved };\n if (editedRecord) return { viewId: editedRecord.id, name: editedRecord.name, ...unsaved };\n return undefined;\n}\n\nconst validName = (name: string) => {\n const parsed = savedViewNameSchema.safeParse(name);\n return parsed.success ? parsed.data : undefined;\n};\n\n/**\n * Saved views of one observability page, stored by the host's `SavedViewsSource`. The page URL is\n * the draft: picking a view writes its state to the URL (and its columns to the column picker), and\n * the view shows unsaved changes while the URL differs from it.\n */\nexport function useHostedSavedViews({\n page,\n source,\n queryKeyScope,\n searchParams,\n setSearchParams,\n columns,\n defaultDatePreset,\n canEdit = () => true,\n canCreate = true,\n}: UseHostedSavedViewsArgs): SavedViewsController<HostedSavedViewSettings> {\n const queryClient = useQueryClient();\n const queryKey = savedViewsQueryKey(queryKeyScope, page);\n const { data: records = [] } = useQuery({ queryKey, queryFn: () => source.list(page) });\n\n // Nothing picked yet: the view matching the URL (e.g. after a reload) is active.\n // `{ id: undefined }`: the page default was picked.\n const [picked, setPicked] = useState<Picked>();\n const [creatingName, setCreatingName] = useState<string>();\n const [error, setError] = useState<string>();\n const [pending, setPending] = useState(false);\n // Bumped whenever the user picks or leaves a view, so a request that settles afterwards does not\n // switch the page back to the view it created.\n const choiceRef = useRef(0);\n // The search params a view was applied over. A host can render the new URL a render after the\n // navigation that set it (a data router does), so until it does the old URL says nothing about\n // whether the view has unsaved changes.\n const [appliedOver, setAppliedOver] = useState<URLSearchParams>();\n\n const current = savedViewFromSearchParams(page, searchParams, { columns: columns?.preferences });\n\n // A view as the page would hold it once applied, so a view saved by an older page (or with stale\n // parts) still compares equal to the URL it sets.\n const matchesUrl = (state: SavedViewState) => {\n const applied = applySavedView(state, new URLSearchParams(), { defaultDatePreset });\n const normalized = savedViewFromSearchParams(page, applied.params, { columns: applied.columns });\n const withColumns = applied.columns !== undefined;\n return comparable(normalized, withColumns) === comparable(current, withColumns);\n };\n\n const activeRecord = picked\n ? records.find(record => record.id === picked.id)\n : records.find(record => matchesUrl(record.state));\n const activeMatches = activeRecord !== undefined && matchesUrl(activeRecord.state);\n const awaitingUrl = appliedOver !== undefined && appliedOver === searchParams && !activeMatches;\n if (appliedOver !== undefined && !awaitingUrl) setAppliedOver(undefined);\n const activeUnsaved = activeRecord !== undefined && !activeMatches && !awaitingUrl;\n\n const toView = (record: SavedViewRecord): SavedView<HostedSavedViewSettings> => ({\n id: record.id,\n name: record.name,\n filters: summaryFilters(record.state),\n settings: { state: record.state, record },\n });\n const views = records.map(toView);\n const activeView = activeRecord ? toView(activeRecord) : undefined;\n\n const draft = draftOf(current, creatingName, activeUnsaved ? activeRecord : undefined);\n\n const applyState = (state: SavedViewState) => {\n const applied = applySavedView(state, searchParams, { defaultDatePreset });\n setAppliedOver(searchParams);\n setSearchParams(previous => applySavedView(state, previous, { defaultDatePreset }).params);\n if (applied.columns && columns) columns.setPreferences(applied.columns);\n };\n\n const storeRecord = (record: SavedViewRecord) =>\n queryClient.setQueryData<SavedViewRecord[]>(queryKey, (list = []) =>\n list.some(item => item.id === record.id)\n ? list.map(item => (item.id === record.id ? record : item))\n : [record, ...list],\n );\n\n const run = async <T>(task: () => Promise<T>, onError: (error: unknown) => string): Promise<T | undefined> => {\n setPending(true);\n setError(undefined);\n try {\n return await task();\n } catch (cause) {\n setError(onError(cause));\n return undefined;\n } finally {\n setPending(false);\n void queryClient.invalidateQueries({ queryKey });\n }\n };\n\n const fitsSizeLimit = (state: SavedViewState) => {\n if (stateBytes(state) <= SAVED_VIEW_STATE_MAX_BYTES) return true;\n setError(SAVED_VIEW_TOO_LARGE_MESSAGE);\n return false;\n };\n\n const select = (viewId: string | undefined) => {\n choiceRef.current += 1;\n setCreatingName(undefined);\n setError(undefined);\n if (viewId === undefined) {\n setPicked({ id: undefined });\n applyState({ schemaVersion: 1, page, filters: [] });\n columns?.resetColumns();\n return;\n }\n const record = records.find(item => item.id === viewId);\n if (!record) return;\n setPicked({ id: record.id });\n applyState(record.state);\n };\n\n return {\n views,\n activeView,\n draft,\n unsaved: creatingName !== undefined || activeUnsaved,\n error,\n pending,\n applied: draft ?? activeView,\n select,\n create: () => {\n if (!canCreate) return;\n setError(undefined);\n setCreatingName(NEW_VIEW_NAME);\n },\n edit: viewId => {\n if (viewId !== activeRecord?.id) select(viewId);\n },\n change: ({ name }) => {\n if (name === undefined || creatingName === undefined) return;\n setError(undefined);\n setCreatingName(name);\n },\n save: async () => {\n if (pending || !fitsSizeLimit(current)) return;\n if (creatingName !== undefined) {\n const name = validName(creatingName) ?? NEW_VIEW_NAME;\n const choice = choiceRef.current;\n const record = await run(\n () => source.create({ name, state: current }),\n cause => errorMessage(cause, name),\n );\n if (!record) return;\n storeRecord(record);\n if (choice !== choiceRef.current) return;\n setCreatingName(undefined);\n setPicked({ id: record.id });\n return;\n }\n if (!activeRecord || !activeUnsaved || !canEdit(activeRecord)) return;\n const { id, name } = activeRecord;\n const record = await run(\n () => source.update(id, { state: current }),\n cause => errorMessage(cause, name),\n );\n if (record) storeRecord(record);\n },\n discard: () => {\n choiceRef.current += 1;\n setError(undefined);\n if (creatingName !== undefined) setCreatingName(undefined);\n else if (activeRecord) applyState(activeRecord.state);\n },\n rename: async (viewId, rawName) => {\n const name = validName(rawName);\n const record = records.find(item => item.id === viewId);\n if (!name || !record || pending || !canEdit(record)) return false;\n if (name === record.name) return true;\n const renamed = await run(\n () => source.update(viewId, { name }),\n cause => errorMessage(cause, name),\n );\n if (!renamed) return false;\n storeRecord(renamed);\n return true;\n },\n duplicate: async viewId => {\n const original = records.find(item => item.id === viewId);\n if (!canCreate || !original || pending || !fitsSizeLimit(original.state)) return;\n const name = validName(`${original.name} copy`) ?? original.name;\n const choice = choiceRef.current;\n const copy = await run(\n () => source.create({ name, state: original.state }),\n cause => errorMessage(cause, name),\n );\n if (!copy) return;\n storeRecord(copy);\n if (choice !== choiceRef.current) return;\n setCreatingName(undefined);\n setPicked({ id: copy.id });\n applyState(copy.state);\n },\n remove: async viewId => {\n const record = records.find(item => item.id === viewId);\n if (!record || pending || !canEdit(record)) return;\n choiceRef.current += 1;\n const removed = await run(\n async () => {\n await source.remove(viewId);\n return true;\n },\n () => DELETE_FAILED_MESSAGE,\n );\n if (!removed) return;\n queryClient.setQueryData<SavedViewRecord[]>(queryKey, (list = []) => list.filter(item => item.id !== viewId));\n // The page keeps its filters; the default tab is picked so the deleted view is not re-matched.\n if (activeRecord?.id === viewId) setPicked({ id: undefined });\n },\n };\n}\n"],"mappings":"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"}