effect-start 0.31.0 → 0.33.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 (227) hide show
  1. package/dist/Entity.d.ts +3 -0
  2. package/dist/Entity.d.ts.map +1 -1
  3. package/dist/Entity.js +20 -5
  4. package/dist/Entity.js.map +1 -1
  5. package/dist/Fetch.d.ts +1 -1
  6. package/dist/Fetch.d.ts.map +1 -1
  7. package/dist/Fetch.js +1 -1
  8. package/dist/Fetch.js.map +1 -1
  9. package/dist/Html.d.ts +5 -11
  10. package/dist/Html.d.ts.map +1 -1
  11. package/dist/Html.js +21 -1
  12. package/dist/Html.js.map +1 -1
  13. package/dist/KeyValueStore.d.ts +37 -0
  14. package/dist/KeyValueStore.d.ts.map +1 -0
  15. package/dist/KeyValueStore.js +99 -0
  16. package/dist/KeyValueStore.js.map +1 -0
  17. package/dist/Password.d.ts +26 -0
  18. package/dist/Password.d.ts.map +1 -0
  19. package/dist/Password.js +83 -0
  20. package/dist/Password.js.map +1 -0
  21. package/dist/Route.d.ts +2 -2
  22. package/dist/Route.d.ts.map +1 -1
  23. package/dist/Route.js +1 -1
  24. package/dist/Route.js.map +1 -1
  25. package/dist/RouteBody.d.ts +2 -2
  26. package/dist/RouteBody.d.ts.map +1 -1
  27. package/dist/RouteMount.d.ts +0 -20
  28. package/dist/RouteMount.d.ts.map +1 -1
  29. package/dist/RouteMount.js +0 -15
  30. package/dist/RouteMount.js.map +1 -1
  31. package/dist/RouteSse.d.ts +1 -2
  32. package/dist/RouteSse.d.ts.map +1 -1
  33. package/dist/RouteSse.js +2 -2
  34. package/dist/RouteSse.js.map +1 -1
  35. package/dist/Start.d.ts.map +1 -1
  36. package/dist/Start.js +4 -0
  37. package/dist/Start.js.map +1 -1
  38. package/dist/_StreamExtra.d.ts.map +1 -1
  39. package/dist/_StreamExtra.js +0 -1
  40. package/dist/_StreamExtra.js.map +1 -1
  41. package/dist/bun/BunRoute.d.ts.map +1 -1
  42. package/dist/bun/BunRoute.js +90 -78
  43. package/dist/bun/BunRoute.js.map +1 -1
  44. package/dist/bun/BunServer.d.ts +1 -1
  45. package/dist/bun/BunServer.d.ts.map +1 -1
  46. package/dist/bun/BunServer.js +8 -1
  47. package/dist/bun/BunServer.js.map +1 -1
  48. package/dist/datastar/attributes/computed.js +3 -3
  49. package/dist/datastar/attributes/computed.js.map +1 -1
  50. package/dist/datastar/attributes/on.js +11 -36
  51. package/dist/datastar/attributes/on.js.map +1 -1
  52. package/dist/datastar/engine.d.ts +9 -7
  53. package/dist/datastar/engine.d.ts.map +1 -1
  54. package/dist/datastar/engine.js +45 -29
  55. package/dist/datastar/engine.js.map +1 -1
  56. package/dist/datastar/jsx.d.ts +70 -0
  57. package/dist/datastar/jsx.d.ts.map +1 -0
  58. package/dist/datastar/jsx.js +2 -0
  59. package/dist/datastar/jsx.js.map +1 -0
  60. package/dist/datastar/window.d.ts +8 -0
  61. package/dist/datastar/window.d.ts.map +1 -0
  62. package/dist/datastar/window.js +4 -0
  63. package/dist/datastar/window.js.map +1 -0
  64. package/dist/experimental/KeyValueStore.d.ts +37 -0
  65. package/dist/experimental/KeyValueStore.d.ts.map +1 -0
  66. package/dist/experimental/KeyValueStore.js +99 -0
  67. package/dist/experimental/KeyValueStore.js.map +1 -0
  68. package/dist/experimental/SqlCache.d.ts +19 -0
  69. package/dist/experimental/SqlCache.d.ts.map +1 -0
  70. package/dist/experimental/SqlCache.js +35 -0
  71. package/dist/experimental/SqlCache.js.map +1 -0
  72. package/dist/experimental/SqlIntrospect.d.ts +92 -0
  73. package/dist/experimental/SqlIntrospect.d.ts.map +1 -0
  74. package/dist/experimental/SqlIntrospect.js +478 -0
  75. package/dist/experimental/SqlIntrospect.js.map +1 -0
  76. package/dist/index.d.ts +1 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +1 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/jsx-runtime.d.ts +2 -2
  81. package/dist/jsx-runtime.d.ts.map +1 -1
  82. package/dist/jsx.d.ts +3216 -0
  83. package/dist/jsx.d.ts.map +1 -0
  84. package/dist/jsx.js +6 -0
  85. package/dist/jsx.js.map +1 -0
  86. package/dist/lint/plugin.d.ts +4 -3
  87. package/dist/lint/plugin.js +56 -17
  88. package/dist/lint/plugin.js.map +1 -1
  89. package/dist/sql/index.d.ts +0 -2
  90. package/dist/sql/index.d.ts.map +1 -1
  91. package/dist/sql/index.js +0 -2
  92. package/dist/sql/index.js.map +1 -1
  93. package/dist/studio/Studio.d.ts +1 -1
  94. package/dist/studio/Studio.d.ts.map +1 -1
  95. package/dist/studio/Studio.js +5 -4
  96. package/dist/studio/Studio.js.map +1 -1
  97. package/dist/studio/StudioErrors.d.ts.map +1 -1
  98. package/dist/studio/StudioErrors.js +2 -2
  99. package/dist/studio/StudioErrors.js.map +1 -1
  100. package/dist/studio/StudioLogger.js +3 -4
  101. package/dist/studio/StudioLogger.js.map +1 -1
  102. package/dist/studio/StudioStore.d.ts +26 -17
  103. package/dist/studio/StudioStore.d.ts.map +1 -1
  104. package/dist/studio/StudioStore.js +61 -44
  105. package/dist/studio/StudioStore.js.map +1 -1
  106. package/dist/studio/StudioTracer.d.ts.map +1 -1
  107. package/dist/studio/StudioTracer.js +10 -4
  108. package/dist/studio/StudioTracer.js.map +1 -1
  109. package/dist/studio/_Pretty.d.ts +4 -0
  110. package/dist/studio/_Pretty.d.ts.map +1 -0
  111. package/dist/studio/_Pretty.js +56 -0
  112. package/dist/studio/_Pretty.js.map +1 -0
  113. package/dist/studio/routes/errors/route.d.ts +1 -1
  114. package/dist/studio/routes/errors/route.d.ts.map +1 -1
  115. package/dist/studio/routes/errors/route.js +2 -2
  116. package/dist/studio/routes/errors/route.js.map +1 -1
  117. package/dist/studio/routes/fiberDetail.d.ts +1 -1
  118. package/dist/studio/routes/fiberDetail.js +4 -4
  119. package/dist/studio/routes/fiberDetail.js.map +1 -1
  120. package/dist/studio/routes/fibers/route.d.ts +2 -2
  121. package/dist/studio/routes/fibers/route.js +4 -4
  122. package/dist/studio/routes/fibers/route.js.map +1 -1
  123. package/dist/studio/routes/layout.d.ts +2 -0
  124. package/dist/studio/routes/layout.d.ts.map +1 -1
  125. package/dist/studio/routes/layout.html +3 -12
  126. package/dist/studio/routes/layout.js +6 -1
  127. package/dist/studio/routes/layout.js.map +1 -1
  128. package/dist/studio/routes/logs/route.d.ts +1 -1
  129. package/dist/studio/routes/logs/route.d.ts.map +1 -1
  130. package/dist/studio/routes/logs/route.js +2 -2
  131. package/dist/studio/routes/logs/route.js.map +1 -1
  132. package/dist/studio/routes/traceDetail.d.ts +1 -1
  133. package/dist/studio/routes/traceDetail.js +1 -1
  134. package/dist/studio/routes/traceDetail.js.map +1 -1
  135. package/dist/studio/routes/traces/route.d.ts +2 -2
  136. package/dist/studio/routes/traces/route.d.ts.map +1 -1
  137. package/dist/studio/routes/traces/route.js +9 -6
  138. package/dist/studio/routes/traces/route.js.map +1 -1
  139. package/dist/studio/routes/tree.d.ts +30 -8
  140. package/dist/studio/routes/tree.d.ts.map +1 -1
  141. package/dist/studio/ui/Errors.d.ts +1 -1
  142. package/dist/studio/ui/Errors.js +1 -1
  143. package/dist/studio/ui/Errors.js.map +1 -1
  144. package/dist/studio/ui/Fibers.d.ts +2 -2
  145. package/dist/studio/ui/Fibers.d.ts.map +1 -1
  146. package/dist/studio/ui/Fibers.js +4 -3
  147. package/dist/studio/ui/Fibers.js.map +1 -1
  148. package/dist/studio/ui/Logs.d.ts +1 -1
  149. package/dist/studio/ui/Logs.d.ts.map +1 -1
  150. package/dist/studio/ui/Logs.js +2 -1
  151. package/dist/studio/ui/Logs.js.map +1 -1
  152. package/dist/studio/ui/Metrics.d.ts +1 -1
  153. package/dist/studio/ui/Routes.d.ts +1 -1
  154. package/dist/studio/ui/Routes.d.ts.map +1 -1
  155. package/dist/studio/ui/Services.d.ts +1 -1
  156. package/dist/studio/ui/Services.d.ts.map +1 -1
  157. package/dist/studio/ui/Shell.d.ts +2 -2
  158. package/dist/studio/ui/Shell.d.ts.map +1 -1
  159. package/dist/studio/ui/System.d.ts +1 -1
  160. package/dist/studio/ui/Traces.d.ts +4 -3
  161. package/dist/studio/ui/Traces.d.ts.map +1 -1
  162. package/dist/studio/ui/Traces.js +36 -21
  163. package/dist/studio/ui/Traces.js.map +1 -1
  164. package/dist/studio/ui/_PrettyValue.d.ts +10 -0
  165. package/dist/studio/ui/_PrettyValue.d.ts.map +1 -0
  166. package/dist/studio/ui/_PrettyValue.js +27 -0
  167. package/dist/studio/ui/_PrettyValue.js.map +1 -0
  168. package/dist/tailwind/TailwindPlugin.d.ts.map +1 -1
  169. package/dist/tailwind/TailwindPlugin.js +89 -62
  170. package/dist/tailwind/TailwindPlugin.js.map +1 -1
  171. package/dist/ts/import-plugin.cjs +388 -0
  172. package/dist/ts/import-plugin.cjs.map +1 -0
  173. package/dist/ts/import-plugin.d.cts +87 -0
  174. package/dist/ts/import-plugin.d.cts.map +1 -0
  175. package/dist/ts/import-plugin.d.ts +87 -0
  176. package/dist/ts/import-plugin.d.ts.map +1 -0
  177. package/dist/ts/import-plugin.js +390 -0
  178. package/dist/ts/import-plugin.js.map +1 -0
  179. package/package.json +109 -8
  180. package/src/Entity.ts +32 -7
  181. package/src/Fetch.ts +2 -2
  182. package/src/Html.ts +28 -21
  183. package/src/Password.ts +130 -0
  184. package/src/Route.ts +2 -2
  185. package/src/RouteBody.ts +2 -2
  186. package/src/RouteMount.ts +0 -54
  187. package/src/RouteSse.ts +4 -4
  188. package/src/Start.ts +4 -0
  189. package/src/_StreamExtra.ts +0 -1
  190. package/src/bun/BunRoute.ts +117 -95
  191. package/src/bun/BunServer.ts +9 -2
  192. package/src/datastar/README.md +24 -8
  193. package/src/datastar/attributes/computed.ts +3 -3
  194. package/src/datastar/attributes/on.ts +11 -37
  195. package/src/datastar/engine.ts +61 -37
  196. package/src/datastar/jsx.d.ts +12 -26
  197. package/src/datastar/types.d.ts +8 -0
  198. package/src/experimental/KeyValueStore.ts +161 -0
  199. package/src/{sql → experimental}/SqlCache.ts +1 -1
  200. package/src/{sql → experimental}/SqlIntrospect.ts +1 -1
  201. package/src/index.ts +1 -0
  202. package/src/jsx-runtime.ts +1 -1
  203. package/src/jsx.d.ts +17 -2
  204. package/src/lint/plugin.js +54 -19
  205. package/src/sql/index.ts +0 -2
  206. package/src/studio/Studio.ts +6 -5
  207. package/src/studio/StudioErrors.ts +2 -3
  208. package/src/studio/StudioLogger.ts +4 -4
  209. package/src/studio/StudioStore.ts +177 -115
  210. package/src/studio/StudioTracer.ts +11 -5
  211. package/src/studio/_Pretty.ts +59 -0
  212. package/src/studio/routes/errors/route.tsx +3 -1
  213. package/src/studio/routes/fiberDetail.tsx +4 -4
  214. package/src/studio/routes/fibers/route.tsx +4 -4
  215. package/src/studio/routes/layout.html +3 -12
  216. package/src/studio/routes/layout.tsx +9 -1
  217. package/src/studio/routes/logs/route.tsx +3 -1
  218. package/src/studio/routes/traceDetail.tsx +1 -1
  219. package/src/studio/routes/traces/route.tsx +13 -6
  220. package/src/studio/ui/Errors.tsx +1 -1
  221. package/src/studio/ui/Fibers.tsx +14 -10
  222. package/src/studio/ui/Logs.tsx +15 -10
  223. package/src/studio/ui/Traces.tsx +80 -80
  224. package/src/studio/ui/_PrettyValue.tsx +34 -0
  225. package/src/tailwind/TailwindPlugin.ts +102 -75
  226. package/src/RouteTrie.ts +0 -205
  227. package/src/experimental/index.ts +0 -1
@@ -12,7 +12,7 @@ export default Route.get(
12
12
  const url = new URL(ctx.request.url)
13
13
  const search = url.searchParams.get("errorSearch") || ""
14
14
  const tag = url.searchParams.get("errorTag") || ""
15
- const allErrors = yield* StudioStore.allErrors(StudioStore.store.sql)
15
+ const allErrors = yield* StudioStore.allErrors()
16
16
  const tagSet = new Set<string>()
17
17
  for (const error of allErrors) {
18
18
  for (const d of error.details) {
@@ -67,6 +67,8 @@ export default Route.get(
67
67
  <Errors.ErrorLine error={e} />
68
68
  ))}
69
69
  </div>
70
+
71
+ <div data-init={`@get('${prefix}/errors')`} />
70
72
  </form>
71
73
  </Shell.Shell>
72
74
  )
@@ -12,8 +12,8 @@ export default Route.get(
12
12
  const fiberId = ctx.pathParams.id
13
13
  const fiberName = fiberId.startsWith("#") ? fiberId : `#${fiberId}`
14
14
 
15
- const fiberLogs = yield* StudioStore.logsByFiberId(StudioStore.store.sql, fiberName)
16
- const fiberSpans = yield* StudioStore.spansByFiberId(StudioStore.store.sql, fiberName)
15
+ const fiberLogs = yield* StudioStore.logsByFiberId(fiberName)
16
+ const fiberSpans = yield* StudioStore.spansByFiberId(fiberName)
17
17
 
18
18
  const fiberNum = parseInt(fiberName.slice(1), 10)
19
19
  const counter = StudioStore.fiberIdCounter()
@@ -30,8 +30,8 @@ export default Route.get(
30
30
  else if (fiberNum < counter) alive = "dead"
31
31
  }
32
32
 
33
- const parents = yield* StudioStore.getParentChain(StudioStore.store.sql, fiberName)
34
- const fiberContext = yield* StudioStore.getFiberContext(StudioStore.store.sql, fiberName)
33
+ const parents = yield* StudioStore.getParentChain(fiberName)
34
+ const fiberContext = yield* StudioStore.getFiberContext(fiberName)
35
35
 
36
36
  return (
37
37
  <Shell.Shell prefix={StudioStore.store.prefix} active="fibers">
@@ -8,8 +8,8 @@ import * as Shell from "../../ui/Shell.tsx"
8
8
 
9
9
  export default Route.get(
10
10
  Route.html(function* () {
11
- const logs = yield* StudioStore.allLogs(StudioStore.store.sql)
12
- const spans = yield* StudioStore.allSpans(StudioStore.store.sql)
11
+ const logs = yield* StudioStore.allLogs()
12
+ const spans = yield* StudioStore.allSpans()
13
13
  const fibers = Fibers.collectFibers(logs, spans)
14
14
  return (
15
15
  <Shell.Shell prefix={StudioStore.store.prefix} active="fibers">
@@ -28,8 +28,8 @@ export default Route.get(
28
28
  Stream.filter((e) => e._tag === "SpanStart" || e._tag === "SpanEnd" || e._tag === "Log"),
29
29
  Stream.mapEffect(() =>
30
30
  Effect.gen(function* () {
31
- const logs = yield* StudioStore.allLogs(StudioStore.store.sql)
32
- const spans = yield* StudioStore.allSpans(StudioStore.store.sql)
31
+ const logs = yield* StudioStore.allLogs()
32
+ const spans = yield* StudioStore.allSpans()
33
33
  const fibers = Fibers.collectFibers(logs, spans)
34
34
  const html = Html.renderToString(
35
35
  <Fibers.FiberList fibers={fibers} prefix={StudioStore.store.prefix} />,
@@ -261,21 +261,12 @@
261
261
  }
262
262
  .tl-row {
263
263
  border-bottom: 1px solid #1e293b;
264
+ color: inherit;
265
+ text-decoration: none;
264
266
  }
265
- .tl-row[open] {
267
+ .tl-row:hover {
266
268
  background: #111827;
267
269
  }
268
- .tl-summary {
269
- cursor: pointer;
270
- user-select: none;
271
- list-style: none;
272
- }
273
- .tl-summary::-webkit-details-marker {
274
- display: none;
275
- }
276
- .tl-summary:hover {
277
- background: #1e293b;
278
- }
279
270
  .tl-cell {
280
271
  padding: 6px 8px;
281
272
  display: flex;
@@ -1,4 +1,12 @@
1
+ import * as Effect from "effect/Effect"
1
2
  import * as BunRoute from "../../bun/BunRoute.ts"
2
3
  import * as Route from "../../Route.ts"
4
+ import * as StudioStore from "../StudioStore.ts"
3
5
 
4
- export default Route.use(BunRoute.htmlBundle(() => import("./layout.html")))
6
+ export default Route.use(
7
+ Route.filter(function* () {
8
+ yield* Effect.annotateCurrentSpan(StudioStore.studioTraceAttribute, true)
9
+ return { context: {} }
10
+ }),
11
+ BunRoute.htmlBundle(() => import("./layout.html")),
12
+ )
@@ -12,7 +12,7 @@ export default Route.get(
12
12
  const url = new URL(ctx.request.url)
13
13
  const level = url.searchParams.get("logLevel") || ""
14
14
  const search = url.searchParams.get("logSearch") || ""
15
- let logs = yield* StudioStore.allLogs(StudioStore.store.sql)
15
+ let logs = yield* StudioStore.allLogs()
16
16
  if (level) logs = logs.filter((l) => l.level === level)
17
17
  if (search) {
18
18
  const lower = search.toLowerCase()
@@ -53,6 +53,8 @@ export default Route.get(
53
53
  <Logs.LogLine log={l} />
54
54
  ))}
55
55
  </div>
56
+
57
+ <div data-init={`@get('${prefix}/logs')`} />
56
58
  </form>
57
59
  </Shell.Shell>
58
60
  )
@@ -18,7 +18,7 @@ export default Route.get(
18
18
  </Shell.Shell>
19
19
  )
20
20
  }
21
- const spans = yield* StudioStore.spansByTraceId(StudioStore.store.sql, traceId)
21
+ const spans = yield* StudioStore.spansByTraceId(traceId)
22
22
 
23
23
  return (
24
24
  <Shell.Shell prefix={StudioStore.store.prefix} active="traces">
@@ -12,7 +12,7 @@ export default Route.get(
12
12
  Route.html(function* (ctx) {
13
13
  const url = new URL(ctx.request.url)
14
14
  const search = url.searchParams.get("traceSearch") || ""
15
- const allSpans = yield* StudioStore.allSpans(StudioStore.store.sql)
15
+ const allSpans = StudioStore.filterOutStudioSpans(yield* StudioStore.allSpans())
16
16
  const names = Array.from(new Set(allSpans.map((s) => s.name))).sort()
17
17
  let spans = allSpans
18
18
  if (search) {
@@ -52,17 +52,24 @@ export default Route.get(
52
52
  }),
53
53
  Route.sse(
54
54
  Stream.fromPubSub(StudioStore.store.events).pipe(
55
- Stream.filter((e) => e._tag === "SpanStart" || e._tag === "SpanEnd"),
56
- Stream.mapEffect(() =>
55
+ Stream.filter((e) => e._tag === "TraceEnd"),
56
+ Stream.mapEffect((e) =>
57
57
  Effect.gen(function* () {
58
- const spans = yield* StudioStore.allSpans(StudioStore.store.sql)
59
- const html = Html.renderToString(<Traces.TraceGroups spans={spans} />).replace(/\n/g, "")
58
+ const traceSpans = yield* StudioStore.spansByTraceId(e.traceId)
59
+ if (StudioStore.isStudioTrace(traceSpans)) {
60
+ return undefined
61
+ }
62
+ const traceHtml = Html.renderToString(
63
+ <Traces.TraceGroup id={e.traceId} spans={traceSpans} />,
64
+ )
65
+
60
66
  return {
61
67
  event: "datastar-patch-elements",
62
- data: `selector #traces-container\nmode inner\nelements ${html}`,
68
+ data: `selector .tl-header\nmode after\nelements ${traceHtml}`,
63
69
  }
64
70
  }),
65
71
  ),
72
+ Stream.filter((event): event is { event: string; data: string } => event !== undefined),
66
73
  ),
67
74
  ),
68
75
  )
@@ -31,7 +31,7 @@ export function ErrorLine(options: { error: StudioStore.StudioError }) {
31
31
  <span style="color:#64748b">tag </span>
32
32
  <span
33
33
  style="color:#fca5a5;text-decoration:underline;cursor:copy"
34
- data-on:click={`(e) => { e.signals.errorTag.value = '${t}'; e.actions.get(location.href, { contentType: 'form' }) }`}
34
+ data-on:click={`(e) => { e.signals.errorTag = '${t}'; e.actions.get(location.href, { contentType: 'form' }) }`}
35
35
  >
36
36
  {t}
37
37
  </span>
@@ -1,6 +1,7 @@
1
1
  import * as Unique from "../../Unique.ts"
2
2
  import * as StudioStore from "../StudioStore.ts"
3
3
  import * as Logs from "./Logs.tsx"
4
+ import * as PrettyValue from "./_PrettyValue.tsx"
4
5
 
5
6
  function formatDuration(ms: number | undefined): string {
6
7
  if (ms == null) return "..."
@@ -9,14 +10,20 @@ function formatDuration(ms: number | undefined): string {
9
10
  return `${(ms / 1000).toFixed(2)}s`
10
11
  }
11
12
 
12
- function KeyValue(options: { label: string; value: string | number | bigint | undefined | null }) {
13
+ function KeyValue(options: { label: string; value: unknown }) {
13
14
  if (options.value == null) return null
14
15
  return (
15
- <div style="display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px">
16
+ <div
17
+ style="display:flex;align-items:flex-start;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px"
18
+ >
16
19
  <span style="color:#64748b;min-width:120px">{options.label}</span>
17
- <span style="color:#e2e8f0;font-family:monospace;word-break:break-all">
18
- {String(options.value)}
19
- </span>
20
+ <div style="flex:1;min-width:0">
21
+ <PrettyValue.PrettyValue
22
+ value={options.value}
23
+ style="color:#e2e8f0;font-family:monospace;word-break:break-all"
24
+ preStyle="color:#e2e8f0;font-family:monospace;word-break:break-all;white-space:pre-wrap;margin:0"
25
+ />
26
+ </div>
20
27
  </div>
21
28
  )
22
29
  }
@@ -276,10 +283,7 @@ export function FiberDetail(options: {
276
283
  </div>
277
284
  )}
278
285
  {Object.entries(options.context.annotations).map(([k, v]) => (
279
- <KeyValue
280
- label={k}
281
- value={typeof v === "object" ? JSON.stringify(v) : String(v)}
282
- />
286
+ <KeyValue label={k} value={v} />
283
287
  ))}
284
288
  </div>
285
289
  </div>
@@ -310,7 +314,7 @@ export function FiberDetail(options: {
310
314
  {Object.entries(s.attributes)
311
315
  .filter(([k]) => k !== "code.stacktrace")
312
316
  .map(([k, v]) => (
313
- <KeyValue label={k} value={String(v)} />
317
+ <KeyValue label={k} value={v} />
314
318
  ))}
315
319
  </div>
316
320
  )
@@ -1,5 +1,6 @@
1
1
  import * as Unique from "../../Unique.ts"
2
2
  import * as StudioStore from "../StudioStore.ts"
3
+ import * as PrettyValue from "./_PrettyValue.tsx"
3
4
 
4
5
  function levelColor(level: string): string {
5
6
  if (level === "DEBUG") return "#94a3b8"
@@ -25,26 +26,30 @@ export function LogLine(options: { log: StudioStore.StudioLog }) {
25
26
  return (
26
27
  <div
27
28
  id={`log-${options.log.id}`}
28
- style="padding:3px 8px;border-bottom:1px solid #1f2937;font-family:monospace;font-size:12px;display:flex;align-items:baseline"
29
+ style="padding:6px 8px;border-bottom:1px solid #1f2937;font-family:monospace;font-size:12px;display:flex;align-items:flex-start;gap:8px"
29
30
  >
30
31
  <span style="color:#6b7280;white-space:nowrap">{time}</span>
31
32
  <span style={`color:${color};font-weight:600;width:56px;text-align:center;flex-shrink:0`}>
32
33
  {options.log.level}
33
34
  </span>
34
- <span style="color:#e5e7eb;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis">
35
- {options.log.message}
36
- </span>
35
+ <div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:4px">
36
+ <PrettyValue.PreformattedText
37
+ text={options.log.message}
38
+ style="color:#e5e7eb;margin:0;white-space:pre-wrap;word-break:break-word;font:inherit"
39
+ />
40
+ {options.log.cause && (
41
+ <PrettyValue.PreformattedText
42
+ text={options.log.cause}
43
+ style="color:#ef4444;font-size:11px;margin:0;white-space:pre-wrap;word-break:break-word;font:inherit"
44
+ />
45
+ )}
46
+ </div>
37
47
  <a
38
48
  href={`${StudioStore.store.prefix}/fibers/${options.log.fiberId.replace("#", "")}`}
39
- style="color:#6b7280;white-space:nowrap;margin-left:8px;text-decoration:none"
49
+ style="color:#6b7280;white-space:nowrap;text-decoration:none;flex-shrink:0"
40
50
  >
41
51
  {options.log.fiberId}
42
52
  </a>
43
- {options.log.cause && (
44
- <div style="color:#ef4444;font-size:11px;padding:2px 0 0 0;white-space:pre-wrap;width:100%">
45
- {options.log.cause}
46
- </div>
47
- )}
48
53
  </div>
49
54
  )
50
55
  }
@@ -1,4 +1,6 @@
1
+ import * as Pretty from "../_Pretty.ts"
1
2
  import * as StudioStore from "../StudioStore.ts"
3
+ import * as PrettyValue from "./_PrettyValue.tsx"
2
4
 
3
5
  function formatDuration(ms: number | undefined): string {
4
6
  if (ms == null) return "..."
@@ -13,14 +15,20 @@ function statusColor(status: string): string {
13
15
  return "#eab308"
14
16
  }
15
17
 
16
- function KeyValue(options: { label: string; value: string | number | bigint | undefined | null }) {
18
+ function KeyValue(options: { label: string; value: unknown }) {
17
19
  if (options.value == null) return null
18
20
  return (
19
- <div style="display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px">
21
+ <div
22
+ style="display:flex;align-items:flex-start;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px"
23
+ >
20
24
  <span style="color:#64748b;min-width:120px">{options.label}</span>
21
- <span style="color:#e2e8f0;font-family:monospace;word-break:break-all">
22
- {String(options.value)}
23
- </span>
25
+ <div style="flex:1;min-width:0">
26
+ <PrettyValue.PrettyValue
27
+ value={options.value}
28
+ style="color:#e2e8f0;font-family:monospace;word-break:break-all"
29
+ preStyle="color:#e2e8f0;font-family:monospace;word-break:break-all;white-space:pre-wrap;margin:0"
30
+ />
31
+ </div>
24
32
  </div>
25
33
  )
26
34
  }
@@ -47,6 +55,20 @@ interface TreeSpan {
47
55
  ancestorHasNextSibling: Array<boolean>
48
56
  }
49
57
 
58
+ function sortByStartTime(a: StudioStore.StudioSpan, b: StudioStore.StudioSpan): number {
59
+ if (a.startTime < b.startTime) return -1
60
+ if (a.startTime > b.startTime) return 1
61
+ return 0
62
+ }
63
+
64
+ function pickRootSpan(spans: Array<StudioStore.StudioSpan>): StudioStore.StudioSpan {
65
+ const spanIds = new Set(spans.map((span) => span.spanId))
66
+ return (
67
+ spans.find((span) => !span.parentSpanId || !spanIds.has(span.parentSpanId)) ??
68
+ spans.slice().sort(sortByStartTime)[0]
69
+ )
70
+ }
71
+
50
72
  function buildSpanTree(spans: Array<StudioStore.StudioSpan>): Array<TreeSpan> {
51
73
  const byId = new Map<bigint, StudioStore.StudioSpan>()
52
74
  const childrenOf = new Map<bigint, Array<StudioStore.StudioSpan>>()
@@ -69,23 +91,31 @@ function buildSpanTree(spans: Array<StudioStore.StudioSpan>): Array<TreeSpan> {
69
91
  }
70
92
  }
71
93
 
72
- const sortByStart = (a: StudioStore.StudioSpan, b: StudioStore.StudioSpan) =>
73
- Number(a.startTime - b.startTime)
74
-
75
- roots.sort(sortByStart)
94
+ roots.sort(sortByStartTime)
76
95
  for (const children of childrenOf.values()) {
77
- children.sort(sortByStart)
96
+ children.sort(sortByStartTime)
78
97
  }
79
98
 
80
99
  const result: Array<TreeSpan> = []
100
+ const visited = new Set<bigint>()
81
101
 
82
102
  function walk(
83
103
  span: StudioStore.StudioSpan,
84
104
  depth: number,
85
105
  isLast: boolean,
86
106
  ancestors: Array<boolean>,
107
+ lineage: Set<bigint>,
87
108
  ) {
88
- const children = childrenOf.get(span.spanId) ?? []
109
+ if (lineage.has(span.spanId) || visited.has(span.spanId)) return
110
+
111
+ const nextLineage = new Set(lineage)
112
+ nextLineage.add(span.spanId)
113
+
114
+ const children = (childrenOf.get(span.spanId) ?? []).filter(
115
+ (child) => !nextLineage.has(child.spanId) && !visited.has(child.spanId),
116
+ )
117
+
118
+ visited.add(span.spanId)
89
119
  result.push({
90
120
  span,
91
121
  depth,
@@ -94,12 +124,18 @@ function buildSpanTree(spans: Array<StudioStore.StudioSpan>): Array<TreeSpan> {
94
124
  ancestorHasNextSibling: [...ancestors],
95
125
  })
96
126
  for (let i = 0; i < children.length; i++) {
97
- walk(children[i], depth + 1, i === children.length - 1, [...ancestors, !isLast])
127
+ walk(children[i], depth + 1, i === children.length - 1, [...ancestors, !isLast], nextLineage)
98
128
  }
99
129
  }
100
130
 
101
131
  for (let i = 0; i < roots.length; i++) {
102
- walk(roots[i], 0, i === roots.length - 1, [])
132
+ walk(roots[i], 0, i === roots.length - 1, [], new Set())
133
+ }
134
+
135
+ const remaining = spans.filter((span) => !visited.has(span.spanId)).sort(sortByStartTime)
136
+
137
+ for (let i = 0; i < remaining.length; i++) {
138
+ walk(remaining[i], 0, i === remaining.length - 1, [], new Set())
103
139
  }
104
140
 
105
141
  return result
@@ -225,73 +261,30 @@ export function groupByTraceId(
225
261
  return groups
226
262
  }
227
263
 
228
- export function TraceGroup(options: { spans: Array<StudioStore.StudioSpan> }) {
264
+ export function TraceGroup(options: { id?: bigint; spans: Array<StudioStore.StudioSpan> }) {
229
265
  if (options.spans.length === 0) return null
230
- const root = options.spans.find((s) => !s.parentSpanId) ?? options.spans[0]
231
- const traceId = root.traceId
266
+ const root = pickRootSpan(options.spans)
267
+ const traceId = options.id ?? root.traceId
232
268
  const totalMs = root.durationMs ?? 0
233
- const rootStart = root.startTime
234
269
  const hasError = options.spans.some((s) => s.status === "error")
235
270
  const status = hasError ? "error" : root.status
236
271
 
237
272
  return (
238
- <details class="tl-row">
239
- <summary class="tl-summary tl-cols">
240
- <span class="tl-cell tl-cell-status">
241
- <span
242
- style={`width:8px;height:8px;border-radius:50%;background:${statusColor(status)};display:block`}
243
- />
244
- </span>
245
- <span class="tl-cell tl-cell-name">{root.name}</span>
246
- <span class="tl-cell tl-cell-spans">{options.spans.length}</span>
247
- <span class="tl-cell tl-cell-dur">{formatDuration(totalMs)}</span>
248
- <span class="tl-cell tl-cell-id">{String(traceId).slice(0, 12)}</span>
249
- </summary>
250
- <div class="tl-body">
251
- <div style="display:flex;gap:12px;align-items:center;margin-bottom:8px">
252
- <a
253
- href={`${StudioStore.store.prefix}/traces/${traceId}`}
254
- style="color:#38bdf8;font-size:12px;text-decoration:none"
255
- >
256
- Full trace view
257
- </a>
258
- <StatusBadge status={status} />
259
- <span style="color:#64748b;font-size:11px">
260
- {options.spans.length} span{options.spans.length !== 1 ? "s" : ""}
261
- </span>
262
- <span style="color:#64748b;font-size:11px;font-family:monospace">
263
- {formatDuration(totalMs)}
264
- </span>
265
- <span style="color:#475569;font-size:10px;font-family:monospace">{traceId}</span>
266
- </div>
267
- {options.spans.map((s) => {
268
- const offsetMs = Number(s.startTime - rootStart) / 1_000_000
269
- const leftPct = totalMs > 0 ? Math.min(100, (offsetMs / totalMs) * 100) : 0
270
- const widthPct =
271
- totalMs > 0
272
- ? Math.max(0.5, Math.min(100 - leftPct, ((s.durationMs ?? 0) / totalMs) * 100))
273
- : 100
274
- return (
275
- <div style="display:flex;align-items:center;gap:8px;padding:2px 0;font-size:11px;font-family:monospace">
276
- <span style={`color:${statusColor(s.status)};min-width:10px`}>
277
- {s.parentSpanId ? " " : ""}
278
- </span>
279
- <span style="color:#d1d5db;min-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
280
- {s.name}
281
- </span>
282
- <div style="flex:1;height:10px;background:#1f2937;border-radius:2px;position:relative;overflow:hidden">
283
- <div
284
- style={`position:absolute;height:100%;border-radius:2px;background:${statusColor(s.status)};left:${leftPct}%;width:${widthPct}%`}
285
- />
286
- </div>
287
- <span style="color:#9ca3af;min-width:60px;text-align:right">
288
- {formatDuration(s.durationMs)}
289
- </span>
290
- </div>
291
- )
292
- })}
293
- </div>
294
- </details>
273
+ <a
274
+ id={`trace-${traceId}`}
275
+ class="tl-row tl-cols"
276
+ href={`${StudioStore.store.prefix}/traces/${traceId}`}
277
+ >
278
+ <span class="tl-cell tl-cell-status">
279
+ <span
280
+ style={`width:8px;height:8px;border-radius:50%;background:${statusColor(status)};display:block`}
281
+ />
282
+ </span>
283
+ <span class="tl-cell tl-cell-name">{root.name}</span>
284
+ <span class="tl-cell tl-cell-spans">{options.spans.length}</span>
285
+ <span class="tl-cell tl-cell-dur">{formatDuration(totalMs)}</span>
286
+ <span class="tl-cell tl-cell-id">{String(traceId).slice(0, 12)}</span>
287
+ </a>
295
288
  )
296
289
  }
297
290
 
@@ -324,7 +317,7 @@ export function TraceDetail(options: { prefix: string; spans: Array<StudioStore.
324
317
  if (options.spans.length === 0) {
325
318
  return <div class="empty">Trace not found</div>
326
319
  }
327
- const root = options.spans.find((s) => !s.parentSpanId) ?? options.spans[0]
320
+ const root = pickRootSpan(options.spans)
328
321
  const traceId = root.traceId
329
322
  const totalMs = root.durationMs ?? 0
330
323
  const rootStart = root.startTime
@@ -375,7 +368,7 @@ export function TraceDetail(options: { prefix: string; spans: Array<StudioStore.
375
368
  const customAttrs = Object.entries(s.attributes).filter(([k]) => k !== "code.stacktrace")
376
369
 
377
370
  return (
378
- <details class="span-panel" style="margin-bottom:4px">
371
+ <details class="span-panel" style="margin-bottom:4px" open>
379
372
  <summary class="span-panel-header">
380
373
  <span
381
374
  style={`width:8px;height:8px;border-radius:50%;background:${statusColor(s.status)};flex-shrink:0`}
@@ -394,16 +387,23 @@ export function TraceDetail(options: { prefix: string; spans: Array<StudioStore.
394
387
  {s.parentSpanId && <KeyValue label="Parent" value={s.parentSpanId} />}
395
388
  {stacktrace && <KeyValue label="Source" value={stacktrace} />}
396
389
  {customAttrs.map(([k, v]) => (
397
- <KeyValue label={k} value={String(v)} />
390
+ <KeyValue label={k} value={v} />
398
391
  ))}
399
392
  {s.events.length > 0 && (
400
393
  <div style="margin-top:4px">
401
394
  <span style="color:#64748b;font-size:11px">Events:</span>
402
395
  {s.events.map((ev) => (
403
- <div style="padding:2px 0;font-size:11px;color:#94a3b8;font-family:monospace">
404
- {ev.name}
396
+ <div
397
+ style="display:flex;align-items:flex-start;gap:8px;padding:4px 0;font-size:11px;color:#94a3b8;font-family:monospace"
398
+ >
399
+ <span>{ev.name}</span>
405
400
  {ev.attributes && (
406
- <span style="color:#64748b"> {JSON.stringify(ev.attributes)}</span>
401
+ <div style="flex:1;min-width:0">
402
+ <PrettyValue.PreformattedText
403
+ text={Pretty.prettyPrintJson(ev.attributes)}
404
+ style="color:#64748b;white-space:pre-wrap;word-break:break-all;margin:0;font:inherit"
405
+ />
406
+ </div>
407
407
  )}
408
408
  </div>
409
409
  ))}
@@ -0,0 +1,34 @@
1
+ import * as Pretty from "../_Pretty.ts"
2
+
3
+ const htmlEscapeMap: Record<string, string> = {
4
+ "&": "&amp;",
5
+ "<": "&lt;",
6
+ ">": "&gt;",
7
+ '"': "&quot;",
8
+ "'": "&#39;",
9
+ }
10
+
11
+ function escapeHtml(text: string): string {
12
+ return text.replace(/[&<>"']/g, (char) => htmlEscapeMap[char]!)
13
+ }
14
+
15
+ function toPreHtml(text: string): string {
16
+ return escapeHtml(text).replaceAll("\n", "&#10;")
17
+ }
18
+
19
+ export function PreformattedText(options: { text: string; style?: string }) {
20
+ return <pre style={options.style} dangerouslySetInnerHTML={{ __html: toPreHtml(options.text) }} />
21
+ }
22
+
23
+ export function PrettyValue(options: { value: unknown; style?: string; preStyle?: string }) {
24
+ if (options.value == null) return null
25
+ if (Pretty.isStructuredValue(options.value)) {
26
+ return (
27
+ <PreformattedText
28
+ text={Pretty.prettyPrintJson(options.value)}
29
+ style={options.preStyle}
30
+ />
31
+ )
32
+ }
33
+ return <span style={options.style}>{String(options.value)}</span>
34
+ }