@nice-code/action 0.6.2 → 0.6.3

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.
@@ -5516,6 +5516,7 @@ var PREFS_KEY = "__nice-action-devtools-prefs";
5516
5516
  var DOCKED_HEIGHT_DEFAULT = 320;
5517
5517
  var DOCKED_WIDTH_DEFAULT = 420;
5518
5518
  var DETAIL_RATIO_DEFAULT = 0.5;
5519
+ var SANS_FONT2 = "ui-sans-serif, system-ui, sans-serif";
5519
5520
  var DOCK_POSITIONS = ["dock-bottom", "dock-top", "dock-left", "dock-right"];
5520
5521
  function isDockPosition(value) {
5521
5522
  return typeof value === "string" && DOCK_POSITIONS.includes(value);
@@ -5526,7 +5527,9 @@ function readPrefs(defaultPosition, initialOpen) {
5526
5527
  isOpen: initialOpen,
5527
5528
  dockedHeight: DOCKED_HEIGHT_DEFAULT,
5528
5529
  dockedWidth: DOCKED_WIDTH_DEFAULT,
5529
- detailRatio: DETAIL_RATIO_DEFAULT
5530
+ detailRatio: DETAIL_RATIO_DEFAULT,
5531
+ stayOnLatest: true,
5532
+ followLatestOnSelect: true
5530
5533
  };
5531
5534
  try {
5532
5535
  if (typeof localStorage === "undefined")
@@ -5555,15 +5558,23 @@ function getHandlerKey(entry) {
5555
5558
  return "local";
5556
5559
  return `ext:${hop.transport ?? "ext"}`;
5557
5560
  }
5561
+ function entriesShareActionInput(a, b) {
5562
+ if (a.actionId !== b.actionId || a.domain !== b.domain)
5563
+ return false;
5564
+ return a.inputHash != null && b.inputHash != null ? a.inputHash === b.inputHash : safeStringify(a.input, 0) === safeStringify(b.input, 0);
5565
+ }
5558
5566
  function canGroupWith(a, b) {
5559
- if (a.status === "running" || b.status === "running")
5567
+ if (!entriesShareActionInput(a, b))
5560
5568
  return false;
5561
5569
  const handlerA = getHandlerKey(a);
5562
5570
  const handlerB = getHandlerKey(b);
5563
5571
  const handlerConflict = handlerA !== "none" && handlerB !== "none" && handlerA !== handlerB;
5564
- const inputMatch = a.inputHash != null && b.inputHash != null ? a.inputHash === b.inputHash : safeStringify(a.input, 0) === safeStringify(b.input, 0);
5572
+ if (handlerConflict)
5573
+ return false;
5574
+ if (a.status === "running" || b.status === "running")
5575
+ return true;
5565
5576
  const outputMatch = a.outputHash != null && b.outputHash != null ? a.outputHash === b.outputHash : true;
5566
- return a.actionId === b.actionId && a.domain === b.domain && a.status === b.status && !handlerConflict && inputMatch && outputMatch;
5577
+ return a.status === b.status && outputMatch;
5567
5578
  }
5568
5579
  function groupEntries(entries) {
5569
5580
  const groups = [];
@@ -5596,7 +5607,7 @@ function NiceActionDevtools_Panel({
5596
5607
  useEffect4(() => core.subscribe(setEntries), [core]);
5597
5608
  const groups = useMemo3(() => {
5598
5609
  const byCuid = new Map(entries.map((e) => [e.cuid, e]));
5599
- const roots = entries.filter((e) => e.status !== "running" && (e.parentCuid == null || !byCuid.has(e.parentCuid)));
5610
+ const roots = entries.filter((e) => e.parentCuid == null || !byCuid.has(e.parentCuid));
5600
5611
  return groupEntries(roots);
5601
5612
  }, [entries]);
5602
5613
  const childEntriesMap = useMemo3(() => {
@@ -5612,16 +5623,6 @@ function NiceActionDevtools_Panel({
5612
5623
  }
5613
5624
  return map;
5614
5625
  }, [entries]);
5615
- const handleGroupRowClick = (group) => {
5616
- const repCuid = group.representative.cuid;
5617
- const allInGroup = [group.representative, ...group.rest];
5618
- const selectedInGroup = allInGroup.find((e) => e.cuid === selectedCuid) ?? null;
5619
- if (selectedInGroup != null && selectedCuid !== repCuid) {
5620
- setSelectedCuid(repCuid);
5621
- } else {
5622
- setSelectedCuid(selectedCuid === repCuid ? null : repCuid);
5623
- }
5624
- };
5625
5626
  const setPrefs = (update) => {
5626
5627
  setPrefsRaw((prev) => ({ ...prev, ...update }));
5627
5628
  };
@@ -5629,10 +5630,34 @@ function NiceActionDevtools_Panel({
5629
5630
  const timer = setTimeout(() => writePrefs(prefs), 250);
5630
5631
  return () => clearTimeout(timer);
5631
5632
  }, [prefs]);
5632
- const { position, isOpen, dockedHeight, dockedWidth, detailRatio } = prefs;
5633
+ const { position, isOpen, dockedHeight, dockedWidth, detailRatio, stayOnLatest, followLatestOnSelect } = prefs;
5633
5634
  const dockSide = getDockSide(position);
5634
5635
  const isHorizDock = dockSide === "top" || dockSide === "bottom";
5635
5636
  const dockedSize = isHorizDock ? dockedHeight : dockedWidth;
5637
+ const latestCuid = groups.length > 0 ? groups[0].representative.cuid : null;
5638
+ useEffect4(() => {
5639
+ if (stayOnLatest && latestCuid != null)
5640
+ setSelectedCuid(latestCuid);
5641
+ }, [stayOnLatest, latestCuid]);
5642
+ const applySelection = (next) => {
5643
+ if (next != null && next === latestCuid && followLatestOnSelect) {
5644
+ if (!stayOnLatest)
5645
+ setPrefs({ stayOnLatest: true });
5646
+ } else if (stayOnLatest) {
5647
+ setPrefs({ stayOnLatest: false });
5648
+ }
5649
+ setSelectedCuid(next);
5650
+ };
5651
+ const handleGroupRowClick = (group) => {
5652
+ const repCuid = group.representative.cuid;
5653
+ const allInGroup = [group.representative, ...group.rest];
5654
+ const selectedInGroup = allInGroup.find((e) => e.cuid === selectedCuid) ?? null;
5655
+ if (selectedInGroup != null && selectedCuid !== repCuid) {
5656
+ applySelection(repCuid);
5657
+ } else {
5658
+ applySelection(selectedCuid === repCuid ? null : repCuid);
5659
+ }
5660
+ };
5636
5661
  const selectedEntry = selectedCuid != null ? entries.find((e) => e.cuid === selectedCuid) : null;
5637
5662
  const runningCount = entries.filter((e) => e.status === "running").length;
5638
5663
  const dock = useMemo3(() => getDevtoolsDockCoordinator(), []);
@@ -5716,7 +5741,7 @@ function NiceActionDevtools_Panel({
5716
5741
  selectedCuid,
5717
5742
  onGroupClick: handleGroupRowClick,
5718
5743
  onSubClick: (cuid, isSelected) => {
5719
- setSelectedCuid(isSelected ? null : cuid);
5744
+ applySelection(isSelected ? null : cuid);
5720
5745
  },
5721
5746
  childEntriesMap
5722
5747
  };
@@ -5748,18 +5773,35 @@ function NiceActionDevtools_Panel({
5748
5773
  minHeight: 0
5749
5774
  },
5750
5775
  children: [
5751
- /* @__PURE__ */ jsx23("div", {
5776
+ /* @__PURE__ */ jsxs20("div", {
5752
5777
  style: {
5753
5778
  flexGrow: selectedEntry != null ? 1 - detailRatio : 1,
5754
5779
  flexShrink: 1,
5755
5780
  flexBasis: 0,
5756
5781
  minWidth: 0,
5757
- minHeight: 0
5782
+ minHeight: 0,
5783
+ display: "flex",
5784
+ flexDirection: "column",
5785
+ overflow: "hidden"
5758
5786
  },
5759
- children: /* @__PURE__ */ jsx23(ActionList, {
5760
- ...virtualListProps,
5761
- style: { width: "100%", height: "100%", overflowY: "auto" }
5762
- })
5787
+ children: [
5788
+ /* @__PURE__ */ jsx23(FollowToggles, {
5789
+ stayOnLatest,
5790
+ onStayOnLatestChange: (next) => setPrefs({ stayOnLatest: next }),
5791
+ followLatestOnSelect,
5792
+ onFollowLatestOnSelectChange: (next) => {
5793
+ if (next && latestCuid != null && selectedCuid === latestCuid && !stayOnLatest) {
5794
+ setPrefs({ followLatestOnSelect: next, stayOnLatest: true });
5795
+ } else {
5796
+ setPrefs({ followLatestOnSelect: next });
5797
+ }
5798
+ }
5799
+ }),
5800
+ /* @__PURE__ */ jsx23(ActionList, {
5801
+ ...virtualListProps,
5802
+ style: { width: "100%", flex: 1, minHeight: 0, overflowY: "auto" }
5803
+ })
5804
+ ]
5763
5805
  }),
5764
5806
  selectedEntry != null && /* @__PURE__ */ jsxs20(Fragment11, {
5765
5807
  children: [
@@ -5799,6 +5841,82 @@ function NiceActionDevtools_Panel({
5799
5841
  ]
5800
5842
  });
5801
5843
  }
5844
+ function FollowToggles({
5845
+ stayOnLatest,
5846
+ onStayOnLatestChange,
5847
+ followLatestOnSelect,
5848
+ onFollowLatestOnSelectChange
5849
+ }) {
5850
+ return /* @__PURE__ */ jsxs20("div", {
5851
+ style: {
5852
+ display: "flex",
5853
+ flexDirection: "column",
5854
+ flexShrink: 0,
5855
+ paddingBottom: "3px",
5856
+ background: DEVTOOL_SECTION_BACKGROUND,
5857
+ borderBottom: `1px solid ${DEVTOOL_LIST_BASE_BACKGROUND}`
5858
+ },
5859
+ children: [
5860
+ /* @__PURE__ */ jsx23(ToggleLabel, {
5861
+ title: "Auto-select the most recent action so the detail pane keeps showing the latest as new actions land",
5862
+ checked: stayOnLatest,
5863
+ onChange: onStayOnLatestChange,
5864
+ children: "Follow latest"
5865
+ }),
5866
+ /* @__PURE__ */ jsxs20("div", {
5867
+ style: { display: "flex", alignItems: "center", paddingLeft: "12px", marginTop: "-4px" },
5868
+ children: [
5869
+ /* @__PURE__ */ jsx23("span", {
5870
+ "aria-hidden": true,
5871
+ style: {
5872
+ color: DEVTOOL_COLOR_TEXT_MUTED,
5873
+ fontFamily: SANS_FONT2,
5874
+ fontSize: "10px",
5875
+ lineHeight: 1
5876
+ },
5877
+ children: "└"
5878
+ }),
5879
+ /* @__PURE__ */ jsx23(ToggleLabel, {
5880
+ title: "When you click the latest action, turn 'Follow latest' back on so the view resumes tracking new actions. Turn this off to pin exactly to the action you click instead.",
5881
+ checked: followLatestOnSelect,
5882
+ onChange: onFollowLatestOnSelectChange,
5883
+ children: "clicking latest re-follows"
5884
+ })
5885
+ ]
5886
+ })
5887
+ ]
5888
+ });
5889
+ }
5890
+ function ToggleLabel({
5891
+ checked,
5892
+ onChange,
5893
+ title,
5894
+ children
5895
+ }) {
5896
+ return /* @__PURE__ */ jsxs20("label", {
5897
+ title,
5898
+ style: {
5899
+ display: "flex",
5900
+ alignItems: "center",
5901
+ gap: "6px",
5902
+ padding: "5px 10px",
5903
+ cursor: "pointer",
5904
+ userSelect: "none",
5905
+ color: checked ? DEVTOOL_COLOR_TEXT_SECONDARY : DEVTOOL_COLOR_TEXT_MUTED,
5906
+ fontSize: "10px",
5907
+ fontFamily: SANS_FONT2
5908
+ },
5909
+ children: [
5910
+ /* @__PURE__ */ jsx23("input", {
5911
+ type: "checkbox",
5912
+ checked,
5913
+ onChange: (e) => onChange(e.target.checked),
5914
+ style: { accentColor: DEVTOOL_COLOR_SEMANTIC_SYSTEM, cursor: "pointer", margin: 0 }
5915
+ }),
5916
+ children
5917
+ ]
5918
+ });
5919
+ }
5802
5920
  export {
5803
5921
  NiceActionDevtools,
5804
5922
  ActionDevtoolsCore
package/build/index.js CHANGED
@@ -1803,20 +1803,24 @@ class ConnectionTransportManager {
1803
1803
  addTransport(transport) {
1804
1804
  this._transports.push(transport);
1805
1805
  }
1806
+ getPreferredTransport() {
1807
+ return this._transports[0];
1808
+ }
1806
1809
  async getReadyTransport(routeActionParams) {
1807
- const initializingWaiters = [];
1808
- const unavailableTransports = [];
1809
1810
  const action = routeActionParams.action;
1811
+ const candidates = [];
1812
+ const unavailableTransports = [];
1810
1813
  for (const transport of this._transports) {
1811
1814
  const cacheKey = transport.getCacheKey(routeActionParams);
1812
1815
  if (cacheKey != null) {
1813
1816
  const cached = this._cache.get(cacheKey);
1814
1817
  if (cached != null) {
1815
1818
  if (cached instanceof Promise) {
1816
- initializingWaiters.push(cached);
1819
+ candidates.push(cached);
1817
1820
  continue;
1818
1821
  }
1819
- return { ...cached, transport };
1822
+ candidates.push(Promise.resolve({ ...cached, transport }));
1823
+ break;
1820
1824
  }
1821
1825
  }
1822
1826
  const statusInfo = transport.getTransport(routeActionParams);
@@ -1826,7 +1830,8 @@ class ConnectionTransportManager {
1826
1830
  this._cache.set(cacheKey, { methods: readyData, transport });
1827
1831
  readyData.addOnDisconnectListener?.(() => this._cache.delete(cacheKey));
1828
1832
  }
1829
- return { methods: readyData, transport };
1833
+ candidates.push(Promise.resolve({ methods: readyData, transport }));
1834
+ break;
1830
1835
  }
1831
1836
  if (statusInfo.status === "unsupported" /* unsupported */) {
1832
1837
  unavailableTransports.push(transport);
@@ -1856,10 +1861,10 @@ class ConnectionTransportManager {
1856
1861
  if (cacheKey != null) {
1857
1862
  this._cache.set(cacheKey, promise);
1858
1863
  }
1859
- initializingWaiters.push(promise);
1864
+ candidates.push(promise);
1860
1865
  }
1861
1866
  }
1862
- if (initializingWaiters.length === 0) {
1867
+ if (candidates.length === 0) {
1863
1868
  if (unavailableTransports.length > 0) {
1864
1869
  throw err_nice_transport.fromId("unsupported" /* unsupported */, {
1865
1870
  transportTypes: unavailableTransports.map((t) => t.type)
@@ -1869,13 +1874,17 @@ class ConnectionTransportManager {
1869
1874
  actionId: action.id
1870
1875
  });
1871
1876
  }
1872
- try {
1873
- return await Promise.any(initializingWaiters).then();
1874
- } catch (e) {
1875
- throw err_nice_transport.fromId("initialization_failed" /* initialization_failed */, {
1876
- actionId: action.id
1877
- }).withOriginError(e);
1877
+ let lastError;
1878
+ for (const candidate of candidates) {
1879
+ try {
1880
+ return await candidate;
1881
+ } catch (e) {
1882
+ lastError = e;
1883
+ }
1878
1884
  }
1885
+ throw err_nice_transport.fromId("initialization_failed" /* initialization_failed */, {
1886
+ actionId: action.id
1887
+ }).withOriginError(lastError);
1879
1888
  }
1880
1889
  }
1881
1890
 
@@ -1935,20 +1944,19 @@ class ActionExternalClientHandler extends ActionHandler {
1935
1944
  const incomingTimeout = config?.timeout ?? this._defaultTimeout;
1936
1945
  const parentCuid = peekHandlerCuid();
1937
1946
  const callSite = action._callSite ?? new Error().stack;
1938
- const { methods, transport } = await this.transportManager.getReadyTransport({
1947
+ const routeParams = {
1939
1948
  action,
1940
1949
  localClient,
1941
1950
  externalClient: this.externalClient
1942
- });
1943
- action.context.addRouteItem({
1951
+ };
1952
+ const preferredTransport = this.transportManager.getPreferredTransport();
1953
+ const routeItem = preferredTransport != null ? {
1944
1954
  runtime: localClient,
1945
- handler: this.toHandlerRouteItem(transport, {
1946
- action,
1947
- localClient,
1948
- externalClient: this.externalClient
1949
- }),
1955
+ handler: this.toHandlerRouteItem(preferredTransport, routeParams),
1950
1956
  time: Date.now()
1951
- });
1957
+ } : undefined;
1958
+ if (routeItem != null)
1959
+ action.context.addRouteItem(routeItem);
1952
1960
  const runningAction = new RunningAction({
1953
1961
  context: action.context,
1954
1962
  request: action,
@@ -1956,23 +1964,37 @@ class ActionExternalClientHandler extends ActionHandler {
1956
1964
  callSite
1957
1965
  });
1958
1966
  localRuntime.registerRunningAction(runningAction);
1959
- const routeActionParams = {
1960
- action,
1961
- runningAction,
1962
- localClient,
1963
- externalClient: this.externalClient,
1964
- timeout: incomingTimeout
1965
- };
1966
- if (action.type === "request" /* request */ && methods.updateRunConfig != null) {
1967
- const runConfig = methods.updateRunConfig(routeActionParams);
1968
- routeActionParams.timeout = runConfig?.timeout ?? incomingTimeout;
1969
- }
1967
+ this._dispatchWhenTransportReady(runningAction, routeParams, routeItem, incomingTimeout);
1968
+ return runningAction;
1969
+ }
1970
+ async _dispatchWhenTransportReady(runningAction, routeParams, routeItem, incomingTimeout) {
1971
+ const action = routeParams.action;
1970
1972
  try {
1971
- methods.sendActionData(routeActionParams);
1973
+ const { methods, transport } = await this.transportManager.getReadyTransport(routeParams);
1974
+ const handlerRouteItem = this.toHandlerRouteItem(transport, routeParams);
1975
+ if (routeItem != null) {
1976
+ routeItem.handler = handlerRouteItem;
1977
+ routeItem.time = Date.now();
1978
+ } else {
1979
+ action.context.addRouteItem({
1980
+ runtime: routeParams.localClient,
1981
+ handler: handlerRouteItem,
1982
+ time: Date.now()
1983
+ });
1984
+ }
1985
+ const sendInput = {
1986
+ ...routeParams,
1987
+ runningAction,
1988
+ timeout: incomingTimeout
1989
+ };
1990
+ if (action.type === "request" /* request */ && methods.updateRunConfig != null) {
1991
+ const runConfig = methods.updateRunConfig(sendInput);
1992
+ sendInput.timeout = runConfig?.timeout ?? incomingTimeout;
1993
+ }
1994
+ methods.sendActionData(sendInput);
1972
1995
  } catch (err3) {
1973
1996
  runningAction._abort(err3);
1974
1997
  }
1975
- return runningAction;
1976
1998
  }
1977
1999
  async sendReturnPayload(payload, config) {
1978
2000
  const localClient = config.targetLocalRuntime.coordinate;
@@ -27,6 +27,7 @@ export declare class ActionExternalClientHandler extends ActionHandler<EActionHa
27
27
  forActionIds<ACT_DOM extends IActionDomain, IDS extends ReadonlyArray<keyof ACT_DOM["actionSchema"] & string>>(domain: ActionDomain<ACT_DOM>, ids: IDS): this;
28
28
  _setIncomingActionDataListener(listener: (json: TActionPayload_Any_JsonObject<any, any>) => void): void;
29
29
  handleActionRequest<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: ActionPayload_Request<DOM, ID>, config?: IHandleActionOptions): Promise<RunningAction<DOM, ID>>;
30
+ private _dispatchWhenTransportReady;
30
31
  /**
31
32
  * Dispatch a result or progress payload directly back to the external client via the best
32
33
  * available bidirectional transport (WebSocket / Custom). Used for return-path routing when the
@@ -5,5 +5,12 @@ export declare class ConnectionTransportManager {
5
5
  private _transports;
6
6
  constructor(_cache: TTransportCache);
7
7
  addTransport(transport: TransportConnection): void;
8
+ /**
9
+ * The highest-priority transport (first declared). Used to label an action's route *before* the
10
+ * transport has finished connecting — so a still-connecting action shows its (expected) destination
11
+ * instead of an "unknown" hop. {@link getReadyTransport} still decides the real winner, and the
12
+ * caller corrects the hop if a lower-priority transport ends up serving the action.
13
+ */
14
+ getPreferredTransport(): TransportConnection | undefined;
8
15
  getReadyTransport(routeActionParams: ITransportRouteActionParams): Promise<IActionTransportReady>;
9
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nice-code/action",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -44,9 +44,9 @@
44
44
  "build-types": "tsc --project tsconfig.build.json"
45
45
  },
46
46
  "dependencies": {
47
- "@nice-code/common-errors": "0.6.2",
48
- "@nice-code/error": "0.6.2",
49
- "@nice-code/util": "0.6.2",
47
+ "@nice-code/common-errors": "0.6.3",
48
+ "@nice-code/error": "0.6.3",
49
+ "@nice-code/util": "0.6.3",
50
50
  "@standard-schema/spec": "^1.1.0",
51
51
  "@tanstack/react-virtual": "^3.13.26",
52
52
  "http-status-codes": "^2.3.0",