dsh-hyperliquid 0.3.0 → 0.4.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.
package/client.js CHANGED
@@ -148,7 +148,7 @@ var styles_default = `/**
148
148
  .dhl-chat-heading span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary); font-size: 10.5px; }
149
149
  .dhl-chat-ticket-toggle { flex: none; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary); padding: 5px 9px; font: inherit; font-size: 11px; cursor: pointer; }
150
150
  .dhl-chat-ticket-toggle:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
151
- .dhl-chat-prompts { flex: none; display: flex; gap: 6px; padding: 8px 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
151
+ .dhl-chat-prompts { flex: none; display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
152
152
  .dhl-chat-prompts button { border: 1px solid var(--dsw-alias-border-l1); border-radius: 999px; background: transparent; color: var(--dsw-alias-label-secondary); padding: 4px 9px; font: inherit; font-size: 10.5px; cursor: pointer; }
153
153
  .dhl-chat-prompts button:hover { color: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
154
154
  .dhl-chat-unavailable { margin: 18px; padding: 13px 14px; border: 1px solid var(--dsw-alias-state-warn-primary); border-radius: 8px; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 1.6; }
@@ -471,7 +471,7 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
471
471
  .dhl-chart-tools { min-height: 42px; }
472
472
  .dhl-chartbox { height: 410px; }
473
473
  /* Above Lightweight Charts' own canvases (z-index 1\u20132, later in the DOM) so drawings receive the pointer. */
474
- .dhl-chart-drawing-overlay { position: absolute; inset: 0; z-index: 4; width: 100%; height: 100%; overflow: hidden; pointer-events: none; color: #4c82d8; }
474
+ .dhl-chart-drawing-overlay { position: absolute; inset: 0; z-index: 4; width: 100%; height: 100%; overflow: hidden; pointer-events: none; color: #4c82d8; contain: layout paint; transform: translateZ(0); }
475
475
  .dhl-chart-drawing-overlay .role-user { color: #4c82d8; }
476
476
  .dhl-chart-drawing-overlay .role-support { color: var(--dsw-alias-state-success-primary); }
477
477
  .dhl-chart-drawing-overlay .role-resistance { color: var(--dsw-alias-state-error-primary); }
@@ -707,7 +707,7 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
707
707
  /* Direct manipulation: a transparent, wide hit layer on strokes (empty chart space still pans). */
708
708
  .dhl-drawing-hit * { stroke: transparent !important; fill: transparent !important; stroke-dasharray: none !important; stroke-width: 12px !important; pointer-events: stroke; cursor: move; }
709
709
  .dhl-drawing-hit .dhl-text-box, .dhl-drawing-hit .dhl-position rect, .dhl-drawing-hit text { pointer-events: all; }
710
- .dhl-chart-drawing-overlay.drawing .dhl-drawing-hit { display: none; }
710
+ .dhl-chart-drawing-overlay.drawing .dhl-drawing-hit, .dhl-canvas-gesture .dhl-drawing-hit { display: none; }
711
711
  .dhl-drawing.locked .dhl-drawing-hit * { cursor: default; }
712
712
  .dhl-drawing:has(> .dhl-drawing-hit:hover) > line, .dhl-drawing:has(> .dhl-drawing-hit:hover) > g > line, .dhl-drawing.selected > line, .dhl-drawing.selected > g > line { stroke-width: calc(var(--dhl-line-width, 1.8) + 1); }
713
713
  .dhl-drawing.selected { filter: drop-shadow(0 0 3px color-mix(in srgb, currentColor 55%, transparent)); }
@@ -908,7 +908,7 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
908
908
 
909
909
  /* Fixed range volume profile: up/down volume per price row, value area brighter, POC in the alert red. */
910
910
  .dhl-vprofile-range { fill: color-mix(in srgb, currentColor 5%, transparent); stroke: none; }
911
- .dhl-vprofile rect.dhl-vprofile-up, .dhl-vprofile rect.dhl-vprofile-down { stroke: none; }
911
+ .dhl-vprofile .dhl-vprofile-up, .dhl-vprofile .dhl-vprofile-down { stroke: none; }
912
912
  .dhl-vprofile-up { fill: color-mix(in srgb, #4c82d8 62%, transparent); }
913
913
  .dhl-vprofile-down { fill: color-mix(in srgb, #e2a943 62%, transparent); }
914
914
  .dhl-vprofile-out .dhl-vprofile-up { fill: color-mix(in srgb, #4c82d8 26%, transparent); }
@@ -919,6 +919,33 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
919
919
  /* Magnet feedback: a ring on the O/H/L/C value the anchor will snap to. */
920
920
  .dhl-snap-ring { fill: none; stroke: var(--dsw-alias-brand-primary, #4c82d8); stroke-width: 1.5; pointer-events: none; }
921
921
  .dhl-draw-toolbar .dhl-select > select { height: 26px; font-size: 11px; }
922
+ .dhl-draw-toolbar .dhl-fib-levels { width: 190px; }
923
+
924
+ .dhl-badge.managed { color: var(--dsw-alias-brand-primary, #4c82d8); border-color: color-mix(in srgb, var(--dsw-alias-brand-primary, #4c82d8) 40%, transparent); background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4c82d8) 10%, transparent); }
925
+ .dhl-managed-list { margin: 6px 0 10px; padding-left: 20px; line-height: 1.7; }
926
+
927
+ /* Trading limits dialog: tabs keep it short; when the viewport is still too small the card caps at the
928
+ overlay's padding box (DSH Modal contract: max-height 100%) and only the body scrolls \u2014 header and
929
+ footer (the save button) stay in view. */
930
+ .dhl-limits-modal { max-height: 100%; }
931
+ .dhl-limits-content { flex: 1 1 auto; min-height: 0; }
932
+ .dhl-limits-content > :has(> .dhl-limits-scroll) { flex: 1 1 auto; min-height: 0; overflow: hidden; margin-top: 12px; }
933
+ .dhl-limits-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: 0 -24px; padding: 14px 24px 2px; scrollbar-gutter: stable; }
934
+ .dhl-limits-tabs { flex: none; display: flex; gap: 4px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
935
+ .dhl-limits-tabs button { position: relative; height: 34px; padding: 0 12px; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; cursor: pointer; }
936
+ .dhl-limits-tabs button:hover { color: var(--dsw-alias-label-primary); }
937
+ .dhl-limits-tabs button.on { color: var(--dsw-alias-label-primary); font-weight: 600; }
938
+ .dhl-limits-tabs button.on::after { content: ''; position: absolute; left: 10px; right: 10px; bottom: -1px; height: 2px; border-radius: 1px; background: var(--dsw-alias-label-primary); }
939
+ .dhl-limits-tabs button:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; border-radius: 6px; }
940
+ .dhl-limits-section + .dhl-limits-section { margin-top: 0; padding-top: 0; border-top: 0; }
941
+ .dhl-limits-ledger-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 0 0 12px; color: var(--dsw-alias-label-secondary); font-size: 12px; }
942
+ .dhl-limits-scroll .dhl-limits-note { margin-top: 10px; font-size: 12px; line-height: 18px; }
943
+ .dhl-limits-facts { display: grid; grid-template-columns: auto 1fr; gap: 10px 14px; margin: 0; font-size: 13px; line-height: 20px; }
944
+ .dhl-limits-facts dt { color: var(--dsw-alias-label-secondary); white-space: nowrap; }
945
+ .dhl-limits-facts dd { margin: 0; color: var(--dsw-alias-label-primary); }
946
+ .dhl-limits-hint { display: block; margin-top: 2px; color: var(--dsw-alias-label-secondary); font-size: 12px; }
947
+ .dhl-limits-scroll .dhl-risk-error { margin: 10px 0 0; }
948
+ .dhl-wallet-form ~ .dhl-limits-facts { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); }
922
949
  `;
923
950
 
924
951
  // src/client/locale.ts
@@ -1297,8 +1324,15 @@ function t(key3, fallback) {
1297
1324
  return localeT(key3, fallback);
1298
1325
  }
1299
1326
  function tr(zh, en) {
1300
- return ctxRef?.get?.("locale")?.getLocale?.()?.active === "en" ? en : zh;
1327
+ const now = Date.now();
1328
+ if (now - localeReadAt > 250) {
1329
+ localeReadAt = now;
1330
+ english = ctxRef?.get?.("locale")?.getLocale?.()?.active === "en";
1331
+ }
1332
+ return english ? en : zh;
1301
1333
  }
1334
+ var localeReadAt = -Infinity;
1335
+ var english = false;
1302
1336
  async function hyperCall(method, request = null) {
1303
1337
  if (!conn) throw new Error(t("error.noConnection", "\u65E0\u8FDE\u63A5"));
1304
1338
  if (typeof conn.rpc?.call !== "function") throw new Error(t("error.callFailed", "\u8FDC\u7AEF\u8C03\u7528\u5931\u8D25") + `: hyper.${method}`);
@@ -1647,23 +1681,23 @@ function selectChartCandle(candles, time, active, hit, maLines = [], settings) {
1647
1681
  const priorVolumeMean20 = previous.length === 20 ? previous.reduce((sum, value) => sum + value, 0) / 20 : null;
1648
1682
  return { candle, hit, values, priorVolumeMean20, settings };
1649
1683
  }
1650
- function formatChartReference(selection, coin, interval, english = false) {
1684
+ function formatChartReference(selection, coin, interval, english2 = false) {
1651
1685
  const { candle, hit, values, priorVolumeMean20 } = selection;
1652
1686
  const number = (value) => String(value);
1653
1687
  const computed = (value) => String(Number(value.toPrecision(10)));
1654
1688
  const time = new Date(candle.t).toISOString().replace("T", " ").replace(".000Z", " UTC");
1655
1689
  const hitLabel = {
1656
- volume: english ? "Volume" : "\u6210\u4EA4\u91CF",
1690
+ volume: english2 ? "Volume" : "\u6210\u4EA4\u91CF",
1657
1691
  sma20: "SMA20",
1658
1692
  sma60: "MA60",
1659
1693
  ema20: "EMA20",
1660
1694
  ema50: "EMA50",
1661
- bollingerUpper: english ? "BOLL upper" : "BOLL\u4E0A\u8F68",
1662
- bollingerLower: english ? "BOLL lower" : "BOLL\u4E0B\u8F68",
1695
+ bollingerUpper: english2 ? "BOLL upper" : "BOLL\u4E0A\u8F68",
1696
+ bollingerLower: english2 ? "BOLL lower" : "BOLL\u4E0B\u8F68",
1663
1697
  rsi14: `RSI${selection.settings?.rsi.period ?? 14}`,
1664
1698
  macd: "MACD",
1665
- macdSignal: english ? "MACD signal" : "\u4FE1\u53F7\u7EBF",
1666
- macdHistogram: english ? "MACD histogram" : "MACD\u67F1",
1699
+ macdSignal: english2 ? "MACD signal" : "\u4FE1\u53F7\u7EBF",
1700
+ macdHistogram: english2 ? "MACD histogram" : "MACD\u67F1",
1667
1701
  atr14: "ATR14",
1668
1702
  vwap: "VWAP (UTC HLC3)",
1669
1703
  stochastic: "Stochastic",
@@ -1671,17 +1705,17 @@ function formatChartReference(selection, coin, interval, english = false) {
1671
1705
  obv: "OBV"
1672
1706
  };
1673
1707
  const lines = [
1674
- english ? `[Chart point] ${coin} ${interval} candle opened ${time}` : `\u3010\u56FE\u8868\u70B9\u4F4D\u3011${coin} ${interval} K \u7EBF\u5F00\u76D8 ${time}`,
1675
- english ? `OHLC: ${number(candle.o)} / ${number(candle.h)} / ${number(candle.l)} / ${number(candle.c)}` : `\u5F00/\u9AD8/\u4F4E/\u6536\uFF1A${number(candle.o)} / ${number(candle.h)} / ${number(candle.l)} / ${number(candle.c)}`,
1676
- english ? `Volume: ${number(candle.v)}` : `\u6210\u4EA4\u91CF\uFF1A${number(candle.v)}`
1708
+ english2 ? `[Chart point] ${coin} ${interval} candle opened ${time}` : `\u3010\u56FE\u8868\u70B9\u4F4D\u3011${coin} ${interval} K \u7EBF\u5F00\u76D8 ${time}`,
1709
+ english2 ? `OHLC: ${number(candle.o)} / ${number(candle.h)} / ${number(candle.l)} / ${number(candle.c)}` : `\u5F00/\u9AD8/\u4F4E/\u6536\uFF1A${number(candle.o)} / ${number(candle.h)} / ${number(candle.l)} / ${number(candle.c)}`,
1710
+ english2 ? `Volume: ${number(candle.v)}` : `\u6210\u4EA4\u91CF\uFF1A${number(candle.v)}`
1677
1711
  ];
1678
1712
  if (priorVolumeMean20 != null && priorVolumeMean20 > 0) {
1679
- lines.push(english ? `Previous 20-candle mean volume: ${computed(priorVolumeMean20)}; current/mean: ${(candle.v / priorVolumeMean20).toFixed(2)}x` : `\u524D 20 \u6839\u5747\u91CF\uFF1A${computed(priorVolumeMean20)}\uFF1B\u5F53\u524D/\u5747\u91CF\uFF1A${(candle.v / priorVolumeMean20).toFixed(2)} \u500D`);
1713
+ lines.push(english2 ? `Previous 20-candle mean volume: ${computed(priorVolumeMean20)}; current/mean: ${(candle.v / priorVolumeMean20).toFixed(2)}x` : `\u524D 20 \u6839\u5747\u91CF\uFF1A${computed(priorVolumeMean20)}\uFF1B\u5F53\u524D/\u5747\u91CF\uFF1A${(candle.v / priorVolumeMean20).toFixed(2)} \u500D`);
1680
1714
  }
1681
- if (selection.settings) lines.push(`${english ? "Study parameters" : "\u6307\u6807\u53C2\u6570"}\uFF1A${JSON.stringify(selection.settings)}`);
1682
- if (values.some((row) => row.label === "OBV")) lines.push(english ? "OBV starts at zero on the first loaded candle; its absolute value depends on the window." : "OBV \u4EE5\u5DF2\u52A0\u8F7D\u9996\u6839\u4E3A\u96F6\u70B9\uFF0C\u7EDD\u5BF9\u503C\u53D6\u51B3\u4E8E\u5386\u53F2\u7A97\u53E3\u3002");
1683
- if (values.length) lines.push(`${english ? "Displayed studies" : "\u5DF2\u663E\u793A\u6307\u6807"}\uFF1A${values.map(({ label, value }) => `${label}=${computed(value)}`).join("\uFF0C")}`);
1684
- if (hit && hit !== "volume") lines.push(`${english ? "Selected study" : "\u70B9\u51FB\u6307\u6807"}\uFF1A${hitLabel[hit] ?? hit}`);
1715
+ if (selection.settings) lines.push(`${english2 ? "Study parameters" : "\u6307\u6807\u53C2\u6570"}\uFF1A${JSON.stringify(selection.settings)}`);
1716
+ if (values.some((row) => row.label === "OBV")) lines.push(english2 ? "OBV starts at zero on the first loaded candle; its absolute value depends on the window." : "OBV \u4EE5\u5DF2\u52A0\u8F7D\u9996\u6839\u4E3A\u96F6\u70B9\uFF0C\u7EDD\u5BF9\u503C\u53D6\u51B3\u4E8E\u5386\u53F2\u7A97\u53E3\u3002");
1717
+ if (values.length) lines.push(`${english2 ? "Displayed studies" : "\u5DF2\u663E\u793A\u6307\u6807"}\uFF1A${values.map(({ label, value }) => `${label}=${computed(value)}`).join("\uFF0C")}`);
1718
+ if (hit && hit !== "volume") lines.push(`${english2 ? "Selected study" : "\u70B9\u51FB\u6307\u6807"}\uFF1A${hitLabel[hit] ?? hit}`);
1685
1719
  return lines.join("\n");
1686
1720
  }
1687
1721
  var CHART_REFERENCE_SOURCE = "hyperliquid-chart";
@@ -1851,6 +1885,11 @@ function resetModeView(mode) {
1851
1885
  });
1852
1886
  void Promise.allSettled([loadStatus(), loadAccount(), loadTradeHistory(true)]);
1853
1887
  }
1888
+ function resetWalletView() {
1889
+ networkEpoch += 1;
1890
+ store.patch({ account: null, accountAt: 0, accountError: null, tradeHistory: null, tradeHistoryAt: 0, tradeHistoryError: null });
1891
+ void Promise.allSettled([loadStatus(), loadAccount(), loadTradeHistory(true)]);
1892
+ }
1854
1893
  async function switchTradingMode(mode) {
1855
1894
  const result = await hyperCall("switchTradingMode", { mode, acknowledgeLive: mode === "live" });
1856
1895
  if (result?.mode !== mode) throw new Error(tr("\u4EA4\u6613\u6A21\u5F0F\u5207\u6362\u7ED3\u679C\u4E0D\u4E00\u81F4", "Trading mode switch response mismatch"));
@@ -1895,6 +1934,10 @@ function mergeCandle(rows, next) {
1895
1934
  if (next.t === last.t && JSON.stringify(next) !== JSON.stringify(last)) return [...rows.slice(0, -1), next];
1896
1935
  return rows;
1897
1936
  }
1937
+ async function setCustody(mode, acknowledge = false) {
1938
+ await hyperCall("setCustody", { mode, acknowledge });
1939
+ await loadStatus();
1940
+ }
1898
1941
  async function loadStatus() {
1899
1942
  const epoch = networkEpoch;
1900
1943
  try {
@@ -2119,6 +2162,8 @@ async function pollEvents() {
2119
2162
  if (ev.kind === "network.changed" && (ev.data?.to === "mainnet" || ev.data?.to === "testnet") && ev.data.to !== store.get().status?.network) resetNetworkView(ev.data.to);
2120
2163
  if (ev.kind === "mode.changed" && (ev.data?.to === "live" || ev.data?.to === "paper") && ev.data.to !== store.get().status?.tradingMode) resetModeView(ev.data.to);
2121
2164
  if (ev.kind === "risk.changed") void loadStatus();
2165
+ if (ev.kind === "wallet.changed") resetWalletView();
2166
+ if (ev.kind === "custody.changed") void loadStatus();
2122
2167
  if (ev.kind === "paper.fill" || ev.kind?.startsWith("live.order.")) {
2123
2168
  void loadAccount();
2124
2169
  void loadTradeHistory(true);
@@ -2423,10 +2468,16 @@ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primit
2423
2468
  // src/client/format.ts
2424
2469
  var INTERVALS = ["1m", "5m", "15m", "1h", "4h", "1d"];
2425
2470
  var MORE_INTERVALS = ["3m", "30m", "2h", "8h", "12h", "3d", "1w", "1M"];
2471
+ var numberFormats = /* @__PURE__ */ new Map();
2426
2472
  function fmt(n2, d = 2) {
2427
2473
  const v = Number(n2);
2428
2474
  if (!Number.isFinite(v)) return "\u2014";
2429
- return v.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
2475
+ let formatter = numberFormats.get(d);
2476
+ if (!formatter) {
2477
+ formatter = new Intl.NumberFormat("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
2478
+ numberFormats.set(d, formatter);
2479
+ }
2480
+ return formatter.format(v);
2430
2481
  }
2431
2482
  function fmtPrice(n2) {
2432
2483
  const v = Number(n2);
@@ -17651,6 +17702,7 @@ function isSingleEmoji(value) {
17651
17702
  if (typeof value !== "string" || !value || value.length > 32 || !/\p{Extended_Pictographic}|\p{Regional_Indicator}/u.test(value)) return false;
17652
17703
  return [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(value)].length === 1;
17653
17704
  }
17705
+ var FIB_LEVEL_KINDS = ["fib", "fibext", "fibchannel"];
17654
17706
  function volumeProfile(bars, { rows = 48, valueArea = 0.7 } = {}) {
17655
17707
  const valid = bars.filter((bar) => [bar.open, bar.high, bar.low, bar.close, bar.volume].every(Number.isFinite) && bar.high >= bar.low && bar.volume >= 0);
17656
17708
  if (!valid.length) return null;
@@ -17935,7 +17987,7 @@ function guide(a, b, value, key3, text) {
17935
17987
  }
17936
17988
  var BADGE_PATHS = { alert: "M6 16V11a6 6 0 0 1 12 0v5l2 2H4zM10 20a2 2 0 0 0 4 0", shared: "M4 9h13l-3-3M20 15H7l3 3" };
17937
17989
  var DASH = { solid: void 0, dashed: "7 5", dotted: "1.5 4" };
17938
- var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, label, text: textValue, className, view, anchors = false, interactive = false, color, lineWidth, lineStyle, id, alert, shared, rows, valueArea }) {
17990
+ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, label, text: textValue, className, view, anchors = false, interactive = false, color, lineWidth, lineStyle, id, alert, shared, rows, valueArea, levels }) {
17939
17991
  const badges = [...alert ? ["alert"] : [], ...shared ? ["shared"] : []];
17940
17992
  const { width: w, height: h } = view;
17941
17993
  const px = points2.map((point) => view.project(point.time, point.price));
@@ -17988,7 +18040,7 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
17988
18040
  if (!p[1]) break;
17989
18041
  const [start, end] = points2;
17990
18042
  const x0 = Math.min(p[0].x, p[1].x), x1 = Math.max(p[0].x, p[1].x, x0 + 40);
17991
- const rows2 = FIB_LEVELS.map((level) => ({ level, price: end.price + (start.price - end.price) * level })).map((row) => ({ ...row, y: view.project(end.time, row.price)?.y })).filter((row) => row.y != null);
18043
+ const rows2 = (levels ?? FIB_LEVELS).map((level) => ({ level, price: end.price + (start.price - end.price) * level })).map((row) => ({ ...row, y: view.project(end.time, row.price)?.y })).filter((row) => row.y != null);
17992
18044
  body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, rows2.slice(1).map((row, index) => /* @__PURE__ */ React7.createElement("rect", { key: `b${row.level}`, className: `dhl-fib-band band-${index % 3}`, x: x0, width: x1 - x0, y: Math.min(row.y, rows2[index].y), height: Math.abs(row.y - rows2[index].y) })), rows2.map((row) => /* @__PURE__ */ React7.createElement("g", { key: row.level }, /* @__PURE__ */ React7.createElement("line", { className: row.level === 0.5 || row.level === 0.618 ? "dhl-fib-key" : void 0, x1: x0, x2: x1, y1: row.y, y2: row.y }), text(x0 + 4, row.y - 3, `${row.level} (${fmtPrice(row.price)})`))), /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y }));
17993
18045
  break;
17994
18046
  }
@@ -18070,7 +18122,7 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18070
18122
  }
18071
18123
  const [a, b, c] = points2;
18072
18124
  const x0 = p[2].x, x1 = Math.max(x0 + Math.abs(p[1].x - p[0].x), x0 + 60);
18073
- const rows2 = FIB_EXT_LEVELS.map((level) => ({ level, price: c.price + (b.price - a.price) * level })).map((row) => ({ ...row, y: view.project(c.time, row.price)?.y })).filter((row) => row.y != null);
18125
+ const rows2 = (levels ?? FIB_EXT_LEVELS).map((level) => ({ level, price: c.price + (b.price - a.price) * level })).map((row) => ({ ...row, y: view.project(c.time, row.price)?.y })).filter((row) => row.y != null);
18074
18126
  body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("polyline", { className: "dhl-drawing-mid", points: `${p[0].x},${p[0].y} ${p[1].x},${p[1].y} ${p[2].x},${p[2].y}` }), rows2.map((row) => /* @__PURE__ */ React7.createElement("g", { key: row.level }, /* @__PURE__ */ React7.createElement("line", { className: row.level === 1 || row.level === 1.618 ? "dhl-fib-key" : void 0, x1: x0, x2: x1, y1: row.y, y2: row.y }), text(x0 + 4, row.y - 3, `${row.level} (${fmtPrice(row.price)})`))));
18075
18127
  break;
18076
18128
  }
@@ -18082,7 +18134,7 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18082
18134
  }
18083
18135
  const k = (points2[2].time - points2[0].time) / (points2[1].time - points2[0].time || 1);
18084
18136
  const offset = points2[2].price - (points2[0].price + (points2[1].price - points2[0].price) * k);
18085
- body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, FIB_LEVELS.map((level) => {
18137
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, (levels ?? FIB_LEVELS).map((level) => {
18086
18138
  const a2 = view.project(points2[0].time, points2[0].price + offset * level), b2 = view.project(points2[1].time, points2[1].price + offset * level);
18087
18139
  const seg = a2 && b2 ? clipLine(a2, b2, w, h, 0) : null;
18088
18140
  return seg ? /* @__PURE__ */ React7.createElement("g", { key: level }, /* @__PURE__ */ React7.createElement("line", { className: level === 0.5 || level === 0.618 ? "dhl-fib-key" : void 0, x1: seg[0].x, y1: seg[0].y, x2: seg[1].x, y2: seg[1].y }), text(a2.x + 4, a2.y - 3, String(level))) : null;
@@ -18152,9 +18204,9 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18152
18204
  case "gannsquare": {
18153
18205
  if (!p[1]) break;
18154
18206
  const x0 = Math.min(p[0].x, p[1].x), x1 = Math.max(p[0].x, p[1].x), y0 = Math.min(p[0].y, p[1].y), y1 = Math.max(p[0].y, p[1].y);
18155
- const levels = kind === "gannbox" ? GANN_BOX_LEVELS : [0, 0.25, 0.5, 0.75, 1];
18156
- const xs = levels.map((level) => x0 + (x1 - x0) * level), ys = levels.map((level) => y0 + (y1 - y0) * level);
18157
- body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-drawing-fill", x: x0, y: y0, width: x1 - x0, height: y1 - y0 }), xs.slice(1, -1).map((x, i) => /* @__PURE__ */ React7.createElement("line", { key: `v${i}`, className: "dhl-drawing-mid", x1: x, x2: x, y1: y0, y2: y1 })), ys.slice(1, -1).map((y, i) => /* @__PURE__ */ React7.createElement("line", { key: `h${i}`, className: "dhl-drawing-mid", x1: x0, x2: x1, y1: y, y2: y })), /* @__PURE__ */ React7.createElement("line", { x1: x0, y1, x2: x1, y2: y0 }), /* @__PURE__ */ React7.createElement("line", { x1: x0, y1: y0, x2: x1, y2: y1 }), kind === "gannsquare" ? [[x0, y1, x1, (y0 + y1) / 2], [x0, y1, (x0 + x1) / 2, y0], [x0, y0, x1, (y0 + y1) / 2], [x0, y0, (x0 + x1) / 2, y1]].map(([ax, ay, bx, by], i) => /* @__PURE__ */ React7.createElement("line", { key: `f${i}`, className: "dhl-drawing-mid", x1: ax, y1: ay, x2: bx, y2: by })) : null, kind === "gannbox" ? levels.map((level, i) => /* @__PURE__ */ React7.createElement("g", { key: `l${level}` }, text(xs[i], y0 - 4, String(level), "middle"), text(x0 - 4, ys[i] + 3, String(level), "end"))) : null);
18207
+ const levels2 = kind === "gannbox" ? GANN_BOX_LEVELS : [0, 0.25, 0.5, 0.75, 1];
18208
+ const xs = levels2.map((level) => x0 + (x1 - x0) * level), ys = levels2.map((level) => y0 + (y1 - y0) * level);
18209
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-drawing-fill", x: x0, y: y0, width: x1 - x0, height: y1 - y0 }), xs.slice(1, -1).map((x, i) => /* @__PURE__ */ React7.createElement("line", { key: `v${i}`, className: "dhl-drawing-mid", x1: x, x2: x, y1: y0, y2: y1 })), ys.slice(1, -1).map((y, i) => /* @__PURE__ */ React7.createElement("line", { key: `h${i}`, className: "dhl-drawing-mid", x1: x0, x2: x1, y1: y, y2: y })), /* @__PURE__ */ React7.createElement("line", { x1: x0, y1, x2: x1, y2: y0 }), /* @__PURE__ */ React7.createElement("line", { x1: x0, y1: y0, x2: x1, y2: y1 }), kind === "gannsquare" ? [[x0, y1, x1, (y0 + y1) / 2], [x0, y1, (x0 + x1) / 2, y0], [x0, y0, x1, (y0 + y1) / 2], [x0, y0, (x0 + x1) / 2, y1]].map(([ax, ay, bx, by], i) => /* @__PURE__ */ React7.createElement("line", { key: `f${i}`, className: "dhl-drawing-mid", x1: ax, y1: ay, x2: bx, y2: by })) : null, kind === "gannbox" ? levels2.map((level, i) => /* @__PURE__ */ React7.createElement("g", { key: `l${level}` }, text(xs[i], y0 - 4, String(level), "middle"), text(x0 - 4, ys[i] + 3, String(level), "end"))) : null);
18158
18210
  break;
18159
18211
  }
18160
18212
  case "icon": {
@@ -18192,7 +18244,7 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18192
18244
  const x0 = Math.min(p[0].x, p[1].x), x1 = Math.max(p[0].x, p[1].x);
18193
18245
  const settings = { rows: rows ?? 48, valueArea: valueArea ?? 0.7 };
18194
18246
  const profile = view.profile ? view.profile(from.time, to.time, settings) : volumeProfile(view.candles?.(from.time, to.time) ?? [], settings);
18195
- const partial = view.firstTime != null && view.barMs != null && view.firstTime - from.time > view.barMs;
18247
+ const partial = view.firstTime != null && view.barMs != null && view.firstTime - from.time >= view.barMs;
18196
18248
  if (!profile) {
18197
18249
  body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-drawing-fill", x: x0, y: 0, width: Math.max(1, x1 - x0), height: h }), text(x0 + 4, 16, tr("\u533A\u95F4\u5185\u6682\u65E0\u6210\u4EA4\u91CF\u6570\u636E", "No volume in this range"), "start"));
18198
18250
  handles = [p[0], p[1]];
@@ -18202,13 +18254,17 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18202
18254
  const maxRow = Math.max(...profile.rows.map((row) => row.up + row.down)) || 1;
18203
18255
  const span = Math.max(24, (x1 - x0) * 0.3);
18204
18256
  const yTop = y(profile.rows.at(-1).high), yBottom = y(profile.rows[0].low), yPoc = y(profile.poc), yVah = y(profile.vah), yVal = y(profile.val);
18205
- body = /* @__PURE__ */ React7.createElement("g", { className: "dhl-vprofile" }, yTop != null && yBottom != null ? /* @__PURE__ */ React7.createElement("rect", { className: "dhl-vprofile-range", x: x0, y: yTop, width: Math.max(1, x1 - x0), height: Math.max(1, yBottom - yTop) }) : null, profile.rows.map((row, index) => {
18257
+ const paths2 = { valueUp: "", valueDown: "", outUp: "", outDown: "" };
18258
+ const rectPath = (x, top, width, height) => `M${x.toFixed(2)} ${top.toFixed(2)}h${width.toFixed(2)}v${height.toFixed(2)}h${(-width).toFixed(2)}z`;
18259
+ profile.rows.forEach((row, index) => {
18206
18260
  const top = y(row.high), bottom = y(row.low);
18207
- if (top == null || bottom == null) return null;
18261
+ if (top == null || bottom == null) return;
18208
18262
  const height = Math.max(1, bottom - top - 0.5), inValue = index >= profile.valueLow && index <= profile.valueHigh;
18209
18263
  const upWidth = span * row.up / maxRow, downWidth = span * row.down / maxRow;
18210
- return /* @__PURE__ */ React7.createElement("g", { key: index, className: inValue ? "dhl-vprofile-va" : "dhl-vprofile-out" }, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-vprofile-up", x: x0, y: top, width: upWidth, height }), /* @__PURE__ */ React7.createElement("rect", { className: "dhl-vprofile-down", x: x0 + upWidth, y: top, width: downWidth, height }));
18211
- }), yPoc != null ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-vprofile-poc", x1: x0, y1: yPoc, x2: x1, y2: yPoc }), text(x1 - 4, yPoc - 4, `POC ${fmtPrice(profile.poc)}`, "end")) : null, yVah != null ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: x0, y1: yVah, x2: x1, y2: yVah }), text(x1 - 4, yVah - 4, `VAH ${fmtPrice(profile.vah)}`, "end")) : null, yVal != null ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: x0, y1: yVal, x2: x1, y2: yVal }), text(x1 - 4, yVal + 12, `VAL ${fmtPrice(profile.val)}`, "end")) : null, yBottom != null ? text(x0 + 4, Math.min(h - 4, yBottom + 14), partial ? tr("K \u7EBF\u4F30\u7B97 \xB7 \u533A\u95F4\u524D\u6BB5\u5386\u53F2\u672A\u52A0\u8F7D", "Estimated from bars \xB7 early range not loaded") : tr("K \u7EBF\u4F30\u7B97", "Estimated from bars"), "start", partial ? "dhl-drawing-text dhl-vprofile-warn" : "dhl-drawing-text") : null);
18264
+ paths2[inValue ? "valueUp" : "outUp"] += rectPath(x0, top, upWidth, height);
18265
+ paths2[inValue ? "valueDown" : "outDown"] += rectPath(x0 + upWidth, top, downWidth, height);
18266
+ });
18267
+ body = /* @__PURE__ */ React7.createElement("g", { className: "dhl-vprofile", "data-rows": profile.rows.length }, yTop != null && yBottom != null ? /* @__PURE__ */ React7.createElement("rect", { className: "dhl-vprofile-range", x: x0, y: yTop, width: Math.max(1, x1 - x0), height: Math.max(1, yBottom - yTop) }) : null, /* @__PURE__ */ React7.createElement("g", { className: "dhl-vprofile-va" }, /* @__PURE__ */ React7.createElement("path", { className: "dhl-vprofile-up", d: paths2.valueUp }), /* @__PURE__ */ React7.createElement("path", { className: "dhl-vprofile-down", d: paths2.valueDown })), /* @__PURE__ */ React7.createElement("g", { className: "dhl-vprofile-out" }, /* @__PURE__ */ React7.createElement("path", { className: "dhl-vprofile-up", d: paths2.outUp }), /* @__PURE__ */ React7.createElement("path", { className: "dhl-vprofile-down", d: paths2.outDown })), yPoc != null ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-vprofile-poc", x1: x0, y1: yPoc, x2: x1, y2: yPoc }), text(x1 - 4, yPoc - 4, `POC ${fmtPrice(profile.poc)}`, "end")) : null, yVah != null ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: x0, y1: yVah, x2: x1, y2: yVah }), text(x1 - 4, yVah - 4, `VAH ${fmtPrice(profile.vah)}`, "end")) : null, yVal != null ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: x0, y1: yVal, x2: x1, y2: yVal }), text(x1 - 4, yVal + 12, `VAL ${fmtPrice(profile.val)}`, "end")) : null, yBottom != null ? text(x0 + 4, Math.min(h - 4, yBottom + 14), partial ? tr("K \u7EBF\u4F30\u7B97 \xB7 \u533A\u95F4\u524D\u6BB5\u5386\u53F2\u672A\u52A0\u8F7D", "Estimated from bars \xB7 early range not loaded") : tr("K \u7EBF\u4F30\u7B97", "Estimated from bars"), "start", partial ? "dhl-drawing-text dhl-vprofile-warn" : "dhl-drawing-text") : null);
18212
18268
  handles = [p[0], p[1]];
18213
18269
  break;
18214
18270
  }
@@ -18289,6 +18345,21 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
18289
18345
  setText(mark.label);
18290
18346
  }, [mark.id, mark.label]);
18291
18347
  const locked = Boolean(mark.locked) || globallyLocked;
18348
+ const defaultLevels = (mark.levels ?? (mark.kind === "fibext" ? FIB_EXT_LEVELS : FIB_LEVELS)).join(", ");
18349
+ const [levelsText, setLevelsText] = React8.useState(defaultLevels);
18350
+ React8.useEffect(() => {
18351
+ setLevelsText(defaultLevels);
18352
+ }, [mark.id, defaultLevels]);
18353
+ const applyLevels = () => {
18354
+ if (levelsText === defaultLevels) return;
18355
+ if (!levelsText.trim()) {
18356
+ onEdit({ levels: null });
18357
+ return;
18358
+ }
18359
+ const values = levelsText.split(/[,,\s]+/).filter(Boolean).map(Number);
18360
+ if (values.length && values.every(Number.isFinite)) onEdit({ levels: values });
18361
+ else setLevelsText(defaultLevels);
18362
+ };
18292
18363
  const width = mark.lineWidth ?? 2;
18293
18364
  const style = mark.lineStyle ?? "solid";
18294
18365
  const tool = toolOf(mark.kind);
@@ -18348,7 +18419,22 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
18348
18419
  onClick: () => onEdit({ lineStyle: value })
18349
18420
  },
18350
18421
  /* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 18 18", width: "16", height: "16", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("line", { x1: "2", x2: "16", y1: "9", y2: "9", stroke: "currentColor", strokeWidth: "2", strokeDasharray: value === "dashed" ? "4 3" : value === "dotted" ? "1 3" : void 0, strokeLinecap: "round" }))
18351
- ))) : null, mark.kind === "vprofile" ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6210\u4EA4\u91CF\u5206\u5E03\u8BBE\u7F6E", "Volume profile settings") }, /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u4EF7\u683C\u884C\u6570", "Rows"), title: tr("\u4EF7\u683C\u884C\u6570", "Rows"), value: String(mark.rows ?? 48), disabled: locked, onChange: (event) => onEdit({ rows: Number(event.target.value) }) }, [24, 48, 96, 150].map((value) => /* @__PURE__ */ React8.createElement("option", { key: value, value }, tr(`${value} \u884C`, `${value} rows`)))), /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u4EF7\u503C\u533A\u6BD4\u4F8B", "Value area"), title: tr("\u4EF7\u503C\u533A\u6210\u4EA4\u91CF\u5360\u6BD4", "Value area volume"), value: String(mark.valueArea ?? 0.7), disabled: locked, onChange: (event) => onEdit({ valueArea: Number(event.target.value) }) }, [0.68, 0.7, 0.8].map((value) => /* @__PURE__ */ React8.createElement("option", { key: value, value }, `VA ${Math.round(value * 100)}%`)))) : null, /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group" }, /* @__PURE__ */ React8.createElement("button", { type: "button", disabled: locked, title: tr("\u7CBE\u786E\u7F16\u8F91\u65F6\u95F4\u3001\u4EF7\u683C\u4E0E\u540D\u79F0", "Edit exact time, price and name"), "aria-label": tr("\u7F16\u8F91\u753B\u7EBF\u5750\u6807", "Edit drawing coordinates"), onClick: onCoordinates }, /* @__PURE__ */ React8.createElement(UiIcon, { name: "settings", size: 15 })), /* @__PURE__ */ React8.createElement(
18422
+ ))) : null, FIB_LEVEL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6590\u6CE2\u90A3\u5951\u5C42\u7EA7", "Fibonacci levels") }, /* @__PURE__ */ React8.createElement(
18423
+ "input",
18424
+ {
18425
+ className: "dhl-draw-toolbar-text dhl-fib-levels",
18426
+ value: levelsText,
18427
+ disabled: locked,
18428
+ "aria-label": tr("\u6590\u6CE2\u90A3\u5951\u5C42\u7EA7\uFF08\u9017\u53F7\u5206\u9694\uFF09", "Fibonacci levels (comma separated)"),
18429
+ title: tr("\u9017\u53F7\u5206\u9694\uFF0C\u56DE\u8F66\u5E94\u7528\uFF1B\u6E05\u7A7A\u6062\u590D\u9ED8\u8BA4", "Comma separated, Enter applies; empty restores defaults"),
18430
+ onChange: (event) => setLevelsText(event.target.value),
18431
+ onKeyDown: (event) => {
18432
+ if (event.key === "Enter") applyLevels();
18433
+ if (event.key === "Escape") setLevelsText(defaultLevels);
18434
+ },
18435
+ onBlur: applyLevels
18436
+ }
18437
+ )) : null, mark.kind === "vprofile" ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6210\u4EA4\u91CF\u5206\u5E03\u8BBE\u7F6E", "Volume profile settings") }, /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u4EF7\u683C\u884C\u6570", "Rows"), title: tr("\u4EF7\u683C\u884C\u6570", "Rows"), value: String(mark.rows ?? 48), disabled: locked, onChange: (event) => onEdit({ rows: Number(event.target.value) }) }, [24, 48, 96, 150].map((value) => /* @__PURE__ */ React8.createElement("option", { key: value, value }, tr(`${value} \u884C`, `${value} rows`)))), /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u4EF7\u503C\u533A\u6BD4\u4F8B", "Value area"), title: tr("\u4EF7\u503C\u533A\u6210\u4EA4\u91CF\u5360\u6BD4", "Value area volume"), value: String(mark.valueArea ?? 0.7), disabled: locked, onChange: (event) => onEdit({ valueArea: Number(event.target.value) }) }, [0.68, 0.7, 0.8].map((value) => /* @__PURE__ */ React8.createElement("option", { key: value, value }, `VA ${Math.round(value * 100)}%`)))) : null, /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group" }, /* @__PURE__ */ React8.createElement("button", { type: "button", disabled: locked, title: tr("\u7CBE\u786E\u7F16\u8F91\u65F6\u95F4\u3001\u4EF7\u683C\u4E0E\u540D\u79F0", "Edit exact time, price and name"), "aria-label": tr("\u7F16\u8F91\u753B\u7EBF\u5750\u6807", "Edit drawing coordinates"), onClick: onCoordinates }, /* @__PURE__ */ React8.createElement(UiIcon, { name: "settings", size: 15 })), /* @__PURE__ */ React8.createElement(
18352
18438
  "button",
18353
18439
  {
18354
18440
  type: "button",
@@ -18548,6 +18634,24 @@ async function chartImage(chart, box, title) {
18548
18634
  return new Promise((resolve, reject) => canvas.toBlob((blob) => blob ? resolve(blob) : reject(new Error("PNG export failed")), "image/png"));
18549
18635
  }
18550
18636
 
18637
+ // src/client/chart-feed.ts
18638
+ var fedShape = /* @__PURE__ */ new WeakMap();
18639
+ var fields = ["time", "value", "open", "high", "low", "close", "color"];
18640
+ function samePrefix(previous, data, length) {
18641
+ for (let i = 0; i < length; i++) for (const key3 of fields) if (previous[i][key3] !== data[i][key3]) return false;
18642
+ return true;
18643
+ }
18644
+ function feed(series, data, sig = "") {
18645
+ const prev = fedShape.get(series);
18646
+ const len = data.length, first = data[0]?.time, last = data.at(-1)?.time;
18647
+ const tailOnly = prev && prev.sig === sig && len > 0 && first === prev.data[0]?.time && (len === prev.data.length ? last === prev.data.at(-1)?.time : len === prev.data.length + 1 && data.at(-2)?.time === prev.data.at(-1)?.time) && samePrefix(prev.data, data, prev.data.length - 1);
18648
+ if (tailOnly) {
18649
+ if (len === prev.data.length + 1) series.update(data.at(-2));
18650
+ series.update(data.at(-1));
18651
+ } else series.setData(data);
18652
+ fedShape.set(series, { data, sig });
18653
+ }
18654
+
18551
18655
  // src/client/components/candle-chart.tsx
18552
18656
  var MAGNET_PX = 14;
18553
18657
  var MA_PALETTE = ["#29b6f6", "#ff7eb6", "#c6e377", "#ffb74d", "#b39ddb", "#4dd0e1", "#f48fb1", "#aed581"];
@@ -18651,11 +18755,25 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18651
18755
  const [selectedTime, setSelectedTime] = React10.useState(null);
18652
18756
  const [themeVersion, setThemeVersion] = React10.useState(0);
18653
18757
  const [viewportVersion, setViewportVersion] = React10.useState(0);
18758
+ const projectionStamp = React10.useRef([]);
18759
+ const readProjection = (r, box) => {
18760
+ const base = candlesRef.current[0]?.c ?? 1;
18761
+ return [
18762
+ r.chart.timeScale().logicalToCoordinate(0),
18763
+ r.chart.timeScale().logicalToCoordinate(1),
18764
+ r.candles.priceToCoordinate(base),
18765
+ r.candles.priceToCoordinate(base * 1.01),
18766
+ box.clientWidth,
18767
+ box.clientHeight,
18768
+ base
18769
+ ];
18770
+ };
18654
18771
  const [pendingPoints, setPendingPoints] = React10.useState([]);
18655
18772
  const [drawingHover, setDrawingHover] = React10.useState(null);
18656
18773
  const showVolume = indicators.has("volume");
18657
18774
  const studies = React10.useMemo(() => chartIndicators(candles, settings), [candles, settings]);
18658
18775
  const maComputed = React10.useMemo(() => maLines.map((line, index) => ({ line, color: maColor(line, index), ...maSeries(candles, line) })), [candles, maLines]);
18776
+ const axisKey = `${candles.length}:${candles[0]?.t ?? 0}:${candles.at(-1)?.t ?? 0}`;
18659
18777
  const timeline = React10.useMemo(() => {
18660
18778
  const times = [...new Set(candles.map((candle) => Math.floor(candle.t / 1e3)))].sort((a, b) => a - b);
18661
18779
  const step = times.length > 1 ? times[times.length - 1] - times[times.length - 2] : 60;
@@ -18680,7 +18798,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18680
18798
  return times[i] * 1e3;
18681
18799
  };
18682
18800
  return { toLogical, toTime };
18683
- }, [candles]);
18801
+ }, [axisKey]);
18684
18802
  const timelineRef = React10.useRef(timeline);
18685
18803
  timelineRef.current = timeline;
18686
18804
  React10.useEffect(() => {
@@ -18807,7 +18925,21 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18807
18925
  }
18808
18926
  }
18809
18927
  }
18810
- if (event.shiftKey && previous) price = previous.price;
18928
+ if (event.shiftKey && previous) {
18929
+ const px = chart.timeScale().logicalToCoordinate(timelineRef.current.toLogical(previous.time));
18930
+ const py = candleSeries.priceToCoordinate(previous.price);
18931
+ const x = event.clientX - rect.left;
18932
+ if (px == null || py == null) return { time, price: previous.price };
18933
+ const dx = x - px, dy = y - py;
18934
+ const angle = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);
18935
+ if (Math.abs(Math.abs(angle) - Math.PI / 2) < 1e-6 || Math.abs(angle) < 1e-6 || Math.abs(Math.abs(angle) - Math.PI) < 1e-6) return { time, price: previous.price };
18936
+ const along = dx * Math.cos(angle) + dy * Math.sin(angle);
18937
+ const tx = px + Math.cos(angle) * along, ty = py + Math.sin(angle) * along;
18938
+ const snappedLogical = chart.timeScale().coordinateToLogical(tx);
18939
+ const snappedPrice = Number(candleSeries.coordinateToPrice(ty));
18940
+ if (snappedLogical == null || !Number.isFinite(snappedPrice) || snappedPrice <= 0) return { time, price: previous.price };
18941
+ return { time: timelineRef.current.toTime(Number(snappedLogical)), price: snappedPrice };
18942
+ }
18811
18943
  return { time, price };
18812
18944
  };
18813
18945
  snapRef.current = snapPoint;
@@ -18877,17 +19009,31 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18877
19009
  let viewportFrame = 0;
18878
19010
  const refreshOverlay = () => {
18879
19011
  if (viewportFrame) cancelAnimationFrame(viewportFrame);
18880
- viewportFrame = requestAnimationFrame(() => setViewportVersion((value) => value + 1));
19012
+ viewportFrame = requestAnimationFrame(() => React10.startTransition(() => setViewportVersion((value) => value + 1)));
18881
19013
  };
18882
19014
  chart.timeScale().subscribeVisibleLogicalRangeChange(refreshOverlay);
19015
+ let scalingPrice = false;
19016
+ const endCanvasGesture = () => box.classList.remove("dhl-canvas-gesture");
19017
+ const beginCanvasGesture = (event) => {
19018
+ if (event.target instanceof HTMLCanvasElement) box.classList.add("dhl-canvas-gesture");
19019
+ };
19020
+ box.addEventListener("pointerdown", beginCanvasGesture, { passive: true });
19021
+ window.addEventListener("pointerup", endCanvasGesture);
19022
+ window.addEventListener("pointercancel", endCanvasGesture);
19023
+ window.addEventListener("blur", endCanvasGesture);
19024
+ const scaleDown = (event) => {
19025
+ scalingPrice = event.target instanceof HTMLCanvasElement && event.clientX >= box.getBoundingClientRect().right - chart.priceScale("right").width();
19026
+ };
18883
19027
  const scaleDrag = (event) => {
18884
- if (event.buttons) refreshOverlay();
19028
+ if (event.buttons && scalingPrice) refreshOverlay();
18885
19029
  };
19030
+ box.addEventListener("pointerdown", scaleDown, { passive: true });
18886
19031
  box.addEventListener("pointermove", scaleDrag, { passive: true });
18887
19032
  box.addEventListener("wheel", refreshOverlay, { passive: true });
18888
19033
  box.addEventListener("dblclick", refreshOverlay);
18889
19034
  const sizeObserver = new ResizeObserver(refreshOverlay);
18890
19035
  sizeObserver.observe(box);
19036
+ box.querySelectorAll("canvas").forEach((canvas) => sizeObserver.observe(canvas));
18891
19037
  chart.timeScale().subscribeVisibleTimeRangeChange((range) => {
18892
19038
  if (!fitted.current || restoring.current || !range || typeof range.from !== "number" || typeof range.to !== "number") return;
18893
19039
  if (rangeTimer) clearTimeout(rangeTimer);
@@ -18930,6 +19076,12 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18930
19076
  box.removeEventListener("click", chartClick);
18931
19077
  chart.timeScale().unsubscribeVisibleLogicalRangeChange(refreshOverlay);
18932
19078
  box.removeEventListener("pointermove", scaleDrag);
19079
+ box.removeEventListener("pointerdown", beginCanvasGesture);
19080
+ window.removeEventListener("pointerup", endCanvasGesture);
19081
+ window.removeEventListener("pointercancel", endCanvasGesture);
19082
+ window.removeEventListener("blur", endCanvasGesture);
19083
+ endCanvasGesture();
19084
+ box.removeEventListener("pointerdown", scaleDown);
18933
19085
  box.removeEventListener("wheel", refreshOverlay);
18934
19086
  box.removeEventListener("dblclick", refreshOverlay);
18935
19087
  sizeObserver.disconnect();
@@ -18989,26 +19141,26 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18989
19141
  const prevFirst = firstBarTime.current;
18990
19142
  const prepended = prevFirst != null && bars[0].time < prevFirst ? Math.max(0, bars.findIndex((bar) => bar.time >= prevFirst)) : 0;
18991
19143
  firstBarTime.current = bars[0].time;
18992
- r.candles.setData(chartType === "line" || chartType === "area" ? bars.map((bar) => ({ time: bar.time, value: bar.close })) : bars);
18993
- r.volume.setData(vols.map((v) => ({ ...v, color: v.color === FALLBACK_UP ? th.up : th.down })));
18994
- r.sma.setData(chartData(studies.sma20));
18995
- r.basis.setData(chartData(studies.basis));
18996
- r.basis.applyOptions({ visible: indicators.has("bollinger20") });
18997
- r.sma60.setData(chartData(studies.sma60));
18998
- r.ema20.setData(chartData(studies.ema20));
18999
- r.ema50.setData(chartData(studies.ema50));
19000
- r.upper.setData(chartData(studies.upper));
19001
- r.lower.setData(chartData(studies.lower));
19002
- r.volume.applyOptions({ visible: showVolume });
19003
- r.sma.applyOptions({ visible: indicators.has("sma20") });
19004
- r.sma60.applyOptions({ visible: indicators.has("sma60") });
19005
- r.ema20.applyOptions({ visible: indicators.has("ema20") });
19006
- r.ema50.applyOptions({ visible: indicators.has("ema50") });
19007
- r.upper.applyOptions({ visible: indicators.has("bollinger20") });
19008
- r.lower.applyOptions({ visible: indicators.has("bollinger20") });
19144
+ const studySig = JSON.stringify(settings), colorSig = `${chartType}|${th.up}|${th.down}`;
19145
+ feed(r.candles, chartType === "line" || chartType === "area" ? bars.map((bar) => ({ time: bar.time, value: bar.close })) : bars, chartType);
19146
+ if (showVolume) feed(r.volume, vols.map((v) => ({ ...v, color: v.color === FALLBACK_UP ? th.up : th.down })), colorSig);
19147
+ if (r.volume.options().visible !== showVolume) r.volume.applyOptions({ visible: showVolume });
19148
+ for (const [series, key3, indicator] of [
19149
+ [r.sma, "sma20", "sma20"],
19150
+ [r.sma60, "sma60", "sma60"],
19151
+ [r.ema20, "ema20", "ema20"],
19152
+ [r.ema50, "ema50", "ema50"],
19153
+ [r.basis, "basis", "bollinger20"],
19154
+ [r.upper, "upper", "bollinger20"],
19155
+ [r.lower, "lower", "bollinger20"]
19156
+ ]) {
19157
+ const visible = indicators.has(indicator);
19158
+ if (visible) feed(series, chartData(studies[key3]), studySig);
19159
+ if (series.options().visible !== visible) series.applyOptions({ visible });
19160
+ }
19009
19161
  if (indicators.has("vwap")) {
19010
19162
  r.vwap ??= r.chart.addSeries(lineSeries, { color: "#e89a56", lineWidth: 2, lastValueVisible: false, priceLineVisible: false });
19011
- r.vwap.setData(chartData(studies.vwap));
19163
+ feed(r.vwap, chartData(studies.vwap), studySig);
19012
19164
  } else if (r.vwap) {
19013
19165
  r.chart.removeSeries(r.vwap);
19014
19166
  r.vwap = void 0;
@@ -19019,7 +19171,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19019
19171
  pane.setStretchFactor(0.28);
19020
19172
  r.atr = pane.addSeries(lineSeries, { color: "#56bca7", lineWidth: 2, priceLineVisible: false });
19021
19173
  }
19022
- r.atr.setData(chartData(studies.atr14));
19174
+ feed(r.atr, chartData(studies.atr14), studySig);
19023
19175
  } else if (r.atr) {
19024
19176
  r.chart.removeSeries(r.atr);
19025
19177
  r.atr = void 0;
@@ -19032,7 +19184,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19032
19184
  r.rsiBounds = [settings.rsi.lower, settings.rsi.upper].map((price) => r.rsi.createPriceLine({ price, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false }));
19033
19185
  }
19034
19186
  r.rsiBounds?.forEach((line, index) => line.applyOptions({ price: index ? settings.rsi.upper : settings.rsi.lower }));
19035
- r.rsi.setData(chartData(studies.rsi14));
19187
+ feed(r.rsi, chartData(studies.rsi14), studySig);
19036
19188
  } else if (r.rsi) {
19037
19189
  r.chart.removeSeries(r.rsi);
19038
19190
  r.rsi = void 0;
@@ -19045,9 +19197,9 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19045
19197
  r.macd = pane.addSeries(lineSeries, { color: "#55a6ce", lineWidth: 2, priceLineVisible: false, lastValueVisible: false });
19046
19198
  r.signal = pane.addSeries(lineSeries, { color: "#e2a943", lineWidth: 2, priceLineVisible: false, lastValueVisible: false });
19047
19199
  }
19048
- r.macd.setData(chartData(studies.macd));
19049
- r.signal.setData(chartData(studies.signal));
19050
- r.histogram.setData(studies.histogram.map((point) => ({ time: point.time, value: point.value, color: point.value >= 0 ? th.up : th.down })));
19200
+ feed(r.macd, chartData(studies.macd), studySig);
19201
+ feed(r.signal, chartData(studies.signal), studySig);
19202
+ feed(r.histogram, studies.histogram.map((point) => ({ time: point.time, value: point.value, color: point.value >= 0 ? th.up : th.down })), studySig + colorSig);
19051
19203
  } else if (r.macd) {
19052
19204
  r.chart.removeSeries(r.macd);
19053
19205
  r.chart.removeSeries(r.signal);
@@ -19071,7 +19223,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19071
19223
  if (group2.id === "stochastic") r.stochBounds = [settings.stochastic.lower, settings.stochastic.upper].map((price) => r.extra.stochK.createPriceLine({ price, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false }));
19072
19224
  if (group2.id === "adx") r.extra.adx.createPriceLine({ price: 25, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false });
19073
19225
  }
19074
- for (const key3 of group2.keys) r.extra[key3].setData(chartData(studies[key3]));
19226
+ for (const key3 of group2.keys) feed(r.extra[key3], chartData(studies[key3]), studySig);
19075
19227
  if (group2.id === "stochastic") r.stochBounds?.forEach((line, index) => line.applyOptions({ price: index ? settings.stochastic.upper : settings.stochastic.lower }));
19076
19228
  } else for (const key3 of group2.keys) {
19077
19229
  if (r.extra[key3]) {
@@ -19112,11 +19264,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19112
19264
  maRefs.current.set(item.line.id, entry);
19113
19265
  }
19114
19266
  entry.main.applyOptions({ color: item.color });
19115
- entry.main.setData(toPoints(item.values));
19267
+ feed(entry.main, toPoints(item.values), JSON.stringify(item.line));
19116
19268
  if (item.smoothed) {
19117
19269
  entry.smooth ??= r.chart.addSeries(lineSeries, { lineWidth: 1, lineStyle: LineStyle.Dashed, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: false });
19118
19270
  entry.smooth.applyOptions({ color: item.color });
19119
- entry.smooth.setData(toPoints(item.smoothed));
19271
+ feed(entry.smooth, toPoints(item.smoothed), JSON.stringify(item.line));
19120
19272
  } else if (entry.smooth) {
19121
19273
  r.chart.removeSeries(entry.smooth);
19122
19274
  entry.smooth = void 0;
@@ -19125,7 +19277,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19125
19277
  if (!fitted.current) {
19126
19278
  restoring.current = true;
19127
19279
  r.chart.timeScale().fitContent();
19128
- const saved = initialRange.current;
19280
+ const linked = linkRef.current?.range && linkRef.current.range.source !== linkRef.current.id ? linkRef.current.range : null;
19281
+ const saved = linked ?? initialRange.current;
19129
19282
  if (saved && saved.from < saved.to) {
19130
19283
  try {
19131
19284
  r.chart.timeScale().setVisibleRange({ from: saved.from, to: saved.to });
@@ -19137,6 +19290,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19137
19290
  restoring.current = false;
19138
19291
  }, 100);
19139
19292
  }
19293
+ const overlayFrame = requestAnimationFrame(() => {
19294
+ const box2 = boxRef.current;
19295
+ if (box2 && readProjection(r, box2).some((value, index) => value !== projectionStamp.current[index])) React10.startTransition(() => setViewportVersion((value) => value + 1));
19296
+ });
19297
+ return () => cancelAnimationFrame(overlayFrame);
19140
19298
  }, [candles, studies, indicators, showVolume, maComputed, chartType, settings]);
19141
19299
  React10.useEffect(() => {
19142
19300
  const r = refs.current;
@@ -19296,22 +19454,28 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19296
19454
  const view = React10.useMemo(() => {
19297
19455
  const chartApi = refs.current, box = boxRef.current;
19298
19456
  if (!chartApi || !box) return null;
19457
+ const stamp = readProjection(chartApi, box);
19458
+ projectionStamp.current = stamp;
19459
+ const [x0, x1, y0, y1] = stamp;
19460
+ const base = candlesRef.current[0]?.c ?? 1;
19461
+ const ys = /* @__PURE__ */ new Map();
19299
19462
  return {
19300
19463
  project: (time, price) => {
19301
- const x = chartApi.chart.timeScale().logicalToCoordinate(timeline.toLogical(time));
19302
- const y = chartApi.candles.priceToCoordinate(price);
19464
+ const x = x0 == null || x1 == null ? null : x0 + (x1 - x0) * timeline.toLogical(time);
19465
+ if (!ys.has(price)) ys.set(price, priceScale === "log" ? chartApi.candles.priceToCoordinate(price) : y0 == null || y1 == null ? null : y0 + (y1 - y0) * (price - base) / (base * 0.01));
19466
+ const y = ys.get(price);
19303
19467
  return x == null || y == null || !Number.isFinite(x) || !Number.isFinite(y) ? null : { x, y };
19304
19468
  },
19305
19469
  width: Math.max(0, box.clientWidth - chartApi.chart.priceScale("right").width()),
19306
19470
  height: chartApi.chart.panes()[0]?.getHeight() ?? box.clientHeight,
19307
19471
  bars: (from, to) => timeline.toLogical(to) - timeline.toLogical(from),
19308
- closes: (from, to) => candles.filter((candle) => candle.t >= from && candle.t <= to).map((candle) => ({ time: candle.t, price: candle.c })),
19309
- candles: (from, to) => candles.filter((candle) => candle.t >= from && candle.t <= to).map((candle) => ({ time: candle.t, open: candle.o, high: candle.h, low: candle.l, close: candle.c, volume: candle.v })),
19310
- profile: (from, to, options) => cachedProfile(candles, from, to, options),
19311
- firstTime: candles[0]?.t,
19312
- barMs: candles.length > 1 ? candles[1].t - candles[0].t : void 0
19472
+ closes: (from, to) => candlesRef.current.filter((candle) => candle.t >= from && candle.t <= to).map((candle) => ({ time: candle.t, price: candle.c })),
19473
+ candles: (from, to) => candlesRef.current.filter((candle) => candle.t >= from && candle.t <= to).map((candle) => ({ time: candle.t, open: candle.o, high: candle.h, low: candle.l, close: candle.c, volume: candle.v })),
19474
+ profile: (from, to, options) => cachedProfile(candlesRef.current, from, to, options),
19475
+ firstTime: candlesRef.current[0]?.t,
19476
+ barMs: candlesRef.current.length > 1 ? candlesRef.current[1].t - candlesRef.current[0].t : void 0
19313
19477
  };
19314
- }, [viewportVersion, timeline, themeVersion, expanded, indicators]);
19478
+ }, [viewportVersion, timeline, themeVersion, expanded, indicators, priceScale]);
19315
19479
  React10.useEffect(() => {
19316
19480
  if (selectedId && !markById.has(selectedId)) setSelectedId(null);
19317
19481
  }, [markById, selectedId]);
@@ -19426,7 +19590,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19426
19590
  const cloneOf = React10.useCallback((mark) => {
19427
19591
  const line = timelineRef.current;
19428
19592
  const shifted = markPoints(mark).map((point) => ({ time: line.toTime(line.toLogical(point.time) + 5), price: point.price }));
19429
- const style = Object.fromEntries(Object.entries({ color: mark.color, lineWidth: mark.lineWidth, lineStyle: mark.lineStyle }).filter(([, value]) => value != null));
19593
+ const style = Object.fromEntries(Object.entries({ color: mark.color, lineWidth: mark.lineWidth, lineStyle: mark.lineStyle, levels: mark.levels, rows: mark.rows, valueArea: mark.valueArea }).filter(([, value]) => value != null));
19430
19594
  return { kind: mark.kind, geometry: geometryPatch(mark.kind, shifted), style, label: mark.label };
19431
19595
  }, []);
19432
19596
  const clipboard = React10.useRef(null);
@@ -19526,6 +19690,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19526
19690
  }
19527
19691
  latest2 = next;
19528
19692
  setGroupDraft(next);
19693
+ for (const [memberId, member] of originals) if (member.kind === "level") priceLines.current.get(memberId)?.applyOptions({ price: next.get(memberId)[0].price });
19529
19694
  };
19530
19695
  const move2 = (ev) => {
19531
19696
  if (!moved2 && Math.hypot(ev.clientX - origin2.x, ev.clientY - origin2.y) < 3) return;
@@ -19543,6 +19708,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19543
19708
  };
19544
19709
  const cancel2 = () => {
19545
19710
  detach2();
19711
+ for (const [memberId, member] of originals) if (member.kind === "level") priceLines.current.get(memberId)?.applyOptions({ price: Number(member.price) });
19546
19712
  setGroupDraft(null);
19547
19713
  };
19548
19714
  const onKey2 = (ev) => {
@@ -19582,7 +19748,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19582
19748
  const compute = (ev) => {
19583
19749
  let next;
19584
19750
  if (anchor != null) {
19585
- const point = snap(ev, original[anchor === 0 ? 1 : 0]);
19751
+ const point = snap(ev, original[anchor === 0 ? 1 : anchor - 1]);
19586
19752
  if (!point) return;
19587
19753
  next = original.map((item) => ({ ...item }));
19588
19754
  if (mark.kind === "level") next[0] = { ...next[0], price: point.price };
@@ -19680,6 +19846,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19680
19846
  points: shown,
19681
19847
  view,
19682
19848
  text: mark.label,
19849
+ dataVersion: mark.kind === "vprofile" || mark.kind === "regression" ? candles : void 0,
19683
19850
  className: `dhl-drawing ${roleClass(mark)}${mark.kind === "level" ? " is-level" : ""}${selected ? " selected" : ""}${mark.locked || drawingsLocked ? " locked" : ""}`,
19684
19851
  label: mark.source === "user" || mark.kind === "level" ? void 0 : mark.label,
19685
19852
  anchors: selected,
@@ -19690,7 +19857,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19690
19857
  alert: mark.alert,
19691
19858
  shared: mark.shared,
19692
19859
  rows: mark.rows,
19693
- valueArea: mark.valueArea
19860
+ valueArea: mark.valueArea,
19861
+ levels: mark.levels
19694
19862
  }
19695
19863
  );
19696
19864
  }), /* @__PURE__ */ React10.createElement("g", { className: "dhl-fills" }, fills.map((event) => {
@@ -19766,7 +19934,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19766
19934
  var React11 = __toESM(require("react"), 1);
19767
19935
  var HISTORY_LIMIT = 100;
19768
19936
  var GEOMETRY = ["price", "fromTime", "fromPrice", "toTime", "toPrice", "points"];
19769
- var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea"];
19937
+ var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea", "levels"];
19770
19938
  var FLAGS = /* @__PURE__ */ new Set(["locked", "hidden", "shared", "alert"]);
19771
19939
  function recreateRequest(mark) {
19772
19940
  const out = { kind: mark.kind, label: mark.label, role: mark.role, coin: mark.coin, interval: mark.interval };
@@ -19878,6 +20046,28 @@ function useDrawingHistory(scope) {
19878
20046
  }
19879
20047
  return apiDelete(ctx, op.mark.id);
19880
20048
  };
20049
+ const step = async (ctx, forward) => {
20050
+ const from = forward ? redoStack : undoStack, to = forward ? undoStack : redoStack;
20051
+ const op = from.current.pop();
20052
+ if (!op) return;
20053
+ bump();
20054
+ let done = null, left = null;
20055
+ if (op.type === "batch") {
20056
+ const ok = [], failed = [];
20057
+ for (const inner of forward ? op.ops : [...op.ops].reverse()) (await apply2(ctx, inner, forward) ? ok : failed).push(inner);
20058
+ if (!forward) {
20059
+ ok.reverse();
20060
+ failed.reverse();
20061
+ }
20062
+ if (ok.length) done = { type: "batch", ops: ok };
20063
+ if (failed.length) left = { type: "batch", ops: failed };
20064
+ } else if (await apply2(ctx, op, forward)) done = op;
20065
+ else left = op;
20066
+ if (ctx.scope !== scopeRef.current) return;
20067
+ if (done) to.current.push(done);
20068
+ if (left) from.current.push(left);
20069
+ bump();
20070
+ };
19881
20071
  return {
19882
20072
  create: (request) => serial(async (ctx) => {
19883
20073
  const saved = await apiCreate(ctx, request);
@@ -19919,29 +20109,8 @@ function useDrawingHistory(scope) {
19919
20109
  }
19920
20110
  if (done.length) record({ type: "batch", ops: done }, ctx);
19921
20111
  }, void 0),
19922
- // A failed undo/redo goes back where it was, so the stacks keep describing the host.
19923
- undo: () => serial(async (ctx) => {
19924
- const op = undoStack.current.pop();
19925
- if (!op) return;
19926
- redoStack.current.push(op);
19927
- bump();
19928
- if (!await apply2(ctx, op, false)) {
19929
- redoStack.current.pop();
19930
- undoStack.current.push(op);
19931
- bump();
19932
- }
19933
- }, void 0),
19934
- redo: () => serial(async (ctx) => {
19935
- const op = redoStack.current.pop();
19936
- if (!op) return;
19937
- undoStack.current.push(op);
19938
- bump();
19939
- if (!await apply2(ctx, op, true)) {
19940
- undoStack.current.pop();
19941
- redoStack.current.push(op);
19942
- bump();
19943
- }
19944
- }, void 0),
20112
+ undo: () => serial((ctx) => step(ctx, false), void 0),
20113
+ redo: () => serial((ctx) => step(ctx, true), void 0),
19945
20114
  canUndo: undoStack.current.length > 0,
19946
20115
  canRedo: redoStack.current.length > 0
19947
20116
  };
@@ -19950,7 +20119,7 @@ function useDrawingHistory(scope) {
19950
20119
  // src/client/components/drawing-list.tsx
19951
20120
  var React12 = __toESM(require("react"), 1);
19952
20121
  var OPEN_KEY = "dsh-hyperliquid.draw.listOpen";
19953
- function DrawingList({ marks, locked, onSelect, onEdit, onRemove }) {
20122
+ var DrawingList = React12.memo(function DrawingList2({ marks, locked, onSelect, onEdit, onRemove, language }) {
19954
20123
  const [open, setOpen] = React12.useState(() => {
19955
20124
  try {
19956
20125
  return localStorage.getItem(OPEN_KEY) !== "0";
@@ -19966,11 +20135,11 @@ function DrawingList({ marks, locked, onSelect, onEdit, onRemove }) {
19966
20135
  return !value;
19967
20136
  });
19968
20137
  const hidden = marks.filter((mark) => mark.hidden).length;
19969
- return /* @__PURE__ */ React12.createElement("section", { className: "dhl-drawing-list", "aria-label": tr("\u753B\u7EBF\u5217\u8868", "Drawings") }, /* @__PURE__ */ React12.createElement("button", { type: "button", className: "dhl-drawing-list-head", "aria-expanded": open, onClick: toggle }, /* @__PURE__ */ React12.createElement("span", null, tr("\u753B\u7EBF", "Drawings"), " ", marks.length, hidden ? tr(` \xB7 \u9690\u85CF ${hidden}`, ` \xB7 ${hidden} hidden`) : ""), /* @__PURE__ */ React12.createElement(UiIcon, { name: open ? "chevronDown" : "chevronRight", size: 14 })), open ? /* @__PURE__ */ React12.createElement("ul", null, marks.map((mark) => {
20138
+ return /* @__PURE__ */ React12.createElement("section", { className: "dhl-drawing-list", lang: language, "aria-label": tr("\u753B\u7EBF\u5217\u8868", "Drawings") }, /* @__PURE__ */ React12.createElement("button", { type: "button", className: "dhl-drawing-list-head", "aria-expanded": open, onClick: toggle }, /* @__PURE__ */ React12.createElement("span", null, tr("\u753B\u7EBF", "Drawings"), " ", marks.length, hidden ? tr(` \xB7 \u9690\u85CF ${hidden}`, ` \xB7 ${hidden} hidden`) : ""), /* @__PURE__ */ React12.createElement(UiIcon, { name: open ? "chevronDown" : "chevronRight", size: 14 })), open ? /* @__PURE__ */ React12.createElement("ul", null, marks.map((mark) => {
19970
20139
  const tool = toolOf(mark.kind);
19971
20140
  return /* @__PURE__ */ React12.createElement("li", { key: mark.id, className: mark.hidden ? "is-hidden" : "" }, /* @__PURE__ */ React12.createElement("button", { type: "button", className: "dhl-drawing-list-name", title: tr("\u5728\u56FE\u4E0A\u5B9A\u4F4D\u5E76\u9009\u4E2D", "Find and select on the chart"), onClick: () => onSelect(mark.id), disabled: mark.hidden }, /* @__PURE__ */ React12.createElement("span", { style: { color: mark.color } }, /* @__PURE__ */ React12.createElement(ToolIcon, { kind: mark.kind, size: 14 })), /* @__PURE__ */ React12.createElement("b", null, mark.label), /* @__PURE__ */ React12.createElement("small", null, mark.alert ? /* @__PURE__ */ React12.createElement("span", { className: "dhl-list-flag", title: tr("\u4EF7\u683C\u63D0\u9192\u5DF2\u5F00\u542F", "Price alert armed") }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "bell", size: 12 })) : null, mark.shared ? /* @__PURE__ */ React12.createElement("span", { className: "dhl-list-flag", title: tr("\u5DF2\u540C\u6B65\u5230\u6240\u6709\u4F1A\u8BDD", "Synced to all sessions") }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "sync", size: 12 })) : null, tr(tool.zh, tool.en), " \xB7 ", mark.interval, mark.source === "agent" ? " \xB7 agent" : "")), /* @__PURE__ */ React12.createElement("button", { type: "button", "aria-pressed": !mark.hidden, title: mark.hidden ? tr("\u663E\u793A", "Show") : tr("\u9690\u85CF", "Hide"), "aria-label": `${mark.hidden ? tr("\u663E\u793A", "Show") : tr("\u9690\u85CF", "Hide")} ${mark.label}`, onClick: () => onEdit(mark.id, { hidden: !mark.hidden }) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: mark.hidden ? "eyeOff" : "eye", size: 14 })), /* @__PURE__ */ React12.createElement("button", { type: "button", className: mark.locked ? "on" : "", "aria-pressed": Boolean(mark.locked), disabled: locked, title: mark.locked ? tr("\u89E3\u9501", "Unlock") : tr("\u9501\u5B9A", "Lock"), "aria-label": `${mark.locked ? tr("\u89E3\u9501", "Unlock") : tr("\u9501\u5B9A", "Lock")} ${mark.label}`, onClick: () => onEdit(mark.id, { locked: !mark.locked }) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: mark.locked ? "lock" : "unlock", size: 14 })), /* @__PURE__ */ React12.createElement("button", { type: "button", disabled: locked || mark.locked, title: tr("\u5220\u9664", "Delete"), "aria-label": `${tr("\u5220\u9664\u753B\u7EBF", "Remove line")} ${mark.label}`, onClick: () => onRemove(mark.id) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "trash", size: 14 })));
19972
20141
  })) : null);
19973
- }
20142
+ });
19974
20143
 
19975
20144
  // src/client/components/sticker-picker.tsx
19976
20145
  var React13 = __toESM(require("react"), 1);
@@ -20426,7 +20595,7 @@ function MarketPicker({ activeCoin, onClose }) {
20426
20595
  const marketRows = React21.useMemo(() => {
20427
20596
  const livePerps = new Map(s.markets.map((market) => [market.name, market]));
20428
20597
  const catalog = s.marketCatalog.length ? s.marketCatalog.map((row) => {
20429
- const latest = row.tradable ? livePerps.get(row.name) : null;
20598
+ const latest = row.kind === "perp" ? livePerps.get(row.name) : null;
20430
20599
  return latest ? { ...row, ctx: latest.ctx } : row;
20431
20600
  }) : s.markets.map((row) => ({
20432
20601
  name: row.name,
@@ -20540,7 +20709,7 @@ function MarketPicker({ activeCoin, onClose }) {
20540
20709
  }
20541
20710
  };
20542
20711
  const header = (label, key3) => /* @__PURE__ */ React21.createElement("button", { type: "button", onClick: () => sortBy(key3), "aria-label": tr(`\u6309${label}\u6392\u5E8F`, `Sort by ${label}`) }, label, sort === key3 ? /* @__PURE__ */ React21.createElement(UiIcon, { name: descending ? "arrowDown" : "arrowUp", size: 12 }) : null);
20543
- return /* @__PURE__ */ React21.createElement(import_dsh_client_ui_primitives3.Modal, { open: true, onClose, title: t("market.choose", "\u9009\u62E9\u5E02\u573A"), headless: true, className: "dhl-picker-modal", backdropBlur: true, onKeyDownCapture: onKeyDown }, /* @__PURE__ */ React21.createElement("section", { className: "dhl-picker" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-top" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-search" }, /* @__PURE__ */ React21.createElement(SearchIcon, null), /* @__PURE__ */ React21.createElement("input", { ref: searchRef, "data-modal-autofocus": true, value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("market.search", "\u641C\u7D22\u5E02\u573A"), "aria-label": t("market.search", "\u641C\u7D22\u5E02\u573A") }), /* @__PURE__ */ React21.createElement("kbd", null, "/")), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-mode", role: "group", "aria-label": tr("\u5E02\u573A\u8303\u56F4", "Market scope") }, /* @__PURE__ */ React21.createElement("button", { type: "button", className: strict ? "on" : "", "aria-pressed": strict, onClick: () => setStrict(true) }, t("market.tradable", "\u53EF\u4EA4\u6613")), /* @__PURE__ */ React21.createElement("button", { type: "button", className: !strict ? "on" : "", "aria-pressed": !strict, onClick: () => setStrict(false) }, t("market.all", "\u5168\u90E8"))), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-picker-close", onClick: onClose, "aria-label": t("modal.close", "\u5173\u95ED") }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "close", size: 18 }))), !strict ? /* @__PURE__ */ React21.createElement("p", { className: "dhl-picker-readonly-note" }, tr("\u201C\u53EA\u8BFB\u201D\u8868\u793A\u672C\u63D2\u4EF6\u4EC5\u63D0\u4F9B\u884C\u60C5\uFF0C\u5C1A\u672A\u63A5\u5165\u8BE5\u5E02\u573A\u7684\u4E0B\u5355\u4E0E\u98CE\u63A7\uFF1BHIP-3 \u548C\u73B0\u8D27\u5728 Hyperliquid \u672C\u8EAB\u53EF\u4EA4\u6613\u3002", "\u201CView only\u201D means this plugin has market data but no order and risk support for that market. Hyperliquid itself supports HIP-3 and spot trading.")) : null, /* @__PURE__ */ React21.createElement("nav", { className: "dhl-picker-tabs", "aria-label": tr("\u5E02\u573A\u5206\u7C7B", "Market categories") }, CATEGORIES.map(([key3, label]) => /* @__PURE__ */ React21.createElement("button", { type: "button", key: key3, className: category === key3 ? "on" : "", "aria-current": category === key3 ? "page" : void 0, onClick: () => {
20712
+ return /* @__PURE__ */ React21.createElement(import_dsh_client_ui_primitives3.Modal, { open: true, onClose, title: t("market.choose", "\u9009\u62E9\u5E02\u573A"), headless: true, className: "dhl-picker-modal", backdropBlur: true, onKeyDownCapture: onKeyDown }, /* @__PURE__ */ React21.createElement("section", { className: "dhl-picker" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-top" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-search" }, /* @__PURE__ */ React21.createElement(SearchIcon, null), /* @__PURE__ */ React21.createElement("input", { ref: searchRef, "data-modal-autofocus": true, value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("market.search", "\u641C\u7D22\u5E02\u573A"), "aria-label": t("market.search", "\u641C\u7D22\u5E02\u573A") }), /* @__PURE__ */ React21.createElement("kbd", null, "/")), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-mode", role: "group", "aria-label": tr("\u5E02\u573A\u8303\u56F4", "Market scope") }, /* @__PURE__ */ React21.createElement("button", { type: "button", className: strict ? "on" : "", "aria-pressed": strict, onClick: () => setStrict(true) }, t("market.tradable", "\u53EF\u4EA4\u6613")), /* @__PURE__ */ React21.createElement("button", { type: "button", className: !strict ? "on" : "", "aria-pressed": !strict, onClick: () => setStrict(false) }, t("market.all", "\u5168\u90E8"))), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-picker-close", onClick: onClose, "aria-label": t("modal.close", "\u5173\u95ED") }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "close", size: 18 }))), !strict ? /* @__PURE__ */ React21.createElement("p", { className: "dhl-picker-readonly-note" }, tr("\u201C\u53EA\u8BFB\u201D\u8868\u793A\u672C\u63D2\u4EF6\u4EC5\u63D0\u4F9B\u884C\u60C5\uFF0C\u5C1A\u672A\u63A5\u5165\u8BE5\u5E02\u573A\u7684\u4E0B\u5355\u4E0E\u98CE\u63A7\uFF1B\u73B0\u8D27\u4E0E Outcome \u5728 Hyperliquid \u672C\u8EAB\u53EF\u4EA4\u6613\uFF1B\u4EE5 USDC \u4E3A\u4FDD\u8BC1\u91D1\u7684 HIP-3 \u6C38\u7EED\u5DF2\u53EF\u5728\u8FD9\u91CC\u4EA4\u6613\u3002", "\u201CView only\u201D means this plugin has market data but no order and risk support for that market. Hyperliquid itself supports spot and outcome trading; USDC-collateral HIP-3 perps are tradable here.")) : null, /* @__PURE__ */ React21.createElement("nav", { className: "dhl-picker-tabs", "aria-label": tr("\u5E02\u573A\u5206\u7C7B", "Market categories") }, CATEGORIES.map(([key3, label]) => /* @__PURE__ */ React21.createElement("button", { type: "button", key: key3, className: category === key3 ? "on" : "", "aria-current": category === key3 ? "page" : void 0, onClick: () => {
20544
20713
  setCategory(key3);
20545
20714
  if (["spot", "hip3", "outcome", "crypto", "tradfi", "preipo"].includes(key3)) setStrict(false);
20546
20715
  } }, t(`market.category.${key3}`, label)))), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-tablehead" }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "star", size: 15 }), header(t("market.name", "\u5E02\u573A"), "name"), header(t("market.price", "\u6700\u65B0\u4EF7"), "price"), header(t("market.change", "24h \u6DA8\u8DCC"), "change"), header(t("market.funding", "\u8D44\u91D1\u8D39"), "funding"), header(t("market.volume", "24h \u6210\u4EA4\u989D"), "volume"), header(t("market.openInterest", "\u672A\u5E73\u4ED3\u91CF"), "openInterest")), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-list", role: "listbox", "aria-label": tr("\u5E02\u573A\u5217\u8868", "Market list"), ref: listRef, onScroll: (event) => {
@@ -20615,8 +20784,8 @@ function atr14(rows) {
20615
20784
  for (const item of ranges.slice(14)) value = (value * 13 + item) / 14;
20616
20785
  return value;
20617
20786
  }
20618
- function evaluateStrategyStudies(candles, coin, interval, now = Date.now(), english = false) {
20619
- const say = (zh, en) => english ? en : zh;
20787
+ function evaluateStrategyStudies(candles, coin, interval, now = Date.now(), english2 = false) {
20788
+ const say = (zh, en) => english2 ? en : zh;
20620
20789
  const valid = candles.filter((row) => Number.isFinite(row.t) && Number.isFinite(row.c) && row.c > 0 && Number.isFinite(row.h) && Number.isFinite(row.l) && row.h >= row.l && Number.isFinite(row.v));
20621
20790
  const rows = [...new Map(valid.map((row) => [row.t, row])).values()].sort((a, b) => a.t - b.t).filter((row) => closedAt(row, interval) <= now);
20622
20791
  const latest = rows.at(-1);
@@ -20712,8 +20881,8 @@ var STATE_LABELS = {
20712
20881
  };
20713
20882
  function StrategyMenu({ candles, coin, interval }) {
20714
20883
  const menu = React22.useRef(null);
20715
- const english = tr("zh", "en") === "en";
20716
- const studies = React22.useMemo(() => evaluateStrategyStudies(candles, coin, interval, Date.now(), english), [candles, coin, interval, english]);
20884
+ const english2 = tr("zh", "en") === "en";
20885
+ const studies = React22.useMemo(() => evaluateStrategyStudies(candles, coin, interval, Date.now(), english2), [candles, coin, interval, english2]);
20717
20886
  return /* @__PURE__ */ React22.createElement("details", { className: "dhl-strategy-menu", ref: menu }, /* @__PURE__ */ React22.createElement("summary", null, tr("\u7B56\u7565\u6846\u67B6", "Strategies"), /* @__PURE__ */ React22.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React22.createElement("div", { className: "dhl-strategy-popover", role: "group", "aria-label": tr("\u4EA4\u6613\u7B56\u7565\u7814\u7A76", "Trading strategy studies") }, /* @__PURE__ */ React22.createElement("header", null, /* @__PURE__ */ React22.createElement("strong", null, tr("\u7B56\u7565\u7814\u7A76 \xB7 \u5DF2\u6536\u76D8 K \u7EBF", "Strategy studies \xB7 closed candles")), /* @__PURE__ */ React22.createElement("small", null, coin, " \xB7 ", interval)), /* @__PURE__ */ React22.createElement("p", { className: "dhl-strategy-caveat" }, tr(
20718
20887
  "\u8FD9\u4E9B\u6761\u4EF6\u7528\u4E8E\u7EC4\u7EC7\u5206\u6790\uFF0C\u672A\u5728\u672C\u5E02\u573A\u548C\u5468\u671F\u9A8C\u8BC1\u6536\u76CA\uFF1B\u9700\u8981\u6838\u5BF9\u66F4\u9AD8\u5468\u671F\u3001\u76D8\u53E3\u3001\u8D44\u91D1\u8D39\u548C\u4EA4\u6613\u6210\u672C\u3002",
20719
20888
  "These conditions structure analysis; returns are unverified for this market and interval. Check higher timeframe, book, funding and costs."
@@ -20803,7 +20972,7 @@ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
20803
20972
  const [name, setName] = React23.useState("");
20804
20973
  const [error, setError] = React23.useState("");
20805
20974
  const [busy, setBusy] = React23.useState(false);
20806
- const populate = (value) => setDraft(Object.fromEntries(Object.entries(normalizeIndicatorSettings(value)).map(([section, fields]) => [section, Object.fromEntries(Object.entries(fields).map(([key3, n2]) => [key3, String(n2)]))])));
20975
+ const populate = (value) => setDraft(Object.fromEntries(Object.entries(normalizeIndicatorSettings(value)).map(([section, fields2]) => [section, Object.fromEntries(Object.entries(fields2).map(([key3, n2]) => [key3, String(n2)]))])));
20807
20976
  React23.useEffect(() => {
20808
20977
  if (!open) return;
20809
20978
  populate(settings);
@@ -20822,7 +20991,7 @@ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
20822
20991
  cancelled = true;
20823
20992
  };
20824
20993
  }, [open]);
20825
- const values = () => normalizeIndicatorSettings(Object.fromEntries(Object.entries(draft).map(([section, fields]) => [section, Object.fromEntries(Object.entries(fields).map(([key3, n2]) => [key3, n2.trim() ? Number(n2) : NaN]))])));
20994
+ const values = () => normalizeIndicatorSettings(Object.fromEntries(Object.entries(draft).map(([section, fields2]) => [section, Object.fromEntries(Object.entries(fields2).map(([key3, n2]) => [key3, n2.trim() ? Number(n2) : NaN]))])));
20826
20995
  const apply2 = () => {
20827
20996
  try {
20828
20997
  onApply({ indicatorSettings: values(), indicators: active, maLines: lines });
@@ -20871,7 +21040,7 @@ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
20871
21040
  setTemplates(result.templates);
20872
21041
  setSelected("");
20873
21042
  }).catch((cause) => setError(String(cause.message))).finally(() => setBusy(false));
20874
- } }, tr("\u5220\u9664\u6A21\u677F", "Delete")))), /* @__PURE__ */ React23.createElement("div", { className: "dhl-study-parameter-grid" }, SECTIONS.map(([section, title, fields]) => /* @__PURE__ */ React23.createElement("fieldset", { key: section }, /* @__PURE__ */ React23.createElement("legend", null, title), fields.map(([key3, zh, en]) => /* @__PURE__ */ React23.createElement("label", { key: key3 }, tr(zh, en), /* @__PURE__ */ React23.createElement(import_dsh_client_ui_primitives4.Input, { type: "number", step: key3 === "multiplier" ? "any" : 1, "aria-label": `${title} ${tr(zh, en)}`, value: draft[section]?.[key3] ?? "", onChange: (event) => setDraft((current2) => ({ ...current2, [section]: { ...current2[section], [key3]: event.target.value } })) })))))), error ? /* @__PURE__ */ React23.createElement("p", { className: "dhl-inline-error", role: "alert" }, error) : null)
21043
+ } }, tr("\u5220\u9664\u6A21\u677F", "Delete")))), /* @__PURE__ */ React23.createElement("div", { className: "dhl-study-parameter-grid" }, SECTIONS.map(([section, title, fields2]) => /* @__PURE__ */ React23.createElement("fieldset", { key: section }, /* @__PURE__ */ React23.createElement("legend", null, title), fields2.map(([key3, zh, en]) => /* @__PURE__ */ React23.createElement("label", { key: key3 }, tr(zh, en), /* @__PURE__ */ React23.createElement(import_dsh_client_ui_primitives4.Input, { type: "number", step: key3 === "multiplier" ? "any" : 1, "aria-label": `${title} ${tr(zh, en)}`, value: draft[section]?.[key3] ?? "", onChange: (event) => setDraft((current2) => ({ ...current2, [section]: { ...current2[section], [key3]: event.target.value } })) })))))), error ? /* @__PURE__ */ React23.createElement("p", { className: "dhl-inline-error", role: "alert" }, error) : null)
20875
21044
  ));
20876
21045
  }
20877
21046
 
@@ -20885,7 +21054,7 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
20885
21054
  const [state, setState] = React24.useState({ scope: "", candles: [], error: "", older: "idle", at: 0 });
20886
21055
  const currentScope = React24.useRef(scope);
20887
21056
  currentScope.current = scope;
20888
- const feed = state.scope === scope ? state : { scope, candles: [], error: "", older: "idle", at: 0 };
21057
+ const feed2 = state.scope === scope ? state : { scope, candles: [], error: "", older: "idle", at: 0 };
20889
21058
  React24.useEffect(() => {
20890
21059
  let alive = true, busy = false;
20891
21060
  setState({ scope, candles: [], error: "", older: "idle", at: 0 });
@@ -20910,11 +21079,11 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
20910
21079
  }, [scope]);
20911
21080
  const olderBusy = React24.useRef(false);
20912
21081
  const needOlder = async () => {
20913
- if (olderBusy.current || feed.older !== "idle" || !feed.candles.length) return;
21082
+ if (olderBusy.current || feed2.older !== "idle" || !feed2.candles.length) return;
20914
21083
  olderBusy.current = true;
20915
21084
  setState((old) => ({ ...old, older: "loading" }));
20916
21085
  try {
20917
- const first = feed.candles[0].t;
21086
+ const first = feed2.candles[0].t;
20918
21087
  const result = await hyperCall("candles", { coin, interval, endMs: first - 1, startMs: first - durations[RESEARCH_INTERVALS.indexOf(interval)] * 500 });
20919
21088
  if (currentScope.current === scope) setState((old) => {
20920
21089
  const rows = merge2(result.candles, old.candles);
@@ -20927,12 +21096,12 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
20927
21096
  }
20928
21097
  };
20929
21098
  const layers = { orders: true, fills: true, reasons: false, ...s.workspace?.chartLayers };
20930
- const events = React24.useMemo(() => chartTradeEvents(feed.candles, coin, layers.orders ? s.tradeHistory?.orders ?? [] : [], layers.fills ? s.tradeHistory?.fills ?? [] : []), [feed.candles, s.tradeHistory, layers.orders, layers.fills, coin]);
20931
- return /* @__PURE__ */ React24.createElement("section", { className: "dhl-research-pane", "aria-label": `${coin} ${interval} ${tr("\u7814\u7A76\u56FE\u8868", "research chart")}` }, /* @__PURE__ */ React24.createElement("header", null, /* @__PURE__ */ React24.createElement("b", null, coin), /* @__PURE__ */ React24.createElement(Select, { "aria-label": tr("\u7814\u7A76\u5468\u671F", "Research interval"), value: interval, onChange: (e) => onInterval(e.target.value) }, RESEARCH_INTERVALS.map((iv) => /* @__PURE__ */ React24.createElement("option", { key: iv, value: iv }, iv))), /* @__PURE__ */ React24.createElement("small", null, feed.at ? `${tr("\u66F4\u65B0", "Updated")} ${new Date(feed.at).toLocaleTimeString()}` : tr("\u52A0\u8F7D\u4E2D\u2026", "Loading\u2026"))), /* @__PURE__ */ React24.createElement(
21099
+ const events = React24.useMemo(() => chartTradeEvents(feed2.candles, coin, layers.orders ? s.tradeHistory?.orders ?? [] : [], layers.fills ? s.tradeHistory?.fills ?? [] : []), [feed2.candles, s.tradeHistory, layers.orders, layers.fills, coin]);
21100
+ return /* @__PURE__ */ React24.createElement("section", { className: "dhl-research-pane", "aria-label": `${coin} ${interval} ${tr("\u7814\u7A76\u56FE\u8868", "research chart")}` }, /* @__PURE__ */ React24.createElement("header", null, /* @__PURE__ */ React24.createElement("b", null, coin), /* @__PURE__ */ React24.createElement(Select, { "aria-label": tr("\u7814\u7A76\u5468\u671F", "Research interval"), value: interval, onChange: (e) => onInterval(e.target.value) }, RESEARCH_INTERVALS.map((iv) => /* @__PURE__ */ React24.createElement("option", { key: iv, value: iv }, iv))), /* @__PURE__ */ React24.createElement("small", null, feed2.at ? `${tr("\u66F4\u65B0", "Updated")} ${new Date(feed2.at).toLocaleTimeString()}` : tr("\u52A0\u8F7D\u4E2D\u2026", "Loading\u2026"))), /* @__PURE__ */ React24.createElement(
20932
21101
  CandleChart,
20933
21102
  {
20934
21103
  key: scope,
20935
- candles: feed.candles,
21104
+ candles: feed2.candles,
20936
21105
  marks,
20937
21106
  indicators,
20938
21107
  expanded: false,
@@ -20953,7 +21122,7 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
20953
21122
  },
20954
21123
  onSelect: (selection) => insertChartReference(selection, coin, interval),
20955
21124
  onNeedOlder: () => void needOlder(),
20956
- olderState: feed.older,
21125
+ olderState: feed2.older,
20957
21126
  tradeEvents: events,
20958
21127
  showReasons: layers.fills && layers.reasons,
20959
21128
  onAnalyzeFill,
@@ -20968,7 +21137,7 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
20968
21137
  selectRequest: null,
20969
21138
  focus: null
20970
21139
  }
20971
- ), feed.error ? /* @__PURE__ */ React24.createElement("button", { type: "button", className: "dhl-inline-error", onClick: () => setState((old) => ({ ...old, older: "idle" })) }, feed.error, " \xB7 ", tr("\u81EA\u52A8\u91CD\u8BD5\u4E2D\uFF1B\u70B9\u51FB\u91CD\u8BD5\u5386\u53F2", "Retrying; click to retry history")) : null);
21140
+ ), feed2.error ? /* @__PURE__ */ React24.createElement("button", { type: "button", className: "dhl-inline-error", onClick: () => setState((old) => ({ ...old, older: "idle" })) }, feed2.error, " \xB7 ", tr("\u81EA\u52A8\u91CD\u8BD5\u4E2D\uFF1B\u70B9\u51FB\u91CD\u8BD5\u5386\u53F2", "Retrying; click to retry history")) : null);
20972
21141
  }
20973
21142
 
20974
21143
  // lib/workspace-settings.js
@@ -21180,12 +21349,22 @@ function DeskColumn({ onCollapse }) {
21180
21349
  }, []);
21181
21350
  const editMark = React26.useCallback((id, patch) => historyRef.current.edit(id, patch), []);
21182
21351
  const erase = React26.useCallback((id) => historyRef.current.remove(id), []);
21352
+ const selectMark = React26.useCallback((id) => setSelectRequest({ id, at: Date.now() }), []);
21183
21353
  const cloneMark = React26.useCallback((copy) => {
21184
21354
  void historyRef.current.create({ kind: copy.kind, label: copy.label, ...copy.geometry, ...copy.style });
21185
21355
  }, []);
21356
+ const onSecondaryChart = React26.useRef(false);
21357
+ React26.useEffect(() => {
21358
+ const track = (event) => {
21359
+ const target = event.target;
21360
+ onSecondaryChart.current = Boolean(target?.closest?.(".dhl-research-pane") && !target.closest(".dhl-primary-pane"));
21361
+ };
21362
+ window.addEventListener("pointerdown", track, true);
21363
+ return () => window.removeEventListener("pointerdown", track, true);
21364
+ }, []);
21186
21365
  React26.useEffect(() => {
21187
21366
  const onKey = (event) => {
21188
- if (!(event.metaKey || event.ctrlKey) || event.altKey) return;
21367
+ if (!(event.metaKey || event.ctrlKey) || event.altKey || onSecondaryChart.current) return;
21189
21368
  const element = event.target;
21190
21369
  if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
21191
21370
  const key3 = event.key.toLowerCase();
@@ -21225,7 +21404,7 @@ function DeskColumn({ onCollapse }) {
21225
21404
  React26.useEffect(() => {
21226
21405
  const keys = { KeyT: "trend", KeyH: "level", KeyJ: "hray", KeyV: "vline", KeyC: "crossline", KeyF: "fib" };
21227
21406
  const onKey = (event) => {
21228
- if (!event.altKey || event.ctrlKey || event.metaKey || chartTab !== "chart") return;
21407
+ if (!event.altKey || event.ctrlKey || event.metaKey || chartTab !== "chart" || onSecondaryChart.current) return;
21229
21408
  const element = event.target;
21230
21409
  if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
21231
21410
  const kind = event.shiftKey ? event.code === "KeyR" ? "rect" : void 0 : keys[event.code];
@@ -21421,9 +21600,10 @@ function DeskColumn({ onCollapse }) {
21421
21600
  {
21422
21601
  marks,
21423
21602
  locked: drawingsLocked,
21424
- onSelect: (id) => setSelectRequest({ id, at: Date.now() }),
21425
- onEdit: (id, patch) => void editMark(id, patch),
21426
- onRemove: (id) => void erase(id)
21603
+ language: tr("zh", "en"),
21604
+ onSelect: selectMark,
21605
+ onEdit: editMark,
21606
+ onRemove: erase
21427
21607
  }
21428
21608
  ) : null, /* @__PURE__ */ React26.createElement(
21429
21609
  ChartNavigation,
@@ -21638,8 +21818,8 @@ var React28 = __toESM(require("react"), 1);
21638
21818
  function TradeTicket({ onSubmitted }) {
21639
21819
  const s = useStore();
21640
21820
  const coin = s.activeCoin;
21641
- const market = s.markets.find((row) => row.name === coin);
21642
21821
  const catalogMarket = s.marketCatalog.find((row) => row.name === coin);
21822
+ const market = s.markets.find((row) => row.name === coin) ?? (catalogMarket?.tradable ? catalogMarket : void 0);
21643
21823
  const liveMid = s.snapshot?.coin === coin && Date.now() - Number(s.snapshot?.observedAt?.mids) < 1e4 ? Number(s.snapshot?.mids?.mids?.[coin] ?? s.snapshot?.mids?.[coin]) : 0;
21644
21824
  const mark = liveMid || Number(s.selectedMarket?.name === coin ? s.selectedMarket.ctx?.markPx : 0) || Number(market?.ctx?.markPx);
21645
21825
  const position = s.account?.positions?.find((p) => p.coin === coin);
@@ -21874,20 +22054,34 @@ function ChatColumn({ width, fill = false, onCollapse, SessionProvider, renderFa
21874
22054
  requestAnimationFrame(() => input.focus?.());
21875
22055
  }
21876
22056
  }, [activeId, readyRef, showTicket, s.chartReferences]);
21877
- const askMarket = React29.useCallback(() => {
21878
- queueToAgent(tr(
21879
- `\u8BF7\u8C03\u7528 hyper_context \u8BFB\u53D6\u5F53\u524D\u4EA4\u6613\u53F0\u7684 ${s.activeCoin} \u5B9E\u65F6\u884C\u60C5\u3001\u76D8\u53E3\u548C\u8D26\u6237\u4FE1\u606F\uFF0C\u6982\u62EC\u5E02\u573A\u72B6\u6001\u3001\u5173\u952E\u98CE\u9669\u4E0E\u53EF\u80FD\u7684\u4EA4\u6613\u6761\u4EF6\u3002\u4EC5\u5206\u6790\uFF0C\u4E0D\u8981\u4E0B\u5355\u3002`,
21880
- `Use hyper_context to read live ${s.activeCoin} market data, order book and account state. Summarize conditions, key risks and possible trade criteria. Analysis only; do not place an order.`
21881
- ));
21882
- }, [s.activeCoin]);
21883
- const askRisk = React29.useCallback(() => {
21884
- queueToAgent(tr(
21885
- `\u8BF7\u8C03\u7528 hyper_context \u68C0\u67E5\u5F53\u524D\u4EA4\u6613\u4F1A\u8BDD\u7684\u8D26\u6237\u3001${s.activeCoin} \u4ED3\u4F4D\u3001\u6760\u6746\u3001\u6302\u5355\u548C\u98CE\u9669\u9650\u989D\uFF0C\u6307\u51FA\u9700\u8981\u6CE8\u610F\u7684\u98CE\u9669\u4E0E\u53EF\u91C7\u53D6\u7684\u7BA1\u7406\u52A8\u4F5C\u3002\u4EC5\u5206\u6790\uFF0C\u4E0D\u8981\u4E0B\u5355\u6216\u64A4\u5355\u3002`,
21886
- `Use hyper_context to review the account, ${s.activeCoin} position, leverage, open orders and risk limits. Identify risks and possible management actions. Analysis only; do not place or cancel orders.`
21887
- ));
21888
- }, [s.activeCoin]);
22057
+ const QUICK = [
22058
+ [
22059
+ "\u4EA4\u6613\u4F53\u68C0",
22060
+ "Trading check-up",
22061
+ "\u8BF7\u7528 hyper_account action=analyze \u7ED9\u6211\u505A\u4E00\u6B21\u4EA4\u6613\u4F53\u68C0\uFF08\u8FD1 90 \u5929\uFF09\uFF1A\u5148\u8BB2\u6700\u503C\u94B1\u7684 1\u20132 \u4E2A\u53D1\u73B0\u548C\u5EFA\u8BAE\u89C4\u5219\uFF0C\u518D\u95EE\u6211\u662F\u5426\u91C7\u7528\u3002\u4E0D\u8981\u4E0B\u5355\u3002",
22062
+ "Run a trading check-up with hyper_account action=analyze (last 90 days): lead with the 1\u20132 most valuable findings and suggested rules, then ask whether I want to adopt them. Do not trade."
22063
+ ],
22064
+ [
22065
+ "\u4ECA\u5929\u6709\u4EC0\u4E48\u673A\u4F1A",
22066
+ "Today's opportunities",
22067
+ "\u8BF7\u7528 hyper_market action=scan \u770B\u770B\u4ECA\u5929\u7684\u673A\u4F1A\uFF0C\u7ED3\u5408\u6211\u7684\u6863\u6848\u6311 1 \u4E2A\u6700\u503C\u5F97\u5173\u6CE8\u7684\uFF0C\u7528 hyper_market candles/indicators \u6838\u5B9E\uFF0C\u518D\u7528 hyper_chart \u753B\u51FA\u5173\u952E\u4F4D\u548C\u5934\u5BF8\u6846\uFF0C\u6309\u4EA4\u6613\u5361\u683C\u5F0F\u7ED9\u51FA\u8BA1\u5212\u3002\u4E0D\u8981\u4E0B\u5355\u3002",
22068
+ "Use hyper_market action=scan for today's opportunities, pick the one that best fits my profile, verify it with hyper_market candles/indicators, draw the levels and a position box with hyper_chart, and give the plan as a trade card. Do not trade."
22069
+ ],
22070
+ [
22071
+ "\u5E2E\u6211\u76EF\u7740\u4ED3\u4F4D",
22072
+ "Watch my positions",
22073
+ "\u8BF7\u7528 hyper_context \u68C0\u67E5\u6211\u7684\u4ED3\u4F4D\u548C\u6302\u5355\uFF1A\u6BCF\u4E2A\u4ED3\u4F4D\u662F\u5426\u6709\u8DB3\u91CF\u6B62\u635F\u3001\u79BB\u6B62\u635F\u548C\u76EE\u6807\u591A\u8FDC\u3001\u8D44\u91D1\u8D39\u6210\u672C\uFF1B\u5728\u5173\u952E\u4EF7\u4F4D\u7528 hyper_chart \u8BBE\u4EF7\u683C\u63D0\u9192\uFF08alert=true\uFF09\uFF1B\u9700\u8981\u5B9A\u671F\u68C0\u67E5\u7684\u8BDD\u5148\u95EE\u6211\u4E00\u6B21\uFF0C\u518D\u7528 schedule_create \u5B89\u6392\u3002\u4E0D\u8981\u4E0B\u5355\u6216\u64A4\u5355\u3002",
22074
+ "Use hyper_context to check my positions and orders: is each one covered by a stop, how far are stop and target, what does funding cost; set price alerts (alert=true) at key levels with hyper_chart; if periodic checks would help, ask me once, then use schedule_create. Do not place or cancel orders."
22075
+ ],
22076
+ [
22077
+ "\u628A\u56FE\u4E0A\u753B\u7EBF\u53D8\u6210\u7B56\u7565",
22078
+ "Turn my drawings into a strategy",
22079
+ `\u8BF7\u7528 hyper_chart action=list \u8BFB\u53D6 ${s.activeCoin} \u56FE\u4E0A\u7684\u753B\u7EBF\uFF0C\u7ED3\u5408 hyper_market candles \u7406\u89E3\u6211\u7684\u601D\u8DEF\uFF0C\u6574\u7406\u6210\u4E00\u6761\u53EF\u6838\u5BF9\u7684\u7B56\u7565\uFF08\u4F9D\u636E\u3001\u5165\u573A\u3001\u79BB\u573A\u3001\u4F5C\u5E9F\u3001\u4ED3\u4F4D\uFF09\uFF0C\u7528 hyper_journal action=strategy \u5B58\u4E3A idea\uFF0C\u5E76\u8BF4\u660E\u8FD8\u9700\u8981\u9A8C\u8BC1\u4EC0\u4E48\u3002\u4E0D\u8981\u4E0B\u5355\u3002`,
22080
+ `Read my drawings on ${s.activeCoin} with hyper_chart action=list, use hyper_market candles to understand the idea, write it up as a checkable strategy (thesis, entry, exit, invalidation, sizing), save it with hyper_journal action=strategy as an idea, and say what still needs verifying. Do not trade.`
22081
+ ]
22082
+ ];
21889
22083
  const content = !activeId ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-empty" }, t("chat.noSession", "\u9009\u62E9\u5DE6\u4FA7\u4EA4\u6613\u4F1A\u8BDD\uFF0C\u5728\u8FD9\u91CC\u76F4\u63A5\u4E0E\u4EA4\u6613 agent \u5BF9\u8BDD\u3002")) : bindError ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-inline-error", role: "alert" }, t("chat.bindError", "\u4F1A\u8BDD\u7ED1\u5B9A\u5931\u8D25\uFF1A") + bindError) : legacy ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-unavailable", role: "alert" }, t("chat.upgrade", "\u5F53\u524D DSH \u7248\u672C\u4E0D\u652F\u6301\u5D4C\u5165\u539F\u751F\u4F1A\u8BDD\uFF1B\u8BF7\u4F7F\u7528 DSH 0.2 \u6216\u66F4\u65B0\u7248\u672C\u3002")) : !readyRef ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-empty" }, t("chat.linking", "\u6B63\u5728\u63A5\u5165\u4F1A\u8BDD\u2026")) : !SessionProvider || !renderFactorySlot ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-inline-error", role: "alert" }, t("chat.noFactory", "DSH \u539F\u751F\u4F1A\u8BDD\u7EC4\u4EF6\u4E0D\u53EF\u7528\u3002")) : /* @__PURE__ */ React29.createElement("div", { className: "dhl-chatmount", "data-trading-session": activeId }, /* @__PURE__ */ React29.createElement(SessionProvider, { session: readyRef }, renderFactorySlot("conversation.content", { variant: "embedded", phase: "active", hero: false }, { slots: { views: FixedChatView } })));
21890
- return /* @__PURE__ */ React29.createElement("div", { id: "dhl-panel-chat", className: "dhl-col-chat", style: fill ? { flex: "1 1 0", width: "auto" } : width ? { width } : void 0 }, /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-header" }, /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-heading" }, /* @__PURE__ */ React29.createElement("strong", null, t("chat.title", "\u4EA4\u6613\u4F1A\u8BDD")), activeId ? /* @__PURE__ */ React29.createElement("span", { title }, title) : null), /* @__PURE__ */ React29.createElement("button", { type: "button", className: "dhl-chat-ticket-toggle", "aria-expanded": showTicket, onClick: () => setShowTicket((value) => !value) }, showTicket ? t("chat.back", "\u8FD4\u56DE\u4F1A\u8BDD") : t("chat.ticket", "\u4EA4\u6613\u7968\u636E")), /* @__PURE__ */ React29.createElement("button", { className: "dhl-panel-iconbtn", type: "button", onClick: onCollapse, "aria-label": tr("\u6536\u8D77\u5BF9\u8BDD\u680F", "Collapse chat"), title: tr("\u6536\u8D77\u5BF9\u8BDD\u680F", "Collapse chat") }, /* @__PURE__ */ React29.createElement(FoldIcon, { direction: "right" }))), activeId && !showTicket && !legacy ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-prompts", role: "group", "aria-label": t("chat.prompts", "\u5FEB\u6377\u63D0\u95EE") }, /* @__PURE__ */ React29.createElement("button", { type: "button", onClick: askMarket }, t("chat.analyze", "\u5206\u6790\u884C\u60C5")), /* @__PURE__ */ React29.createElement("button", { type: "button", onClick: askRisk }, t("chat.risk", "\u68C0\u67E5\u98CE\u9669"))) : null, showTicket ? /* @__PURE__ */ React29.createElement(TradeTicket, { key: activeId ?? "anonymous", onSubmitted: () => setShowTicket(false) }) : content);
22084
+ return /* @__PURE__ */ React29.createElement("div", { id: "dhl-panel-chat", className: "dhl-col-chat", style: fill ? { flex: "1 1 0", width: "auto" } : width ? { width } : void 0 }, /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-header" }, /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-heading" }, /* @__PURE__ */ React29.createElement("strong", null, t("chat.title", "\u4EA4\u6613\u4F1A\u8BDD")), activeId ? /* @__PURE__ */ React29.createElement("span", { title }, title) : null), /* @__PURE__ */ React29.createElement("button", { type: "button", className: "dhl-chat-ticket-toggle", "aria-expanded": showTicket, onClick: () => setShowTicket((value) => !value) }, showTicket ? t("chat.back", "\u8FD4\u56DE\u4F1A\u8BDD") : t("chat.ticket", "\u4EA4\u6613\u7968\u636E")), /* @__PURE__ */ React29.createElement("button", { className: "dhl-panel-iconbtn", type: "button", onClick: onCollapse, "aria-label": tr("\u6536\u8D77\u5BF9\u8BDD\u680F", "Collapse chat"), title: tr("\u6536\u8D77\u5BF9\u8BDD\u680F", "Collapse chat") }, /* @__PURE__ */ React29.createElement(FoldIcon, { direction: "right" }))), activeId && !showTicket && !legacy ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-prompts", role: "group", "aria-label": t("chat.prompts", "\u5FEB\u6377\u63D0\u95EE") }, QUICK.map(([zh, en, askZh, askEn]) => /* @__PURE__ */ React29.createElement("button", { key: zh, type: "button", onClick: () => queueToAgent(tr(askZh, askEn)) }, tr(zh, en)))) : null, showTicket ? /* @__PURE__ */ React29.createElement(TradeTicket, { key: activeId ?? "anonymous", onSubmitted: () => setShowTicket(false) }) : content);
21891
22085
  }
21892
22086
 
21893
22087
  // src/client/components/wallet-details.tsx
@@ -22015,22 +22209,60 @@ function TradingLimits({ open, onClose }) {
22015
22209
  setBusy(false);
22016
22210
  }
22017
22211
  };
22018
- const field = (key3, label, props, wide = false) => /* @__PURE__ */ React31.createElement("label", { className: wide ? "dhl-risk-wide" : void 0 }, /* @__PURE__ */ React31.createElement("span", null, label), /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Input, { className: "dhl-risk-input", value: draft[key3], onChange: (event) => change(key3, event.target.value), required: true, ...props }));
22019
- const footer = limits ? /* @__PURE__ */ React31.createElement(React31.Fragment, null, st?.riskSettingsSource === "desk" ? /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "outline", disabled: busy, onClick: () => void reset() }, tr("\u6062\u590D\u63D2\u4EF6\u914D\u7F6E", "Restore plugin config")) : null, /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "primary", type: "submit", form: "dhl-risk-form", disabled: busy }, busy ? tr("\u4FDD\u5B58\u4E2D\u2026", "Saving\u2026") : tr("\u4FDD\u5B58", "Save"))) : null;
22020
- return /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Modal, { open, onClose, title: tr("\u4EA4\u6613\u9650\u989D\u4E0E\u7B7E\u540D\u89C4\u5219", "Trading limits & signing rules"), closeLabel: tr("\u5173\u95ED", "Close"), className: "dhl-limits-modal", footer }, /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section" }, /* @__PURE__ */ React31.createElement("h3", null, tr("\u4EA4\u6613\u9650\u989D", "Trading limits"), /* @__PURE__ */ React31.createElement("span", { className: "dhl-limits-ledger" }, paper ? tr("\u6A21\u62DF\u76D8\u8D26\u672C", "Paper ledger") : tr("\u5B9E\u76D8\u8D26\u672C", "Live ledger"))), limits ? /* @__PURE__ */ React31.createElement("form", { id: "dhl-risk-form", className: "dhl-risk-form", onSubmit: (event) => void save(event) }, field("allowedCoins", tr("\u5141\u8BB8\u5F00\u4ED3\u5E01\u5BF9", "Coins allowed to open"), { placeholder: "BTC, ETH", maxLength: 1e3 }, true), field("maxOrderUsd", tr("\u5355\u7B14\u540D\u4E49\u4EF7\u503C\u4E0A\u9650 \xB7 USD", "Max per order \xB7 USD"), { type: "number", min: "0.01", step: "any" }), field("maxDailyUsd", tr("\u6BCF\u65E5\u5F00\u4ED3\u989D\u5EA6 \xB7 USD", "Daily opening budget \xB7 USD"), { type: "number", min: "0.01", step: "any" }), field("maxPositionUsd", tr("\u5355\u5E01\u4ED3\u4F4D\u4E0A\u9650 \xB7 USD", "Max position per coin \xB7 USD"), { type: "number", min: "0.01", step: "any" }), field("maxLeverage", tr("\u6700\u5927\u6760\u6746 \xB7 \u500D", "Max leverage \xB7 x"), { type: "number", min: "1", max: "50", step: "1" }), /* @__PURE__ */ React31.createElement("p", { className: "dhl-risk-budget" }, tr("\u4ECA\u65E5\u5DF2\u7528\u989D\u5EA6\uFF08UTC\uFF09", "Used today (UTC)"), "\uFF1A", fmt(limits.usedTodayUsd ?? 0), " / ", fmt(limits.maxDailyUsd), " USD"), error ? /* @__PURE__ */ React31.createElement("p", { className: "dhl-risk-error", role: "alert" }, error) : null) : /* @__PURE__ */ React31.createElement("div", { className: "dhl-empty" }, tr("\u6B63\u5728\u8BFB\u53D6\u2026", "Loading\u2026")), /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr(
22021
- "\u4FDD\u5B58\u540E\u7ACB\u5373\u4F5C\u7528\u4E8E\u6A21\u62DF\u76D8\u548C\u5B9E\u76D8\uFF0C\u91CD\u542F\u540E\u7EE7\u7EED\u751F\u6548\u3002\u4E24\u79CD\u6A21\u5F0F\u7684\u6BCF\u65E5\u989D\u5EA6\u5206\u5F00\u8BB0\u8D26\uFF1B\u51CF\u4ED3\u548C\u5E73\u4ED3\u4E0D\u53D7\u5F00\u4ED3\u9650\u989D\u963B\u6B62\u3002",
22022
- "Changes apply immediately to paper and live trading and survive restart. Each mode has its own daily budget; reducing and closing remain available."
22023
- ))), /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section" }, /* @__PURE__ */ React31.createElement("h3", null, tr("\u6A21\u62DF\u6210\u4EA4\u6210\u672C", "Paper execution costs")), /* @__PURE__ */ React31.createElement("div", { className: "dhl-risk-form" }, ["feeBps", "slippageBps"].map((key3) => /* @__PURE__ */ React31.createElement("label", { key: key3 }, /* @__PURE__ */ React31.createElement("span", null, key3 === "feeBps" ? tr("\u624B\u7EED\u8D39 \xB7 bps", "Fee \xB7 bps") : tr("\u4E0D\u5229\u6ED1\u70B9 \xB7 bps", "Adverse slippage \xB7 bps")), /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Input, { type: "number", "aria-label": key3 === "feeBps" ? tr("\u6A21\u62DF\u624B\u7EED\u8D39 bps", "Paper fee bps") : tr("\u6A21\u62DF\u6ED1\u70B9 bps", "Paper slippage bps"), min: "0", max: "100", step: "any", value: costs[key3], onChange: (e) => setCosts((old) => ({ ...old, [key3]: e.target.value })) })))), /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr("1 bps = 0.01%\u3002\u8FD9\u662F\u6A21\u62DF\u5047\u8BBE\uFF0C\u4E0D\u662F\u5B9E\u76D8\u8D39\u7387\u3002\u4EC5\u4F5C\u7528\u4E8E\u4E4B\u540E\u7684\u6A21\u62DF\u6210\u4EA4\uFF0C\u65E7\u8BB0\u5F55\u4FDD\u7559\u539F\u503C\uFF1B\u4E0D\u6A21\u62DF\u76D8\u53E3\u6DF1\u5EA6\u3001\u8D44\u91D1\u8D39\u548C\u5F3A\u5E73\u3002", "1 bps = 0.01%. Simulation assumptions, not exchange fee tiers. Future paper fills only; historical assumptions are preserved. No depth, funding or liquidation simulation.")), /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "outline", disabled: busy, onClick: () => {
22212
+ const [tab, setTab] = React31.useState("limits");
22213
+ const [wallet, setWallet] = React31.useState({ signer: "local", web3Account: "main", tradingAddress: "" });
22214
+ const [web3Accounts, setWeb3Accounts] = React31.useState({ available: false, accounts: [] });
22215
+ React31.useEffect(() => {
22216
+ if (!open) return;
22217
+ setWallet({ signer: st?.signer ?? "local", web3Account: st?.web3Account ?? "main", tradingAddress: st?.tradingAddress ?? "" });
22218
+ void hyperCall("walletAccounts").then(setWeb3Accounts).catch(() => setWeb3Accounts({ available: false, accounts: [] }));
22219
+ }, [open]);
22220
+ const saveWallet = () => {
22221
+ setBusy(true);
22222
+ setError("");
22223
+ void hyperCall("updateWalletSettings", wallet).then(() => resetWalletView()).then(() => notify(tr("\u94B1\u5305\u8BBE\u7F6E\u5DF2\u751F\u6548", "Wallet settings applied"))).catch((cause) => setError(String(cause.message))).finally(() => setBusy(false));
22224
+ };
22225
+ React31.useEffect(() => {
22226
+ if (open) setTab("limits");
22227
+ }, [open]);
22228
+ const saveCosts = () => {
22024
22229
  setBusy(true);
22025
22230
  setError("");
22026
22231
  void hyperCall("updatePaperCosts", { costs: { feeBps: costs.feeBps.trim() ? Number(costs.feeBps) : NaN, slippageBps: costs.slippageBps.trim() ? Number(costs.slippageBps) : NaN } }).then(() => loadStatus()).then(() => notify(tr("\u6A21\u62DF\u6210\u672C\u5DF2\u4FDD\u5B58", "Paper costs saved"))).catch((cause) => setError(String(cause.message))).finally(() => setBusy(false));
22027
- } }, tr("\u4FDD\u5B58\u6A21\u62DF\u6210\u672C", "Save paper costs"))), /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section" }, /* @__PURE__ */ React31.createElement("h3", null, tr("\u94B1\u5305\u7B7E\u540D", "Wallet signing")), /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, paper ? tr("\u6A21\u62DF\u76D8\u5728\u672C\u5730\u64AE\u5408\uFF0C\u4E0D\u7B7E\u540D\uFF0C\u4E5F\u4E0D\u7ECF\u8FC7\u94B1\u5305\u89C4\u5219\u3002", "Paper orders fill locally and do not use wallet signing.") : st?.signer === "web3" ? tr(
22028
- "\u5B9E\u76D8\u8BA2\u5355\u901A\u8FC7\u9650\u989D\u68C0\u67E5\u540E\uFF0C\u7531 dsh-web3 \u94B1\u5305\u6309\u5176\u7B7E\u540D\u7B56\u7565\u5904\u7406\u3002\u7B7E\u540D\u7B56\u7565\u5728 DSH \u8BBE\u7F6E \u2192 Web3 \u94B1\u5305\u4E2D\u7BA1\u7406\u3002",
22029
- "After the limits check, live orders use the dsh-web3 wallet signing policy. Manage it in DSH Settings \u2192 Web3 wallet."
22030
- ) : tr(
22031
- "\u5B9E\u76D8\u4F7F\u7528\u63D2\u4EF6\u914D\u7F6E\u4E2D\u7684\u79C1\u94A5\u7B7E\u540D\u3002\u79C1\u94A5\u4E0E\u7B7E\u540D\u65B9\u5F0F\u4ECD\u5728 DSH \u63D2\u4EF6\u914D\u7F6E\u4E2D\u7BA1\u7406\u3002",
22032
- "Live orders use the private key from plugin configuration. Manage the key and signing method in DSH plugin settings."
22033
- )), !paper && !st?.autonomousLive ? /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr("agent \u6BCF\u7B14\u5B9E\u76D8\u4E0B\u5355\u6216\u6539\u6760\u6746\u524D\u4ECD\u9700 DSH \u5BA1\u6279\u3002", "Each agent live order or leverage change still requires DSH approval.")) : null));
22232
+ };
22233
+ const field = (key3, label, props, wide = false) => /* @__PURE__ */ React31.createElement("label", { className: wide ? "dhl-risk-wide" : void 0 }, /* @__PURE__ */ React31.createElement("span", null, label), /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Input, { className: "dhl-risk-input", value: draft[key3], onChange: (event) => change(key3, event.target.value), required: true, ...props }));
22234
+ const footer = tab === "limits" ? limits ? /* @__PURE__ */ React31.createElement(React31.Fragment, null, st?.riskSettingsSource === "desk" ? /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "outline", disabled: busy, onClick: () => void reset() }, tr("\u6062\u590D\u63D2\u4EF6\u914D\u7F6E", "Restore plugin config")) : null, /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "primary", type: "submit", form: "dhl-risk-form", disabled: busy }, busy ? tr("\u4FDD\u5B58\u4E2D\u2026", "Saving\u2026") : tr("\u4FDD\u5B58\u9650\u989D", "Save limits"))) : null : tab === "costs" ? /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "primary", disabled: busy, onClick: saveCosts }, busy ? tr("\u4FDD\u5B58\u4E2D\u2026", "Saving\u2026") : tr("\u4FDD\u5B58\u6A21\u62DF\u6210\u672C", "Save paper costs")) : /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "primary", disabled: busy, onClick: saveWallet }, busy ? tr("\u8FDE\u63A5\u4E2D\u2026", "Connecting\u2026") : tr("\u4FDD\u5B58\u5E76\u8FDE\u63A5", "Save & connect"));
22235
+ const tabs = [["limits", "\u4EA4\u6613\u9650\u989D", "Limits"], ["costs", "\u6A21\u62DF\u6210\u672C", "Paper costs"], ["signing", "\u7B7E\u540D\u4E0E\u6258\u7BA1", "Signing & custody"]];
22236
+ return /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Modal, { open, onClose, title: tr("\u4EA4\u6613\u9650\u989D\u4E0E\u7B7E\u540D\u89C4\u5219", "Trading limits & signing rules"), closeLabel: tr("\u5173\u95ED", "Close"), className: "dhl-limits-modal", contentClassName: "dhl-limits-content", footer }, /* @__PURE__ */ React31.createElement("div", { className: "dhl-limits-tabs", role: "tablist", "aria-label": tr("\u8BBE\u7F6E\u5206\u7EC4", "Settings sections") }, tabs.map(([key3, zh, en]) => /* @__PURE__ */ React31.createElement(
22237
+ "button",
22238
+ {
22239
+ key: key3,
22240
+ type: "button",
22241
+ role: "tab",
22242
+ id: `dhl-limits-tab-${key3}`,
22243
+ "aria-controls": `dhl-limits-panel-${key3}`,
22244
+ "aria-selected": tab === key3,
22245
+ className: tab === key3 ? "on" : "",
22246
+ onClick: () => {
22247
+ setTab(key3);
22248
+ setError("");
22249
+ }
22250
+ },
22251
+ tr(zh, en)
22252
+ ))), /* @__PURE__ */ React31.createElement("div", { className: "dhl-limits-scroll" }, tab === "limits" ? /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section", role: "tabpanel", id: "dhl-limits-panel-limits", "aria-labelledby": "dhl-limits-tab-limits" }, /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-ledger-row" }, /* @__PURE__ */ React31.createElement("span", { className: "dhl-limits-ledger" }, paper ? tr("\u6A21\u62DF\u76D8\u8D26\u672C", "Paper ledger") : tr("\u5B9E\u76D8\u8D26\u672C", "Live ledger")), limits ? /* @__PURE__ */ React31.createElement("span", null, tr("\u4ECA\u65E5\u5DF2\u7528\uFF08UTC\uFF09", "Used today (UTC)"), " ", fmt(limits.usedTodayUsd ?? 0), " / ", fmt(limits.maxDailyUsd), " USD") : null), limits ? /* @__PURE__ */ React31.createElement("form", { id: "dhl-risk-form", className: "dhl-risk-form", onSubmit: (event) => void save(event) }, field("allowedCoins", tr("\u5141\u8BB8\u5F00\u4ED3\u5E01\u5BF9\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u5982 BTC, xyz:XYZ100\uFF09", "Coins allowed to open (comma separated)"), { placeholder: "BTC, ETH, xyz:XYZ100", maxLength: 1e3 }, true), field("maxOrderUsd", tr("\u5355\u7B14\u4E0A\u9650 \xB7 USD", "Per order \xB7 USD"), { type: "number", min: "0.01", step: "any" }), field("maxDailyUsd", tr("\u6BCF\u65E5\u5F00\u4ED3\u989D\u5EA6 \xB7 USD", "Daily budget \xB7 USD"), { type: "number", min: "0.01", step: "any" }), field("maxPositionUsd", tr("\u5355\u5E01\u4ED3\u4F4D\u4E0A\u9650 \xB7 USD", "Per coin position \xB7 USD"), { type: "number", min: "0.01", step: "any" }), field("maxLeverage", tr("\u6700\u5927\u6760\u6746 \xB7 \u500D", "Max leverage \xB7 x"), { type: "number", min: "1", max: "50", step: "1" })) : /* @__PURE__ */ React31.createElement("div", { className: "dhl-empty" }, tr("\u6B63\u5728\u8BFB\u53D6\u2026", "Loading\u2026")), /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr(
22253
+ "\u7ACB\u5373\u4F5C\u7528\u4E8E\u6A21\u62DF\u76D8\u548C\u5B9E\u76D8\uFF0C\u91CD\u542F\u540E\u4FDD\u7559\uFF1B\u4E24\u79CD\u6A21\u5F0F\u7684\u6BCF\u65E5\u989D\u5EA6\u5206\u5F00\u8BB0\u8D26\uFF0C\u51CF\u4ED3\u548C\u5E73\u4ED3\u4E0D\u53D7\u9650\u3002",
22254
+ "Applies to paper and live at once and survives restart; each mode has its own daily budget; reducing and closing are never blocked."
22255
+ ))) : null, tab === "costs" ? /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section", role: "tabpanel", id: "dhl-limits-panel-costs", "aria-labelledby": "dhl-limits-tab-costs" }, /* @__PURE__ */ React31.createElement("div", { className: "dhl-risk-form" }, ["feeBps", "slippageBps"].map((key3) => /* @__PURE__ */ React31.createElement("label", { key: key3 }, /* @__PURE__ */ React31.createElement("span", null, key3 === "feeBps" ? tr("\u624B\u7EED\u8D39 \xB7 bps", "Fee \xB7 bps") : tr("\u4E0D\u5229\u6ED1\u70B9 \xB7 bps", "Adverse slippage \xB7 bps")), /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Input, { type: "number", "aria-label": key3 === "feeBps" ? tr("\u6A21\u62DF\u624B\u7EED\u8D39 bps", "Paper fee bps") : tr("\u6A21\u62DF\u6ED1\u70B9 bps", "Paper slippage bps"), min: "0", max: "100", step: "any", value: costs[key3], onChange: (e) => setCosts((old) => ({ ...old, [key3]: e.target.value })) })))), /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr("1 bps = 0.01%\u3002\u4EC5\u5F71\u54CD\u4E4B\u540E\u7684\u6A21\u62DF\u6210\u4EA4\uFF1B\u4E0D\u6A21\u62DF\u76D8\u53E3\u6DF1\u5EA6\u3001\u8D44\u91D1\u8D39\u548C\u5F3A\u5E73\u3002", "1 bps = 0.01%. Future paper fills only; no depth, funding or liquidation simulation."))) : null, tab === "signing" ? /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section", role: "tabpanel", id: "dhl-limits-panel-signing", "aria-labelledby": "dhl-limits-tab-signing" }, /* @__PURE__ */ React31.createElement("div", { className: "dhl-risk-form dhl-wallet-form" }, /* @__PURE__ */ React31.createElement("label", null, /* @__PURE__ */ React31.createElement("span", null, tr("\u7B7E\u540D\u65B9\u5F0F", "Signer")), /* @__PURE__ */ React31.createElement(Select, { "aria-label": tr("\u7B7E\u540D\u65B9\u5F0F", "Signer"), value: wallet.signer, onChange: (event) => setWallet((old) => ({ ...old, signer: event.target.value })) }, /* @__PURE__ */ React31.createElement("option", { value: "web3" }, tr("dsh-web3 \u94B1\u5305", "dsh-web3 wallet")), /* @__PURE__ */ React31.createElement("option", { value: "local" }, tr("\u63D2\u4EF6\u914D\u7F6E\u7684\u79C1\u94A5", "Plugin private key")))), wallet.signer === "web3" ? /* @__PURE__ */ React31.createElement("label", null, /* @__PURE__ */ React31.createElement("span", null, tr("\u94B1\u5305\u8D26\u6237", "Wallet account")), /* @__PURE__ */ React31.createElement(Select, { "aria-label": tr("\u94B1\u5305\u8D26\u6237", "Wallet account"), value: wallet.web3Account, onChange: (event) => setWallet((old) => ({ ...old, web3Account: event.target.value })) }, web3Accounts.accounts.some((row) => row.id === wallet.web3Account || row.label === wallet.web3Account) ? null : /* @__PURE__ */ React31.createElement("option", { value: wallet.web3Account }, wallet.web3Account), web3Accounts.accounts.map((row) => /* @__PURE__ */ React31.createElement("option", { key: row.id, value: row.id }, `${row.label} \xB7 ${row.address.slice(0, 6)}\u2026${row.address.slice(-4)}`)))) : null, /* @__PURE__ */ React31.createElement("label", { className: "dhl-risk-wide" }, /* @__PURE__ */ React31.createElement("span", null, tr("Hyperliquid \u4E3B\u8D26\u6237\u5730\u5740\uFF08\u4EA4\u6613\u4E0E\u67E5\u8BE2\u7528\uFF09", "Hyperliquid main account address")), /* @__PURE__ */ React31.createElement(
22256
+ import_dsh_client_ui_primitives9.Input,
22257
+ {
22258
+ "aria-label": tr("\u4E3B\u8D26\u6237\u5730\u5740", "Main account address"),
22259
+ placeholder: "0x\u2026",
22260
+ value: wallet.tradingAddress,
22261
+ maxLength: 42,
22262
+ spellCheck: false,
22263
+ onChange: (event) => setWallet((old) => ({ ...old, tradingAddress: event.target.value.trim() }))
22264
+ }
22265
+ ))), wallet.signer === "web3" && !web3Accounts.available ? /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr("\u6CA1\u6709\u68C0\u6D4B\u5230 dsh-web3 \u94B1\u5305\u63D2\u4EF6\u3002", "The dsh-web3 wallet plugin is not available.")) : null, /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr("\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF0C\u65E0\u9700\u91CD\u542F\uFF1B\u79C1\u94A5\u4ECD\u53EA\u5728\u63D2\u4EF6\u914D\u7F6E\u91CC\u7BA1\u7406\u3002\u65B0\u5EFA\u7684 dsh-web3 \u8D26\u6237\u82E5\u4E0D\u5728\u5217\u8868\u4E2D\uFF0C\u9700\u91CD\u542F DSH \u5237\u65B0\u94B1\u5305\u3002", "Applies immediately, no restart; the private key stays in plugin config. A dsh-web3 account created elsewhere may need a DSH restart to appear.")), /* @__PURE__ */ React31.createElement("dl", { className: "dhl-limits-facts" }, /* @__PURE__ */ React31.createElement("dt", null, tr("\u5F53\u524D", "Current")), /* @__PURE__ */ React31.createElement("dd", null, paper ? tr("\u6A21\u62DF\u76D8\u672C\u5730\u64AE\u5408\uFF0C\u4E0D\u7B7E\u540D\u3002", "Paper fills locally without signing.") : st?.signer === "web3" ? tr("dsh-web3 \u94B1\u5305\uFF0C\u6309\u5176\u7B7E\u540D\u7B56\u7565\u5904\u7406\uFF08DSH \u8BBE\u7F6E \u2192 Web3 \u94B1\u5305\uFF09\u3002", "dsh-web3 wallet, under its signing policy (DSH Settings \u2192 Web3 wallet).") : tr("\u63D2\u4EF6\u914D\u7F6E\u4E2D\u7684\u79C1\u94A5\uFF08DSH \u63D2\u4EF6\u914D\u7F6E\uFF09\u3002", "Private key from plugin configuration.")), /* @__PURE__ */ React31.createElement("dt", null, tr("\u6258\u7BA1\u6A21\u5F0F", "Custody")), /* @__PURE__ */ React31.createElement("dd", null, st?.custody === "managed" ? tr("\u5168\u6258\u7BA1\uFF1Aagent \u5728\u9650\u989D\u5185\u76F4\u63A5\u6267\u884C\uFF0C\u4E5F\u53EF\u8C03\u6574\u8FD9\u4E9B\u9650\u989D\u5E76\u5212\u8F6C HIP-3 \u4FDD\u8BC1\u91D1\uFF0C\u6BCF\u6B21\u90FD\u4F1A\u5728\u4F1A\u8BDD\u91CC\u8BF4\u660E\u3002", "Managed: the agent trades within limits, may adjust them and move HIP-3 margin, and says so each time.") : tr("\u534A\u6258\u7BA1\uFF1Aagent \u6BCF\u7B14\u5B9E\u76D8\u4E0B\u5355\u3001\u6539\u6760\u6746\u548C\u5212\u8F6C\u90FD\u9700\u8981\u4F60\u6279\u51C6\uFF0C\u9650\u989D\u53EA\u7531\u4F60\u4FEE\u6539\u3002", "Assisted: you approve each live order, leverage change and transfer; only you change limits."), /* @__PURE__ */ React31.createElement("span", { className: "dhl-limits-hint" }, tr("\u5728\u53F3\u4E0A\u89D2 \u8BBE\u7F6E \u2192 \u6258\u7BA1\u6A21\u5F0F \u5207\u6362\u3002", "Switch in Settings \u2192 Custody."))))) : null, error ? /* @__PURE__ */ React31.createElement("p", { className: "dhl-risk-error", role: "alert" }, error) : null));
22034
22266
  }
22035
22267
 
22036
22268
  // src/client/app.tsx
@@ -22043,6 +22275,22 @@ function App(props) {
22043
22275
  const [walletOpen, setWalletOpen] = React32.useState(false);
22044
22276
  const [moreOpen, setMoreOpen] = React32.useState(false);
22045
22277
  const [limitsOpen, setLimitsOpen] = React32.useState(false);
22278
+ const [confirmManaged, setConfirmManaged] = React32.useState(false);
22279
+ const custody = st?.custody === "managed" ? "managed" : "assisted";
22280
+ const chooseCustody = React32.useCallback(async (next, acknowledge = false) => {
22281
+ setMoreOpen(false);
22282
+ if (next === "managed" && store.get().status?.tradingMode === "live" && !acknowledge) {
22283
+ setConfirmManaged(true);
22284
+ return;
22285
+ }
22286
+ try {
22287
+ await setCustody(next, acknowledge);
22288
+ } catch (error) {
22289
+ notify(tr("\u5207\u6362\u6258\u7BA1\u6A21\u5F0F\u5931\u8D25", "Could not switch custody") + "\uFF1A" + String(error?.message ?? error));
22290
+ } finally {
22291
+ setConfirmManaged(false);
22292
+ }
22293
+ }, []);
22046
22294
  const moreRef = React32.useRef(null);
22047
22295
  const network = st?.network === "testnet" ? "testnet" : "mainnet";
22048
22296
  const mode = st?.tradingMode === "paper" ? "paper" : "live";
@@ -22150,12 +22398,12 @@ function App(props) {
22150
22398
  const showSessions = !layout.sessionsCollapsed;
22151
22399
  const showDesk = !layout.deskCollapsed;
22152
22400
  const showChat = !layout.chatCollapsed;
22153
- return /* @__PURE__ */ React32.createElement("div", { className: "dhl-root" }, /* @__PURE__ */ React32.createElement("div", { className: "dhl-topbar" }, /* @__PURE__ */ React32.createElement("span", { className: "dhl-brand" }, /* @__PURE__ */ React32.createElement(BrandMark, null), "Hyperliquid ", /* @__PURE__ */ React32.createElement("span", { style: { fontWeight: 400, color: "var(--dsw-alias-label-secondary)" } }, t("app.suffix", "\u4EA4\u6613\u53F0"))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-spacer" }), s.statusError ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge err" }, t("status.unavailable", "\u8FDC\u7AEF\u4E0D\u53EF\u7528")) : null, st && network === "testnet" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge paper" }, t("network.testnet", "\u6D4B\u8BD5\u7F51")) : null, st && mode === "paper" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge paper" }, t("mode.paper", "\u6A21\u62DF\u76D8")) : null, wsBadge, /* @__PURE__ */ React32.createElement("button", { type: "button", className: "dhl-badge dhl-wallet-trigger", onClick: () => setWalletOpen(true), "aria-haspopup": "dialog", "aria-label": tr("\u67E5\u770B\u94B1\u5305\u8BE6\u60C5\u548C\u6536\u76CA", "View wallet and P&L") }, mode === "paper" ? tr("\u6A21\u62DF\u8D26\u6237", "Paper account") : st?.address ? st.address.slice(0, 6) + "\u2026" + st.address.slice(-4) : t("account.none", "\u672A\u8FDE\u63A5\u94B1\u5305"), /* @__PURE__ */ React32.createElement(UiIcon, { name: "chevronDown", size: 14 })), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more", ref: moreRef }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: "dhl-top-control dhl-more-trigger", "aria-haspopup": "menu", "aria-expanded": moreOpen, "aria-label": tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings"), title: tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings"), onClick: () => setMoreOpen((value) => !value) }, /* @__PURE__ */ React32.createElement(UiIcon, { name: "settings", size: 17 })), moreOpen ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-popover", role: "menu" }, /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, t("network.label", "\u7F51\u7EDC")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: network === "mainnet" ? "on" : "", disabled: networkBusy || !st, onClick: () => network !== "mainnet" && requestEnv({ network: "mainnet", mode }) }, t("network.mainnet", "\u4E3B\u7F51")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: network === "testnet" ? "on" : "", disabled: networkBusy || !st, onClick: () => network !== "testnet" && requestEnv({ network: "testnet", mode }) }, t("network.testnet", "\u6D4B\u8BD5\u7F51")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u8D26\u6237", "Account")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: mode === "live" ? "on" : "", disabled: networkBusy || !st, onClick: () => mode !== "live" && requestEnv({ network, mode: "live" }) }, t("mode.live", "\u5B9E\u76D8")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: mode === "paper" ? "on" : "", disabled: networkBusy || !st, onClick: () => mode !== "paper" && requestEnv({ network, mode: "paper" }) }, t("mode.paper", "\u6A21\u62DF\u76D8")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u76D8\u53E3 / \u6210\u4EA4\u4F4D\u7F6E", "Book / trades position")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: s.tapePlacement === "auto" ? "on" : "", onClick: () => setTapePlacement("auto") }, tr("\u81EA\u9002\u5E94", "Auto")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: s.tapePlacement === "below" ? "on" : "", onClick: () => setTapePlacement("below") }, tr("\u56FE\u8868\u4E0B\u65B9", "Below chart")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", onClick: () => {
22401
+ return /* @__PURE__ */ React32.createElement("div", { className: "dhl-root" }, /* @__PURE__ */ React32.createElement("div", { className: "dhl-topbar" }, /* @__PURE__ */ React32.createElement("span", { className: "dhl-brand" }, /* @__PURE__ */ React32.createElement(BrandMark, null), "Hyperliquid ", /* @__PURE__ */ React32.createElement("span", { style: { fontWeight: 400, color: "var(--dsw-alias-label-secondary)" } }, t("app.suffix", "\u4EA4\u6613\u53F0"))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-spacer" }), s.statusError ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge err" }, t("status.unavailable", "\u8FDC\u7AEF\u4E0D\u53EF\u7528")) : null, st && network === "testnet" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge paper" }, t("network.testnet", "\u6D4B\u8BD5\u7F51")) : null, st && mode === "paper" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge paper" }, t("mode.paper", "\u6A21\u62DF\u76D8")) : null, st && custody === "managed" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge managed", title: tr("\u5168\u6258\u7BA1\uFF1Aagent \u53EF\u81EA\u4E3B\u6267\u884C\u5B9E\u76D8\u3001\u8C03\u6574\u9650\u989D\u4E0E\u5212\u8F6C\u4FDD\u8BC1\u91D1", "Managed: the agent trades, adjusts limits and moves margin on its own") }, tr("\u5168\u6258\u7BA1", "Managed")) : null, wsBadge, /* @__PURE__ */ React32.createElement("button", { type: "button", className: "dhl-badge dhl-wallet-trigger", onClick: () => setWalletOpen(true), "aria-haspopup": "dialog", "aria-label": tr("\u67E5\u770B\u94B1\u5305\u8BE6\u60C5\u548C\u6536\u76CA", "View wallet and P&L") }, mode === "paper" ? tr("\u6A21\u62DF\u8D26\u6237", "Paper account") : st?.address ? st.address.slice(0, 6) + "\u2026" + st.address.slice(-4) : t("account.none", "\u672A\u8FDE\u63A5\u94B1\u5305"), /* @__PURE__ */ React32.createElement(UiIcon, { name: "chevronDown", size: 14 })), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more", ref: moreRef }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: "dhl-top-control dhl-more-trigger", "aria-haspopup": "menu", "aria-expanded": moreOpen, "aria-label": tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings"), title: tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings"), onClick: () => setMoreOpen((value) => !value) }, /* @__PURE__ */ React32.createElement(UiIcon, { name: "settings", size: 17 })), moreOpen ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-popover", role: "menu" }, /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, t("network.label", "\u7F51\u7EDC")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: network === "mainnet" ? "on" : "", disabled: networkBusy || !st, onClick: () => network !== "mainnet" && requestEnv({ network: "mainnet", mode }) }, t("network.mainnet", "\u4E3B\u7F51")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: network === "testnet" ? "on" : "", disabled: networkBusy || !st, onClick: () => network !== "testnet" && requestEnv({ network: "testnet", mode }) }, t("network.testnet", "\u6D4B\u8BD5\u7F51")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u8D26\u6237", "Account")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: mode === "live" ? "on" : "", disabled: networkBusy || !st, onClick: () => mode !== "live" && requestEnv({ network, mode: "live" }) }, t("mode.live", "\u5B9E\u76D8")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: mode === "paper" ? "on" : "", disabled: networkBusy || !st, onClick: () => mode !== "paper" && requestEnv({ network, mode: "paper" }) }, t("mode.paper", "\u6A21\u62DF\u76D8")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u6258\u7BA1\u6A21\u5F0F", "Custody")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment", role: "group", "aria-label": tr("\u6258\u7BA1\u6A21\u5F0F", "Custody") }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: custody === "assisted" ? "on" : "", "aria-pressed": custody === "assisted", disabled: !st, title: tr("\u5B9E\u76D8\u4E0B\u5355\u3001\u5212\u8F6C\u9700\u4F60\u6279\u51C6\uFF1B\u9650\u989D\u53EA\u7531\u4F60\u4FEE\u6539", "You approve live orders and transfers; only you change limits"), onClick: () => custody !== "assisted" && void chooseCustody("assisted") }, tr("\u534A\u6258\u7BA1", "Assisted")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: custody === "managed" ? "on" : "", "aria-pressed": custody === "managed", disabled: !st, title: tr("agent \u81EA\u4E3B\u6267\u884C\u5B9E\u76D8\u3001\u8C03\u6574\u9650\u989D\u4E0E\u5212\u8F6C\u4FDD\u8BC1\u91D1", "The agent trades, adjusts limits and moves margin on its own"), onClick: () => custody !== "managed" && void chooseCustody("managed") }, tr("\u5168\u6258\u7BA1", "Managed")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u76D8\u53E3 / \u6210\u4EA4\u4F4D\u7F6E", "Book / trades position")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: s.tapePlacement === "auto" ? "on" : "", onClick: () => setTapePlacement("auto") }, tr("\u81EA\u9002\u5E94", "Auto")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: s.tapePlacement === "below" ? "on" : "", onClick: () => setTapePlacement("below") }, tr("\u56FE\u8868\u4E0B\u65B9", "Below chart")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", onClick: () => {
22154
22402
  setMoreOpen(false);
22155
22403
  setLimitsOpen(true);
22156
22404
  } }, tr("\u4EA4\u6613\u9650\u989D\u4E0E\u7B7E\u540D\u89C4\u5219", "Limits & signing rules"))))) : null)), /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Modal, { open: Boolean(confirmLive), onClose: () => setConfirmLive(null), title: tr("\u5207\u6362\u5230\u4E3B\u7F51\u5B9E\u76D8", "Switch to mainnet live"), closeLabel: t("modal.close", "\u5173\u95ED"), className: "dhl-network-modal", footer: /* @__PURE__ */ React32.createElement(React32.Fragment, null, /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Button, { variant: "outline", onClick: () => setConfirmLive(null) }, t("modal.cancel", "\u53D6\u6D88")), /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Button, { variant: "primary", disabled: networkBusy, onClick: () => {
22157
22405
  if (confirmLive) void applyEnv(confirmLive);
22158
- } }, tr("\u786E\u8BA4\u5207\u6362", "Switch"))) }, /* @__PURE__ */ React32.createElement("p", null, tr("\u4E3B\u7F51\u5B9E\u76D8\u4F1A\u663E\u793A\u771F\u5B9E\u884C\u60C5\u548C\u771F\u5B9E\u8D26\u6237\uFF0C\u540E\u7EED\u4EA4\u6613\u5C06\u4F7F\u7528\u771F\u5B9E\u8D44\u91D1\u3002", "Mainnet live shows real markets and your real account; trades use real funds."))), /* @__PURE__ */ React32.createElement(WalletDetails, { open: walletOpen, onClose: () => setWalletOpen(false) }), /* @__PURE__ */ React32.createElement(TradingLimits, { open: limitsOpen, onClose: () => setLimitsOpen(false) }), /* @__PURE__ */ React32.createElement("div", { className: "dhl-toasts" }, marketStale && !resyncing ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-data-warning", role: "status" }, tr("\u884C\u60C5\u6216\u5BBF\u4E3B\u72B6\u6001\u672A\u53CA\u65F6\u66F4\u65B0\uFF1B\u5F53\u524D\u4EF7\u683C\u53EF\u80FD\u8FC7\u671F\u3002\u4EA4\u6613\u7968\u636E\u5DF2\u6682\u505C\u63D0\u4EA4\uFF0C\u7B49\u5F85\u6570\u636E\u6062\u590D\u3002", "Market or host data is stale. Ticket submission is paused until data recovers.")) : null, s.notice ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-notice", onClick: () => notify(null) }, /* @__PURE__ */ React32.createElement(UiIcon, { name: "info", size: 16 }), /* @__PURE__ */ React32.createElement("span", { style: { flex: 1 } }, s.notice.text), /* @__PURE__ */ React32.createElement(UiIcon, { name: "close", size: 14 })) : null), /* @__PURE__ */ React32.createElement("div", { className: "dhl-body" }, showSessions ? /* @__PURE__ */ React32.createElement(SessionsColumn, { width: layout.sessionsWidth, fill: !showDesk && !showChat, onCollapse: toggleSessions }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleSessions, "aria-label": tr("\u5C55\u5F00\u4F1A\u8BDD\u680F", "Expand sessions"), title: tr("\u5C55\u5F00\u4F1A\u8BDD\u680F", "Expand sessions") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "sessions" })), showSessions && (showDesk || showChat) ? /* @__PURE__ */ React32.createElement(Divider, { side: "left", onResize: resizeSessions, onCommit: commitLayout, onReset: resetSessions }) : null, showDesk ? /* @__PURE__ */ React32.createElement(DeskColumn, { onCollapse: toggleDesk }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleDesk, "aria-label": tr("\u5C55\u5F00\u884C\u60C5\u533A", "Expand market panel"), title: tr("\u5C55\u5F00\u884C\u60C5\u533A", "Expand market panel") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "desk" })), showDesk && showChat ? /* @__PURE__ */ React32.createElement(Divider, { side: "right", onResize: resizeChat, onCommit: commitLayout, onReset: resetChat }) : null, showChat ? /* @__PURE__ */ React32.createElement(ChatColumn, { width: layout.chatWidth, fill: !showDesk, onCollapse: toggleChat, SessionProvider: props.SessionProvider ?? props.sessionProvider, renderFactorySlot: props.renderFactorySlot }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleChat, "aria-label": tr("\u5C55\u5F00\u5BF9\u8BDD\u680F", "Expand chat"), title: tr("\u5C55\u5F00\u5BF9\u8BDD\u680F", "Expand chat") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "chat" }))));
22406
+ } }, tr("\u786E\u8BA4\u5207\u6362", "Switch"))) }, /* @__PURE__ */ React32.createElement("p", null, tr("\u4E3B\u7F51\u5B9E\u76D8\u4F1A\u663E\u793A\u771F\u5B9E\u884C\u60C5\u548C\u771F\u5B9E\u8D26\u6237\uFF0C\u540E\u7EED\u4EA4\u6613\u5C06\u4F7F\u7528\u771F\u5B9E\u8D44\u91D1\u3002", "Mainnet live shows real markets and your real account; trades use real funds."))), /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Modal, { open: confirmManaged, onClose: () => setConfirmManaged(false), title: tr("\u5F00\u542F\u5168\u6258\u7BA1", "Turn on managed mode"), closeLabel: t("modal.close", "\u5173\u95ED"), className: "dhl-network-modal", footer: /* @__PURE__ */ React32.createElement(React32.Fragment, null, /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Button, { variant: "outline", onClick: () => setConfirmManaged(false) }, t("modal.cancel", "\u53D6\u6D88")), /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Button, { variant: "primary", onClick: () => void chooseCustody("managed", true) }, tr("\u5F00\u542F\u5168\u6258\u7BA1", "Turn on"))) }, /* @__PURE__ */ React32.createElement("p", null, tr("\u5168\u6258\u7BA1\u540E\uFF0Cagent \u5728\u5F53\u524D\u5B9E\u76D8\u8D26\u6237\u4E2D\u53EF\u4EE5\uFF1A", "In managed mode the agent can, on this live account:")), /* @__PURE__ */ React32.createElement("ul", { className: "dhl-managed-list" }, /* @__PURE__ */ React32.createElement("li", null, tr("\u4E0D\u7ECF\u9010\u7B14\u6279\u51C6\u76F4\u63A5\u4E0B\u5355\u3001\u64A4\u5355\u548C\u8C03\u6574\u6760\u6746\uFF1B", "place, cancel and re-lever orders without per-trade approval;")), /* @__PURE__ */ React32.createElement("li", null, tr("\u81EA\u884C\u4FEE\u6539\u5141\u8BB8\u4EA4\u6613\u7684\u5E01\u5BF9\u4E0E\u5355\u7B14\u3001\u6BCF\u65E5\u3001\u4ED3\u4F4D\u3001\u6760\u6746\u4E0A\u9650\uFF1B", "change the allowed coins and the order, daily, position and leverage limits;")), /* @__PURE__ */ React32.createElement("li", null, tr("\u5728\u4E3B\u6C38\u7EED\u8D26\u6237\u4E0E HIP-3 \u4EA4\u6613\u6240\u4E4B\u95F4\u5212\u8F6C USDC \u4FDD\u8BC1\u91D1\u3002", "move USDC margin between the core perp account and HIP-3 dexes."))), /* @__PURE__ */ React32.createElement("p", null, tr("dsh-web3 \u94B1\u5305\u81EA\u8EAB\u7684\u7B7E\u540D\u7B56\u7565\u4ECD\u7136\u751F\u6548\u3002\u4F60\u53EF\u4EE5\u968F\u65F6\u5207\u56DE\u534A\u6258\u7BA1\u3002", "The dsh-web3 wallet signing policy still applies. You can switch back to assisted at any time."))), /* @__PURE__ */ React32.createElement(WalletDetails, { open: walletOpen, onClose: () => setWalletOpen(false) }), /* @__PURE__ */ React32.createElement(TradingLimits, { open: limitsOpen, onClose: () => setLimitsOpen(false) }), /* @__PURE__ */ React32.createElement("div", { className: "dhl-toasts" }, marketStale && !resyncing ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-data-warning", role: "status" }, tr("\u884C\u60C5\u6216\u5BBF\u4E3B\u72B6\u6001\u672A\u53CA\u65F6\u66F4\u65B0\uFF1B\u5F53\u524D\u4EF7\u683C\u53EF\u80FD\u8FC7\u671F\u3002\u4EA4\u6613\u7968\u636E\u5DF2\u6682\u505C\u63D0\u4EA4\uFF0C\u7B49\u5F85\u6570\u636E\u6062\u590D\u3002", "Market or host data is stale. Ticket submission is paused until data recovers.")) : null, s.notice ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-notice", onClick: () => notify(null) }, /* @__PURE__ */ React32.createElement(UiIcon, { name: "info", size: 16 }), /* @__PURE__ */ React32.createElement("span", { style: { flex: 1 } }, s.notice.text), /* @__PURE__ */ React32.createElement(UiIcon, { name: "close", size: 14 })) : null), /* @__PURE__ */ React32.createElement("div", { className: "dhl-body" }, showSessions ? /* @__PURE__ */ React32.createElement(SessionsColumn, { width: layout.sessionsWidth, fill: !showDesk && !showChat, onCollapse: toggleSessions }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleSessions, "aria-label": tr("\u5C55\u5F00\u4F1A\u8BDD\u680F", "Expand sessions"), title: tr("\u5C55\u5F00\u4F1A\u8BDD\u680F", "Expand sessions") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "sessions" })), showSessions && (showDesk || showChat) ? /* @__PURE__ */ React32.createElement(Divider, { side: "left", onResize: resizeSessions, onCommit: commitLayout, onReset: resetSessions }) : null, showDesk ? /* @__PURE__ */ React32.createElement(DeskColumn, { onCollapse: toggleDesk }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleDesk, "aria-label": tr("\u5C55\u5F00\u884C\u60C5\u533A", "Expand market panel"), title: tr("\u5C55\u5F00\u884C\u60C5\u533A", "Expand market panel") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "desk" })), showDesk && showChat ? /* @__PURE__ */ React32.createElement(Divider, { side: "right", onResize: resizeChat, onCommit: commitLayout, onReset: resetChat }) : null, showChat ? /* @__PURE__ */ React32.createElement(ChatColumn, { width: layout.chatWidth, fill: !showDesk, onCollapse: toggleChat, SessionProvider: props.SessionProvider ?? props.sessionProvider, renderFactorySlot: props.renderFactorySlot }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleChat, "aria-label": tr("\u5C55\u5F00\u5BF9\u8BDD\u680F", "Expand chat"), title: tr("\u5C55\u5F00\u5BF9\u8BDD\u680F", "Expand chat") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "chat" }))));
22159
22407
  }
22160
22408
 
22161
22409
  // src/client/components/panel-entry.tsx