@nice-code/action 0.4.4 → 0.4.5

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 (25) hide show
  1. package/README.md +20 -28
  2. package/build/devtools/browser/index.js +135 -51
  3. package/build/devtools/server/index.js +2 -0
  4. package/build/index.js +303 -165
  5. package/build/types/ActionDefinition/Action/Payload/ActionPayload.types.d.ts +2 -1
  6. package/build/types/ActionRuntime/Handler/ExternalClient/ActionExternalClientHandler.d.ts +4 -3
  7. package/build/types/ActionRuntime/Handler/ExternalClient/ActionExternalClientHandler.types.d.ts +3 -3
  8. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/ConnectionTransportManager.d.ts +2 -2
  9. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Custom/{TransportCustom.d.ts → CustomConnection.d.ts} +2 -2
  10. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Custom/CustomTransport.d.ts +33 -0
  11. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Http/{TransportHttp.d.ts → HttpConnection.d.ts} +2 -2
  12. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Http/HttpTransport.d.ts +28 -0
  13. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Http/TransportHttp.types.d.ts +1 -2
  14. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Transport.d.ts +25 -12
  15. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Transport.types.d.ts +15 -2
  16. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/TransportConnection.d.ts +21 -0
  17. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/{TransportWebSocket.d.ts → WebSocketConnection.d.ts} +2 -2
  18. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketTransport.d.ts +32 -0
  19. package/build/types/ActionRuntime/test/helpers/new_action_test_data.d.ts +2 -2
  20. package/build/types/devtools/browser/components/HandlerChips.d.ts +11 -0
  21. package/build/types/devtools/browser/components/Tooltip.d.ts +11 -1
  22. package/build/types/devtools/core/ActionDevtools.types.d.ts +5 -0
  23. package/build/types/index.d.ts +4 -4
  24. package/package.json +4 -4
  25. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Transport.combined.types.d.ts +0 -4
package/README.md CHANGED
@@ -147,28 +147,18 @@ import {
147
147
  ActionRuntime,
148
148
  ActionExternalClientHandler,
149
149
  RuntimeCoordinate,
150
- ETransportType,
151
- ETransportStatus,
150
+ HttpTransport,
152
151
  } from "@nice-code/action";
153
152
 
154
153
  export const clientCoord = RuntimeCoordinate.env("frontend");
155
154
  export const serverCoord = RuntimeCoordinate.env("backend");
156
155
 
156
+ // Transport definitions are plain, reusable objects you construct once.
157
+ const serverHttp = new HttpTransport({ url: "https://api.example.com/resolve_action" });
158
+
157
159
  const serverHandler = new ActionExternalClientHandler({
158
- externalClientSpecifier: serverCoord,
159
- transports: [
160
- {
161
- type: ETransportType.http,
162
- initialize: () => ({
163
- getTransport: () => ({
164
- status: ETransportStatus.ready,
165
- readyData: {
166
- createRequest: () => ({ url: "https://api.example.com/resolve_action" }),
167
- },
168
- }),
169
- }),
170
- },
171
- ],
160
+ runtimeCoordinate: serverCoord,
161
+ transports: [serverHttp],
172
162
  }).forDomain(userDomain);
173
163
 
174
164
  export const clientRuntime = new ActionRuntime(clientCoord)
@@ -226,20 +216,22 @@ function RenameUser() {
226
216
  ## WebSocket transport
227
217
 
228
218
  ```ts
229
- {
230
- type: ETransportType.ws,
231
- initialize: () => ({
232
- getTransport: () => ({
233
- status: ETransportStatus.ready,
234
- readyData: {
235
- ws: new WebSocket("wss://api.example.com/resolve_action/ws"),
236
- },
237
- }),
238
- }),
239
- }
219
+ import { WebSocketTransport } from "@nice-code/action";
220
+
221
+ const serverWs = new WebSocketTransport({ url: "wss://api.example.com/resolve_action/ws" });
222
+
223
+ const serverHandler = new ActionExternalClientHandler({
224
+ runtimeCoordinate: serverCoord,
225
+ transports: [serverWs, serverHttp], // WebSocket preferred, HTTP as fallback
226
+ }).forDomain(userDomain);
240
227
  ```
241
228
 
242
- Multiple transports can be registered; the runtime picks the best available one (WebSocket preferred for lower latency, HTTP as fallback).
229
+ The socket is opened lazily and reused across actions to the same endpoint. Multiple transports can
230
+ be registered; the runtime picks the best available one (WebSocket preferred for lower latency, HTTP
231
+ as fallback). For channels nice-action doesn't model natively, use `CustomTransport`.
232
+
233
+ For advanced cases, `HttpTransport` / `WebSocketTransport` accept functions to derive the URL or
234
+ headers per action (e.g. `new HttpTransport({ url: (input) => \`/api/${input.action.id}\` })`).
243
235
 
244
236
  ---
245
237
 
@@ -1850,6 +1850,8 @@ function extractRouting(context) {
1850
1850
  insId: handler.client.insId
1851
1851
  } : undefined,
1852
1852
  transport: isExternal ? handler.transType : undefined,
1853
+ transportSummary: isExternal ? handler.transInfo?.summary : undefined,
1854
+ transportUrl: isExternal ? handler.transInfo?.url : undefined,
1853
1855
  time: item.time
1854
1856
  };
1855
1857
  });
@@ -1957,7 +1959,7 @@ class ActionDevtoolsCore {
1957
1959
  }
1958
1960
  }
1959
1961
  // src/devtools/browser/NiceActionDevtools.tsx
1960
- import { useEffect as useEffect4, useId, useMemo as useMemo3, useReducer, useState as useState10 } from "react";
1962
+ import { useEffect as useEffect4, useId, useMemo as useMemo3, useReducer, useState as useState12 } from "react";
1961
1963
 
1962
1964
  // src/devtools/core/devtools_colors.ts
1963
1965
  var DEVTOOL_COLOR_SEMANTIC_ERROR = "#FF5C5C";
@@ -2068,7 +2070,7 @@ var SEMANTIC_COLORS = {
2068
2070
  };
2069
2071
 
2070
2072
  // src/devtools/browser/components/action_detail/ActionDetailPanel.tsx
2071
- import { useMemo, useState as useState7 } from "react";
2073
+ import { useMemo, useState as useState8 } from "react";
2072
2074
 
2073
2075
  // src/devtools/browser/ui_util/size.ts
2074
2076
  function getSizeValue(size) {
@@ -3230,10 +3232,11 @@ function ActionErrorDisplay({
3230
3232
  import { Component } from "lucide-react";
3231
3233
 
3232
3234
  // src/devtools/browser/components/Chip.tsx
3233
- import { useState as useState3 } from "react";
3235
+ import { useState as useState4 } from "react";
3234
3236
 
3235
3237
  // src/devtools/browser/components/Tooltip.tsx
3236
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
3238
+ import { useState as useState3 } from "react";
3239
+ import { jsxDEV as jsxDEV6, Fragment as Fragment2 } from "react/jsx-dev-runtime";
3237
3240
  function Tooltip({
3238
3241
  anchor,
3239
3242
  config,
@@ -3312,9 +3315,31 @@ function Tooltip({
3312
3315
  ]
3313
3316
  }, undefined, true, undefined, this);
3314
3317
  }
3318
+ function HoverTooltip({
3319
+ config,
3320
+ children,
3321
+ style
3322
+ }) {
3323
+ const [anchor, setAnchor] = useState3(null);
3324
+ const enabled = config != null;
3325
+ return /* @__PURE__ */ jsxDEV6(Fragment2, {
3326
+ children: [
3327
+ /* @__PURE__ */ jsxDEV6("span", {
3328
+ onMouseEnter: enabled ? (e) => setAnchor(e.currentTarget.getBoundingClientRect()) : undefined,
3329
+ onMouseLeave: enabled ? () => setAnchor(null) : undefined,
3330
+ style: { ...style, cursor: enabled ? "default" : style?.cursor },
3331
+ children
3332
+ }, undefined, false, undefined, this),
3333
+ enabled && anchor != null && config != null && /* @__PURE__ */ jsxDEV6(Tooltip, {
3334
+ anchor,
3335
+ config
3336
+ }, undefined, false, undefined, this)
3337
+ ]
3338
+ }, undefined, true, undefined, this);
3339
+ }
3315
3340
 
3316
3341
  // src/devtools/browser/components/Chip.tsx
3317
- import { jsxDEV as jsxDEV7, Fragment as Fragment2 } from "react/jsx-dev-runtime";
3342
+ import { jsxDEV as jsxDEV7, Fragment as Fragment3 } from "react/jsx-dev-runtime";
3318
3343
  var CHIP_SIZE_STYLES = {
3319
3344
  ["sm" /* sm */]: { fontSize: "0.8em", padding: "1px 3px" },
3320
3345
  ["md" /* md */]: { fontSize: "0.9em", padding: "1px 4px" },
@@ -3328,13 +3353,13 @@ function Chip({
3328
3353
  tooltip,
3329
3354
  children
3330
3355
  }) {
3331
- const [anchor, setAnchor] = useState3(null);
3356
+ const [anchor, setAnchor] = useState4(null);
3332
3357
  const hasTooltip = tooltip != null;
3333
3358
  const { fontSize, padding } = CHIP_SIZE_STYLES[size];
3334
3359
  const colorEntry = SEMANTIC_COLORS[color];
3335
3360
  const resolvedColor = subtle ? colorEntry.subtle?.color ?? colorEntry.color : colorEntry.color;
3336
3361
  const resolvedBorderColor = subtle ? colorEntry.subtle?.borderColor ?? "transparent" : colorEntry.borderColor;
3337
- return /* @__PURE__ */ jsxDEV7(Fragment2, {
3362
+ return /* @__PURE__ */ jsxDEV7(Fragment3, {
3338
3363
  children: [
3339
3364
  /* @__PURE__ */ jsxDEV7("span", {
3340
3365
  onMouseEnter: hasTooltip ? (e) => setAnchor(e.currentTarget.getBoundingClientRect()) : undefined,
@@ -3363,8 +3388,8 @@ function Chip({
3363
3388
  }
3364
3389
 
3365
3390
  // src/devtools/browser/components/Icon.tsx
3366
- import { useState as useState4 } from "react";
3367
- import { jsxDEV as jsxDEV8, Fragment as Fragment3 } from "react/jsx-dev-runtime";
3391
+ import { useState as useState5 } from "react";
3392
+ import { jsxDEV as jsxDEV8, Fragment as Fragment4 } from "react/jsx-dev-runtime";
3368
3393
  var BASE_ICON_SIDE_LENGTH_EM = 1.6;
3369
3394
  var ICON_GAP_EM = 0.3;
3370
3395
  function Icon({
@@ -3376,14 +3401,14 @@ function Icon({
3376
3401
  style,
3377
3402
  noBackground = false
3378
3403
  }) {
3379
- const [anchor, setAnchor] = useState4(null);
3404
+ const [anchor, setAnchor] = useState5(null);
3380
3405
  const hasTooltip = tooltip != null;
3381
3406
  const colorEntry = SEMANTIC_COLORS[color];
3382
3407
  const resolvedColor = subtle ? colorEntry.subtle?.color ?? colorEntry.color : colorEntry.color;
3383
3408
  const finalIcons = Array.isArray(IconComponent) ? IconComponent : [IconComponent];
3384
3409
  const fullIconWidthEm = (finalIcons.length - 1) * ICON_GAP_EM + finalIcons.length * BASE_ICON_SIDE_LENGTH_EM * getSizeValue(size);
3385
3410
  const iconSideLengthEm = `${BASE_ICON_SIDE_LENGTH_EM * getSizeValue(size)}em`;
3386
- return /* @__PURE__ */ jsxDEV8(Fragment3, {
3411
+ return /* @__PURE__ */ jsxDEV8(Fragment4, {
3387
3412
  children: [
3388
3413
  /* @__PURE__ */ jsxDEV8("div", {
3389
3414
  onMouseEnter: hasTooltip ? (e) => setAnchor(e.currentTarget.getBoundingClientRect()) : undefined,
@@ -3419,9 +3444,9 @@ function Icon({
3419
3444
  }
3420
3445
 
3421
3446
  // src/devtools/browser/components/DomainChip.tsx
3422
- import { jsxDEV as jsxDEV9, Fragment as Fragment4 } from "react/jsx-dev-runtime";
3447
+ import { jsxDEV as jsxDEV9, Fragment as Fragment5 } from "react/jsx-dev-runtime";
3423
3448
  function DomainHierarchyContent({ allDomains }) {
3424
- return /* @__PURE__ */ jsxDEV9(Fragment4, {
3449
+ return /* @__PURE__ */ jsxDEV9(Fragment5, {
3425
3450
  children: allDomains.map((d, i) => {
3426
3451
  const isCurrent = i === allDomains.length - 1;
3427
3452
  return /* @__PURE__ */ jsxDEV9("div", {
@@ -3516,18 +3541,44 @@ function DomainChip({
3516
3541
  }
3517
3542
 
3518
3543
  // src/devtools/browser/components/HandlerChips.tsx
3519
- import { jsxDEV as jsxDEV10, Fragment as Fragment5 } from "react/jsx-dev-runtime";
3544
+ import { jsxDEV as jsxDEV10, Fragment as Fragment6 } from "react/jsx-dev-runtime";
3520
3545
  function getExternalLabel(hop) {
3521
3546
  if (hop.handlerType !== "external")
3522
3547
  return null;
3523
3548
  return hop.handlerClient != null ? `${hop.transport ?? "ext"} → ${hop.handlerClient.envId}` : `→ ${hop.transport ?? "ext"}`;
3524
3549
  }
3550
+ function getTransportTooltip(hop) {
3551
+ return getTransportTooltipForHops([hop]);
3552
+ }
3553
+ function getTransportTooltipForHops(hops) {
3554
+ const lines = hops.filter((hop) => hop.handlerType === "external").map((hop) => ({ summary: hop.transportSummary, url: hop.transportUrl })).filter((line) => line.summary != null || line.url != null);
3555
+ if (lines.length === 0)
3556
+ return;
3557
+ return {
3558
+ title: "Transport",
3559
+ content: /* @__PURE__ */ jsxDEV10("div", {
3560
+ style: { display: "flex", flexDirection: "column", gap: "6px", fontSize: "11px" },
3561
+ children: lines.map((line) => /* @__PURE__ */ jsxDEV10("div", {
3562
+ style: { display: "flex", flexDirection: "column", gap: "2px" },
3563
+ children: [
3564
+ line.summary != null && /* @__PURE__ */ jsxDEV10("span", {
3565
+ children: line.summary
3566
+ }, undefined, false, undefined, this),
3567
+ line.url != null && line.url !== line.summary && /* @__PURE__ */ jsxDEV10("span", {
3568
+ style: { color: DEVTOOL_COLOR_TEXT_MUTED, wordBreak: "break-all" },
3569
+ children: line.url
3570
+ }, undefined, false, undefined, this)
3571
+ ]
3572
+ }, `${line.summary ?? ""}:${line.url ?? ""}`, true, undefined, this))
3573
+ }, undefined, false, undefined, this)
3574
+ };
3575
+ }
3525
3576
  function HandlerChips({ entry, size, subtle }) {
3526
3577
  const firstHop = entry.meta.routing[0];
3527
3578
  const localEnvId = firstHop != null ? firstHop.runtime.envId : null;
3528
3579
  const externalLabel = firstHop != null ? getExternalLabel(firstHop) : null;
3529
3580
  const firstHopIsLocal = firstHop != null && firstHop.handlerType === "local";
3530
- return /* @__PURE__ */ jsxDEV10(Fragment5, {
3581
+ return /* @__PURE__ */ jsxDEV10(Fragment6, {
3531
3582
  children: [
3532
3583
  localEnvId != null && (firstHopIsLocal || externalLabel == null) && /* @__PURE__ */ jsxDEV10(Chip, {
3533
3584
  color: "handler_local" /* handler_local */,
@@ -3539,6 +3590,7 @@ function HandlerChips({ entry, size, subtle }) {
3539
3590
  color: "handler_external" /* handler_external */,
3540
3591
  size,
3541
3592
  subtle,
3593
+ tooltip: firstHop != null ? getTransportTooltip(firstHop) : undefined,
3542
3594
  children: externalLabel
3543
3595
  }, undefined, false, undefined, this)
3544
3596
  ]
@@ -3546,15 +3598,15 @@ function HandlerChips({ entry, size, subtle }) {
3546
3598
  }
3547
3599
 
3548
3600
  // src/devtools/browser/components/RunningTimer.tsx
3549
- import { useEffect as useEffect2, useState as useState5 } from "react";
3550
- import { jsxDEV as jsxDEV11, Fragment as Fragment6 } from "react/jsx-dev-runtime";
3601
+ import { useEffect as useEffect2, useState as useState6 } from "react";
3602
+ import { jsxDEV as jsxDEV11, Fragment as Fragment7 } from "react/jsx-dev-runtime";
3551
3603
  function RunningTimer({ startTime }) {
3552
- const [elapsed, setElapsed] = useState5(() => Date.now() - startTime);
3604
+ const [elapsed, setElapsed] = useState6(() => Date.now() - startTime);
3553
3605
  useEffect2(() => {
3554
3606
  const interval = setInterval(() => setElapsed(Date.now() - startTime), 100);
3555
3607
  return () => clearInterval(interval);
3556
3608
  }, [startTime]);
3557
- return /* @__PURE__ */ jsxDEV11(Fragment6, {
3609
+ return /* @__PURE__ */ jsxDEV11(Fragment7, {
3558
3610
  children: [
3559
3611
  elapsed,
3560
3612
  "ms"
@@ -3563,7 +3615,7 @@ function RunningTimer({ startTime }) {
3563
3615
  }
3564
3616
  function DurationDisplay({ entry }) {
3565
3617
  const d = formatDuration(entry);
3566
- return /* @__PURE__ */ jsxDEV11(Fragment6, {
3618
+ return /* @__PURE__ */ jsxDEV11(Fragment7, {
3567
3619
  children: d ?? /* @__PURE__ */ jsxDEV11(RunningTimer, {
3568
3620
  startTime: entry.startTime
3569
3621
  }, undefined, false, undefined, this)
@@ -3599,6 +3651,8 @@ function CallStackLink({
3599
3651
  const symbol = STATUS_SYMBOL[entry.status];
3600
3652
  const labelColor = entryRole === "caller" ? DEVTOOL_COLOR_TEXT_SECONDARY : getCalledColor(entry);
3601
3653
  const label = entryRole === "caller" ? "↑ from" : getCalledLabel(entry);
3654
+ const firstHop = entry.meta.routing[0];
3655
+ const transportTooltip = entryRole === "called" && firstHop != null ? getTransportTooltip(firstHop) : undefined;
3602
3656
  return /* @__PURE__ */ jsxDEV12("div", {
3603
3657
  onClick,
3604
3658
  style: {
@@ -3620,9 +3674,13 @@ function CallStackLink({
3620
3674
  style: { color, fontSize: "10px", flexShrink: 0 },
3621
3675
  children: symbol
3622
3676
  }, undefined, false, undefined, this),
3623
- /* @__PURE__ */ jsxDEV12("span", {
3624
- style: { color: labelColor, fontSize: "9px", flexShrink: 0 },
3625
- children: label
3677
+ /* @__PURE__ */ jsxDEV12(HoverTooltip, {
3678
+ config: transportTooltip,
3679
+ style: { flexShrink: 0, display: "flex" },
3680
+ children: /* @__PURE__ */ jsxDEV12("span", {
3681
+ style: { color: labelColor, fontSize: "9px" },
3682
+ children: label
3683
+ }, undefined, false, undefined, this)
3626
3684
  }, undefined, false, undefined, this),
3627
3685
  /* @__PURE__ */ jsxDEV12("span", {
3628
3686
  style: { display: "flex", alignItems: "center", gap: "5px" },
@@ -3700,10 +3758,11 @@ function ChildDispatchChips({
3700
3758
  const runtimeId = item.handlerClient?.envId ?? item.transport ?? "ext";
3701
3759
  const hasClient = item.handlerClient != null;
3702
3760
  if (!groups.has(runtimeId)) {
3703
- groups.set(runtimeId, { runtimeId, transports: [], hasClient });
3761
+ groups.set(runtimeId, { runtimeId, transports: [], hasClient, hops: [] });
3704
3762
  }
3705
- const transport = item.transport;
3706
3763
  const group = groups.get(runtimeId);
3764
+ group.hops.push(item);
3765
+ const transport = item.transport;
3707
3766
  if (transport != null && !group.transports.includes(transport)) {
3708
3767
  group.transports.push(transport);
3709
3768
  }
@@ -3716,6 +3775,7 @@ function ChildDispatchChips({
3716
3775
  return /* @__PURE__ */ jsxDEV13(Chip, {
3717
3776
  color: "handler_external" /* handler_external */,
3718
3777
  size,
3778
+ tooltip: getTransportTooltipForHops(group.hops),
3719
3779
  children: label
3720
3780
  }, group.runtimeId, false, undefined, this);
3721
3781
  })
@@ -3723,7 +3783,7 @@ function ChildDispatchChips({
3723
3783
  }
3724
3784
 
3725
3785
  // src/devtools/browser/components/MetaSection.tsx
3726
- import { useState as useState6 } from "react";
3786
+ import { useState as useState7 } from "react";
3727
3787
  import { jsxDEV as jsxDEV14 } from "react/jsx-dev-runtime";
3728
3788
  function MetaChip({ label, value }) {
3729
3789
  return /* @__PURE__ */ jsxDEV14("span", {
@@ -3744,7 +3804,7 @@ function MetaChip({ label, value }) {
3744
3804
  }, undefined, true, undefined, this);
3745
3805
  }
3746
3806
  function MetaSection({ entry }) {
3747
- const [expanded, setExpanded] = useState6(false);
3807
+ const [expanded, setExpanded] = useState7(false);
3748
3808
  const { meta, cuid } = entry;
3749
3809
  const expandedRows = [
3750
3810
  { label: "cuid", value: cuid },
@@ -3869,7 +3929,8 @@ function RoutingSection({
3869
3929
  const isLocal = hop.handlerType === "local";
3870
3930
  const isLast = i === hopCount - 1 && phantomCount === 0;
3871
3931
  const badgeColor = isLocal ? DEVTOOL_COLOR_SEMANTIC_SUCCESS : DEVTOOL_COLOR_SEMANTIC_WARNING;
3872
- const badgeText = isLocal ? "● exec" : `→ ${hop.transport ?? "ext"}`;
3932
+ const badgeText = isLocal ? "● exec" : `→ ${hop.transportSummary ?? hop.transport ?? "ext"}`;
3933
+ const transportTooltip = isLocal ? undefined : getTransportTooltip(hop);
3873
3934
  const runtimeTitle = [hop.runtime.perId, hop.runtime.insId].filter(Boolean).join(" · ") || undefined;
3874
3935
  return /* @__PURE__ */ jsxDEV15("div", {
3875
3936
  style: {
@@ -3905,9 +3966,20 @@ function RoutingSection({
3905
3966
  }, undefined, false, undefined, this)
3906
3967
  ]
3907
3968
  }, undefined, true, undefined, this),
3908
- /* @__PURE__ */ jsxDEV15("span", {
3909
- style: { color: badgeColor, fontSize: "10px", whiteSpace: "nowrap" },
3910
- children: badgeText
3969
+ /* @__PURE__ */ jsxDEV15(HoverTooltip, {
3970
+ config: transportTooltip,
3971
+ style: { display: "block", minWidth: 0, overflow: "hidden" },
3972
+ children: /* @__PURE__ */ jsxDEV15("span", {
3973
+ style: {
3974
+ color: badgeColor,
3975
+ fontSize: "10px",
3976
+ whiteSpace: "nowrap",
3977
+ overflow: "hidden",
3978
+ textOverflow: "ellipsis",
3979
+ display: "block"
3980
+ },
3981
+ children: badgeText
3982
+ }, undefined, false, undefined, this)
3911
3983
  }, undefined, false, undefined, this),
3912
3984
  /* @__PURE__ */ jsxDEV15("span", {
3913
3985
  style: {
@@ -4140,7 +4212,7 @@ function ActionDetailPanel({
4140
4212
  childEntries,
4141
4213
  onSelectEntry
4142
4214
  }) {
4143
- const [focusedChildCuid, setFocusedChildCuid] = useState7(null);
4215
+ const [focusedChildCuid, setFocusedChildCuid] = useState8(null);
4144
4216
  const focusedEntry = focusedChildCuid != null ? childEntries.find((e) => e.cuid === focusedChildCuid) ?? entry : entry;
4145
4217
  const maxRoutingHops = Math.max(entry.meta.routing.length, ...childEntries.map((e) => e.meta.routing.length), 0);
4146
4218
  const maxCallSiteFrames = Math.max(countUserFrames(entry.meta.originClient, entry.callSite), ...childEntries.map((e) => countUserFrames(e.meta.originClient, e.callSite)), 0);
@@ -4240,13 +4312,14 @@ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2 } from
4240
4312
 
4241
4313
  // src/devtools/browser/components/action_list/ActionEntryRow.tsx
4242
4314
  import { CircleX as CircleX3, PackageCheck as PackageCheck2, Variable as Variable2 } from "lucide-react";
4243
- import { Fragment as Fragment8, useState as useState8 } from "react";
4315
+ import { Fragment as Fragment9, useState as useState10 } from "react";
4244
4316
 
4245
4317
  // src/devtools/browser/components/action_list/ActionInputAndOutputChip.tsx
4246
4318
  import { CircleX as CircleX2, PackageCheck, Sparkle, Variable } from "lucide-react";
4319
+ import { useState as useState9 } from "react";
4247
4320
 
4248
4321
  // src/devtools/browser/components/action_list/IoTooltipContent.tsx
4249
- import { jsxDEV as jsxDEV17, Fragment as Fragment7 } from "react/jsx-dev-runtime";
4322
+ import { jsxDEV as jsxDEV17, Fragment as Fragment8 } from "react/jsx-dev-runtime";
4250
4323
  var JSON_TOKEN_RE2 = /("(?:\\.|[^"\\])*")(\s*:)?|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|(\btrue\b|\bfalse\b|\bnull\b|\bundefined\b)|([{}[\],])/g;
4251
4324
  function renderColoredJson2(text) {
4252
4325
  const nodes = [];
@@ -4333,6 +4406,8 @@ var ActionInputAndOutputChip = ({
4333
4406
  const firstHopIsLocal = firstHop != null && firstHop.handlerType === "local";
4334
4407
  const isLocal = localEnvId != null && (firstHopIsLocal || externalLabel == null);
4335
4408
  const color = isLocal ? "handler_local" /* handler_local */ : "handler_external" /* handler_external */;
4409
+ const transportTooltip = firstHop != null ? getTransportTooltip(firstHop) : undefined;
4410
+ const [labelAnchor, setLabelAnchor] = useState9(null);
4336
4411
  const isNewInput = breakReasons.includes("new_input" /* new_input */);
4337
4412
  const isNewOutput = breakReasons.includes("new_output" /* new_output */);
4338
4413
  const hasError = entry.error != null || entry.abortReason != null;
@@ -4381,9 +4456,15 @@ var ActionInputAndOutputChip = ({
4381
4456
  children: "→"
4382
4457
  }, undefined, false, undefined, this),
4383
4458
  /* @__PURE__ */ jsxDEV18("span", {
4384
- style: { opacity: 0.8 },
4459
+ style: { opacity: 0.8, cursor: transportTooltip != null ? "default" : undefined },
4460
+ onMouseEnter: transportTooltip != null ? (e) => setLabelAnchor(e.currentTarget.getBoundingClientRect()) : undefined,
4461
+ onMouseLeave: transportTooltip != null ? () => setLabelAnchor(null) : undefined,
4385
4462
  children: label
4386
4463
  }, undefined, false, undefined, this),
4464
+ labelAnchor != null && transportTooltip != null && /* @__PURE__ */ jsxDEV18(Tooltip, {
4465
+ anchor: labelAnchor,
4466
+ config: transportTooltip
4467
+ }, undefined, false, undefined, this),
4387
4468
  /* @__PURE__ */ jsxDEV18("span", {
4388
4469
  style: { opacity: 0.6 },
4389
4470
  color,
@@ -4420,7 +4501,7 @@ var ActionInputAndOutputChip = ({
4420
4501
  };
4421
4502
 
4422
4503
  // src/devtools/browser/components/action_list/ActionEntryRow.tsx
4423
- import { jsxDEV as jsxDEV19, Fragment as Fragment9 } from "react/jsx-dev-runtime";
4504
+ import { jsxDEV as jsxDEV19, Fragment as Fragment10 } from "react/jsx-dev-runtime";
4424
4505
  function getLatestChipColor(status) {
4425
4506
  if (status === "failed")
4426
4507
  return "failed" /* failed */;
@@ -4447,7 +4528,7 @@ function GroupDotTooltip({
4447
4528
  config: {
4448
4529
  align: "center",
4449
4530
  maxWidth: 240,
4450
- content: /* @__PURE__ */ jsxDEV19(Fragment9, {
4531
+ content: /* @__PURE__ */ jsxDEV19(Fragment10, {
4451
4532
  children: [
4452
4533
  /* @__PURE__ */ jsxDEV19("div", {
4453
4534
  style: { color: dotColor, marginBottom: "1px" },
@@ -4489,10 +4570,10 @@ function GroupDot({
4489
4570
  isActive,
4490
4571
  onSelect
4491
4572
  }) {
4492
- const [anchor, setAnchor] = useState8(null);
4573
+ const [anchor, setAnchor] = useState10(null);
4493
4574
  const dotColor = STATUS_COLOR[entry.status];
4494
4575
  const hovered = anchor != null;
4495
- return /* @__PURE__ */ jsxDEV19(Fragment9, {
4576
+ return /* @__PURE__ */ jsxDEV19(Fragment10, {
4496
4577
  children: [
4497
4578
  /* @__PURE__ */ jsxDEV19("button", {
4498
4579
  "data-cuid": entry.cuid,
@@ -4541,7 +4622,10 @@ function ActionEntryRow({
4541
4622
  }) {
4542
4623
  const timestamp = formatTimestamp(entry.startTime);
4543
4624
  const hasGroup = groupEntries != null && groupEntries.length > 1;
4544
- const hasBottomContent = childEntries != null && childEntries.length > 0 || (breakReasons?.some((r) => r !== "new_input" /* new_input */ && r !== "new_output" /* new_output */) ?? false) || entry.error != null || entry.abortReason != null;
4625
+ const externalChildEntries = childEntries?.filter((child) => child.meta.routing[0]?.handlerType === "external") ?? [];
4626
+ const nonIoBreakReasons = breakReasons?.filter((r) => r !== "new_input" /* new_input */ && r !== "new_output" /* new_output */) ?? [];
4627
+ const hasBottomError = entry.error != null || entry.abortReason != null;
4628
+ const hasBottomContent = externalChildEntries.length > 0 || nonIoBreakReasons.length > 0;
4545
4629
  return /* @__PURE__ */ jsxDEV19("div", {
4546
4630
  "data-cuid": entry.cuid,
4547
4631
  onClick,
@@ -4663,7 +4747,7 @@ function ActionEntryRow({
4663
4747
  }, undefined, true, undefined, this)
4664
4748
  ]
4665
4749
  }, undefined, true, undefined, this),
4666
- hasBottomContent && (childEntries?.length ?? 0) > 0 && /* @__PURE__ */ jsxDEV19("div", {
4750
+ hasBottomContent && /* @__PURE__ */ jsxDEV19("div", {
4667
4751
  style: {
4668
4752
  display: "flex",
4669
4753
  flexWrap: "wrap",
@@ -4672,7 +4756,7 @@ function ActionEntryRow({
4672
4756
  paddingLeft: "4.5em"
4673
4757
  },
4674
4758
  children: [
4675
- childEntries?.map((child) => /* @__PURE__ */ jsxDEV19(Fragment8, {
4759
+ externalChildEntries.map((child) => /* @__PURE__ */ jsxDEV19(Fragment9, {
4676
4760
  children: [
4677
4761
  /* @__PURE__ */ jsxDEV19(Icon, {
4678
4762
  size: "sm" /* sm */,
@@ -4708,12 +4792,12 @@ function ActionEntryRow({
4708
4792
  }, undefined, false, undefined, this)
4709
4793
  ]
4710
4794
  }, child.actionId, true, undefined, this)),
4711
- breakReasons?.filter((r) => r !== "new_input" /* new_input */ && r !== "new_output" /* new_output */).map((reason) => /* @__PURE__ */ jsxDEV19(Chip, {
4795
+ nonIoBreakReasons.map((reason) => /* @__PURE__ */ jsxDEV19(Chip, {
4712
4796
  color: "default" /* default */,
4713
4797
  subtle: true,
4714
4798
  children: reason
4715
4799
  }, reason, false, undefined, this)),
4716
- (childEntries?.length ?? 0) > 0 && (entry.error != null || entry.abortReason != null) && /* @__PURE__ */ jsxDEV19(Icon, {
4800
+ externalChildEntries.length > 0 && hasBottomError && /* @__PURE__ */ jsxDEV19(Icon, {
4717
4801
  size: "sm" /* sm */,
4718
4802
  icon: CircleX3,
4719
4803
  color: entry.status === "aborted" ? "aborted" /* aborted */ : "error" /* error */,
@@ -4874,7 +4958,7 @@ function ActionList({
4874
4958
  }
4875
4959
 
4876
4960
  // src/devtools/browser/components/PanelChrome.tsx
4877
- import { useState as useState9 } from "react";
4961
+ import { useState as useState11 } from "react";
4878
4962
  import { jsxDEV as jsxDEV21 } from "react/jsx-dev-runtime";
4879
4963
  var MONO_FONT = "ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace";
4880
4964
  var SANS_FONT = "ui-sans-serif, system-ui, sans-serif";
@@ -5079,7 +5163,7 @@ function SplitHandle({
5079
5163
  horizontal,
5080
5164
  onRatioChange
5081
5165
  }) {
5082
- const [hovered, setHovered] = useState9(false);
5166
+ const [hovered, setHovered] = useState11(false);
5083
5167
  const onMouseDown = (e) => {
5084
5168
  e.preventDefault();
5085
5169
  const container = e.currentTarget.parentElement;
@@ -5272,7 +5356,7 @@ function getDevtoolsDockCoordinator() {
5272
5356
  }
5273
5357
 
5274
5358
  // src/devtools/browser/NiceActionDevtools.tsx
5275
- import { jsxDEV as jsxDEV22, Fragment as Fragment10 } from "react/jsx-dev-runtime";
5359
+ import { jsxDEV as jsxDEV22, Fragment as Fragment11 } from "react/jsx-dev-runtime";
5276
5360
  if (typeof document !== "undefined" && !document.getElementById("__nice-action-devtools-styles")) {
5277
5361
  const style = document.createElement("style");
5278
5362
  style.id = "__nice-action-devtools-styles";
@@ -5383,9 +5467,9 @@ function NiceActionDevtools_Panel({
5383
5467
  position: defaultPosition = "dock-bottom",
5384
5468
  initialOpen = false
5385
5469
  }) {
5386
- const [prefs, setPrefsRaw] = useState10(() => readPrefs(defaultPosition, initialOpen));
5387
- const [entries, setEntries] = useState10([]);
5388
- const [selectedCuid, setSelectedCuid] = useState10(null);
5470
+ const [prefs, setPrefsRaw] = useState12(() => readPrefs(defaultPosition, initialOpen));
5471
+ const [entries, setEntries] = useState12([]);
5472
+ const [selectedCuid, setSelectedCuid] = useState12(null);
5389
5473
  useEffect4(() => core.subscribe(setEntries), [core]);
5390
5474
  const groups = useMemo3(() => {
5391
5475
  const byCuid = new Map(entries.map((e) => [e.cuid, e]));
@@ -5554,7 +5638,7 @@ function NiceActionDevtools_Panel({
5554
5638
  style: { width: "100%", height: "100%", overflowY: "auto" }
5555
5639
  }, undefined, false, undefined, this)
5556
5640
  }, undefined, false, undefined, this),
5557
- selectedEntry != null && /* @__PURE__ */ jsxDEV22(Fragment10, {
5641
+ selectedEntry != null && /* @__PURE__ */ jsxDEV22(Fragment11, {
5558
5642
  children: [
5559
5643
  /* @__PURE__ */ jsxDEV22(SplitHandle, {
5560
5644
  horizontal: isHorizDock,
@@ -151,6 +151,8 @@ function extractRouting(context) {
151
151
  insId: handler.client.insId
152
152
  } : undefined,
153
153
  transport: isExternal ? handler.transType : undefined,
154
+ transportSummary: isExternal ? handler.transInfo?.summary : undefined,
155
+ transportUrl: isExternal ? handler.transInfo?.url : undefined,
154
156
  time: item.time
155
157
  };
156
158
  });