dsh-hyperliquid 0.2.0 → 0.3.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
@@ -24,9 +24,9 @@ var __export = (target, all) => {
24
24
  };
25
25
  var __copyProps = (to, from, except, desc) => {
26
26
  if (from && typeof from === "object" || typeof from === "function") {
27
- for (let key2 of __getOwnPropNames(from))
28
- if (!__hasOwnProp.call(to, key2) && key2 !== except)
29
- __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
27
+ for (let key3 of __getOwnPropNames(from))
28
+ if (!__hasOwnProp.call(to, key3) && key3 !== except)
29
+ __defProp(to, key3, { get: () => from[key3], enumerable: !(desc = __getOwnPropDesc(from, key3)) || desc.enumerable });
30
30
  }
31
31
  return to;
32
32
  };
@@ -48,7 +48,7 @@ __export(index_exports, {
48
48
  inject: () => inject
49
49
  });
50
50
  module.exports = __toCommonJS(index_exports);
51
- var React29 = __toESM(require("react"), 1);
51
+ var React34 = __toESM(require("react"), 1);
52
52
 
53
53
  // src/client/styles.css
54
54
  var styles_default = `/**
@@ -319,6 +319,41 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
319
319
  .dhl-chart-tools button:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
320
320
  .dhl-chart-tools button.on { color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent); }
321
321
  .dhl-chart-actions { display: flex; align-items: center; gap: 3px; }
322
+ .dhl-favorite-tools { display: flex; align-items: center; gap: 3px; padding: 4px 10px; border-bottom: 1px solid var(--dsw-alias-border-l1); flex-wrap: wrap; }
323
+ .dhl-favorite-tools button { display: grid; place-items: center; width: 28px; height: 28px; padding: 4px; border: 0; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary); cursor: pointer; }
324
+ .dhl-favorite-tools button:hover, .dhl-favorite-tools button.on { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-brand-primary); }
325
+ .dhl-favorite-tools small { color: var(--dsw-alias-label-tertiary); font-size: 10px; margin-left: 6px; }
326
+ .dhl-chart-navigation { position: relative; display: flex; flex-wrap: wrap; gap: 4px; align-items: center; padding: 5px 10px; border-top: 1px solid var(--dsw-alias-border-l1); font-size: 11px; color: var(--dsw-alias-label-secondary); }
327
+ .dhl-chart-navigation button, .dhl-chart-navigation summary { display: inline-flex; gap: 5px; align-items: center; justify-content: center; padding: 5px 7px; border: 0; border-radius: 5px; background: transparent; color: inherit; font: inherit; cursor: pointer; list-style: none; }
328
+ .dhl-chart-navigation summary::-webkit-details-marker { display: none; }
329
+ .dhl-chart-navigation button:hover, .dhl-chart-navigation summary:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
330
+ .dhl-chart-navigation button:disabled { opacity: .4; cursor: default; }
331
+ .dhl-chart-ranges { display: flex; gap: 2px; }
332
+ .dhl-chart-footer-spacer { flex: 1; }
333
+ .dhl-chart-footer-menu { position: relative; }
334
+ .dhl-chart-footer-menu > div, .dhl-chart-footer-menu > form { position: absolute; z-index: 15; bottom: calc(100% + 8px); right: 0; min-width: 225px; padding: 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); box-shadow: 0 6px 22px rgba(0,0,0,.15); }
335
+ .dhl-chart-footer-menu > .dhl-chart-date-form { left: 0; right: auto; width: 270px; display: grid; gap: 8px; }
336
+ .dhl-chart-date-form label { font-weight: 600; color: var(--dsw-alias-label-primary); }
337
+ .dhl-chart-date-form small { line-height: 1.5; }
338
+ .dhl-chart-date-form input { width: 100%; min-width: 0; border-radius: 7px; color-scheme: light dark; }
339
+ .dhl-chart-date-form button { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-brand-primary); }
340
+ .dhl-chart-export-menu { display: grid; gap: 4px; }
341
+ .dhl-chart-export-menu button { justify-content: flex-start; white-space: nowrap; }
342
+ .dhl-chart-shortcuts { width: min(340px, 85vw); }
343
+ .dhl-chart-shortcuts strong { display: block; margin-bottom: 8px; color: var(--dsw-alias-label-primary); }
344
+ .dhl-chart-shortcuts > div { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 5px 0; }
345
+ .dhl-chart-shortcuts kbd { font: inherit; color: var(--dsw-alias-label-primary); }
346
+ .dhl-draw-tool-row { display: flex; align-items: center; }
347
+ .dhl-chart-tools .dhl-draw-popover .dhl-draw-tool-row > button:first-child { flex: 1; min-width: 0; }
348
+ .dhl-chart-tools .dhl-draw-popover .dhl-draw-tool-row > .dhl-draw-favorite { flex: 0 0 30px; width: 30px; justify-content: center; padding: 7px 5px; }
349
+ .dhl-draw-favorite[aria-pressed="true"] { color: var(--dsw-alias-brand-primary); }
350
+ .dhl-coordinate-editor { display: grid; gap: 10px; }
351
+ .dhl-coordinate-editor label { display: grid; gap: 5px; font-size: 12px; }
352
+ .dhl-coordinate-editor input { min-width: 0; width: 100%; border-radius: 7px; }
353
+ .dhl-coordinate-editor small { font-size: 11px; color: var(--dsw-alias-label-secondary); }
354
+ .dhl-coordinate-anchors { max-height: 320px; overflow: auto; display: grid; gap: 8px; }
355
+ .dhl-coordinate-anchors fieldset { display: grid; gap: 8px; margin: 0; padding: 10px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 7px; }
356
+ .dhl-coordinate-anchors legend { color: var(--dsw-alias-label-secondary); font-size: 11px; }
322
357
  .dhl-chart-actions button:disabled { opacity: .4; cursor: default; }
323
358
  .dhl-layer-menu { position: relative; font-size: 11px; color: var(--dsw-alias-label-secondary); }
324
359
  .dhl-layer-menu summary { cursor: pointer; padding: 4px 6px; white-space: nowrap; }
@@ -445,10 +480,6 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
445
480
  .dhl-chart-drawing-preview { stroke: currentColor; stroke-width: 1.5; stroke-dasharray: 5 4; opacity: .9; }
446
481
  .dhl-chart-ohlc { position: absolute; z-index: 1; top: 6px; left: 10px; display: flex; flex-wrap: wrap; gap: 4px 10px; pointer-events: none; color: var(--dsw-alias-label-secondary); font-size: 10px; font-variant-numeric: tabular-nums; }
447
482
  .dhl-chart-ohlc b { font-weight: 550; }
448
- .dhl-chart-annotations { position: absolute; z-index: 1; right: 56px; top: 25px; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; max-width: 45%; pointer-events: none; font-size: 10px; }
449
- .dhl-chart-annotations span { padding: 2px 5px; border-radius: 3px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
450
- .dhl-chart-annotations .role-support { color: var(--dsw-alias-state-success-primary); }
451
- .dhl-chart-annotations .role-resistance { color: var(--dsw-alias-state-error-primary); }
452
483
  .dhl-chart-draw-hint { padding: 6px 12px; color: var(--dsw-alias-brand-primary); font-size: 11px; border-top: 1px solid var(--dsw-alias-border-l1); }
453
484
  .dhl-chart-click-hint { padding: 5px 8px 0; color: var(--dsw-alias-label-secondary); font-size: 10.5px; line-height: 1.4; }
454
485
  .dhl-chart-mark-list { display: flex; flex-wrap: wrap; gap: 5px; padding: 5px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }
@@ -821,6 +852,73 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
821
852
  .dhl-sticker-custom button { height: 32px; padding: 0 14px; border: 0; border-radius: 8px; background: var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary)); color: var(--dsw-alias-label-primary-foreground, #fff); font: inherit; font-size: 13px; cursor: pointer; }
822
853
  .dhl-sticker-custom button:disabled { opacity: .4; cursor: default; }
823
854
  .dhl-icon-grid { grid-template-columns: repeat(6, 1fr); }
855
+
856
+ /* Expanded charts reserve room for the quick tools, navigation and drawing list. */
857
+ .dhl-chart-panel-expanded .dhl-chart { display: flex; flex-direction: column; overflow: auto; }
858
+ .dhl-chart-panel-expanded .dhl-chartbox { height: auto; flex: 1 0 240px; }
859
+ .dhl-chart-panel-expanded .dhl-chart-tools { flex-wrap: wrap; flex-shrink: 0; }
860
+ .dhl-chart-panel-expanded .dhl-intervals, .dhl-chart-panel-expanded .dhl-chart-actions { flex-wrap: wrap; }
861
+ .dhl-chart-panel-expanded .dhl-favorite-tools, .dhl-chart-panel-expanded .dhl-chart-click-hint, .dhl-chart-panel-expanded .dhl-chart-navigation { flex-shrink: 0; }
862
+ .dhl-chart-panel-expanded .dhl-drawing-list { max-height: 130px; flex-shrink: 0; overflow: auto; }
863
+ .dhl-draw-popover .dhl-draw-favorite[aria-pressed="true"] svg { color: var(--dsw-alias-brand-primary); }
864
+
865
+ /* Research and evidence share the existing desk tokens. */
866
+ .dhl-research-controls { display:flex; align-items:center; flex-wrap:wrap; gap:12px; padding:8px 12px; border-bottom:1px solid var(--dsw-alias-border-l1); font-size:11px; }
867
+ .dhl-research-controls label { display:flex; gap:5px; align-items:center; cursor:pointer; }
868
+ .dhl-research-grid { display:grid; grid-template-columns:minmax(0,1fr); }
869
+ .dhl-research-grid.dual, .dhl-research-grid.quad { grid-template-columns:repeat(2,minmax(0,1fr)); gap:1px; background:var(--dsw-alias-border-l1); }
870
+ .dhl-research-pane { min-width:0; background:var(--dsw-alias-bg-layer-1); }
871
+ .dhl-research-pane > header { display:flex; gap:8px; align-items:center; min-height:36px; padding:4px 10px; font-size:11px; }
872
+ .dhl-research-pane > header small { margin-left:auto; color:var(--dsw-alias-label-secondary); }
873
+ .dhl-chart-panel-expanded .dhl-research-grid.single .dhl-primary-pane { display:flex; flex-direction:column; min-height:0; flex:1; }
874
+ .dhl-data-window { padding:10px 12px; border-top:1px solid var(--dsw-alias-border-l1); font-size:11px; }
875
+ .dhl-data-window header { display:flex; flex-wrap:wrap; gap:10px; align-items:center; color:var(--dsw-alias-label-secondary); }
876
+ .dhl-data-window header strong { color:var(--dsw-alias-label-primary); }
877
+ .dhl-data-window dl { display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); margin:8px 0; gap:5px 20px; }
878
+ .dhl-data-window dl > div { display:flex; justify-content:space-between; gap:8px; }
879
+ .dhl-data-window dt { color:var(--dsw-alias-label-secondary); }
880
+ .dhl-data-window dd { margin:0; font-variant-numeric:tabular-nums; }
881
+ .dhl-data-window small { color:var(--dsw-alias-label-secondary); }
882
+ .dhl-study-settings { display:flex; flex-direction:column; gap:16px; max-height:65vh; overflow:auto; padding:2px; }
883
+ .dhl-study-template { display:flex; flex-direction:column; gap:8px; }
884
+ .dhl-study-template small { color:var(--dsw-alias-label-secondary); }
885
+ .dhl-study-template-actions { display:flex; flex-wrap:wrap; gap:8px; }
886
+ .dhl-study-parameter-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }
887
+ .dhl-study-parameter-grid fieldset { border:1px solid var(--dsw-alias-border-l1); border-radius:8px; padding:12px; min-width:0; }
888
+ .dhl-study-parameter-grid legend { font-size:12px; font-weight:600; }
889
+ .dhl-study-parameter-grid label { display:flex; align-items:center; justify-content:space-between; gap:10px; margin:6px 0; font-size:12px; }
890
+ .dhl-study-parameter-grid input { width:90px; }
891
+ .dhl-indicator-settings-trigger { gap:8px; border-top:1px solid var(--dsw-alias-border-l1)!important; }
892
+ .dhl-review { font-size:12px; }
893
+ .dhl-review .dhl-history-controls { flex-wrap:wrap; }
894
+ .dhl-review-summary { display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:12px; padding:10px 0; }
895
+ .dhl-review-summary > div { display:flex; flex-direction:column; gap:5px; }
896
+ .dhl-review-summary span, .dhl-evidence-note { color:var(--dsw-alias-label-secondary); font-size:11px; line-height:1.65; }
897
+ .dhl-review-summary strong { font-size:15px; font-variant-numeric:tabular-nums; }
898
+ .dhl-review details { border-top:1px solid var(--dsw-alias-border-l1); padding:9px 0; }
899
+ .dhl-review summary { cursor:pointer; line-height:1.8; }
900
+ .dhl-review-fill summary { word-spacing:3px; }
901
+ .dhl-review-fill summary time { color:var(--dsw-alias-label-secondary); }
902
+ .dhl-evidence-detail { display:flex; flex-direction:column; align-items:flex-start; gap:8px; padding:10px 14px; overflow-wrap:anywhere; }
903
+ .dhl-review-order { display:grid; grid-template-columns:1fr 1fr 1.3fr; gap:10px; padding:6px 0; }
904
+ @media(max-width:1000px) { .dhl-research-grid.dual,.dhl-research-grid.quad { grid-template-columns:minmax(0,1fr); } }
905
+ @media(max-width:520px) { .dhl-study-parameter-grid { grid-template-columns:minmax(0,1fr); } }
906
+
907
+ .dhl-chart-panel-expanded .dhl-research-grid.single { flex:1; min-height:0; }
908
+
909
+ /* Fixed range volume profile: up/down volume per price row, value area brighter, POC in the alert red. */
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; }
912
+ .dhl-vprofile-up { fill: color-mix(in srgb, #4c82d8 62%, transparent); }
913
+ .dhl-vprofile-down { fill: color-mix(in srgb, #e2a943 62%, transparent); }
914
+ .dhl-vprofile-out .dhl-vprofile-up { fill: color-mix(in srgb, #4c82d8 26%, transparent); }
915
+ .dhl-vprofile-out .dhl-vprofile-down { fill: color-mix(in srgb, #e2a943 26%, transparent); }
916
+ .dhl-vprofile-poc { stroke: #ef5f6c; stroke-width: 1.5; }
917
+ .dhl-drawing .dhl-vprofile-warn { fill: var(--dsw-alias-state-warning-primary, #e2a943); }
918
+
919
+ /* Magnet feedback: a ring on the O/H/L/C value the anchor will snap to. */
920
+ .dhl-snap-ring { fill: none; stroke: var(--dsw-alias-brand-primary, #4c82d8); stroke-width: 1.5; pointer-events: none; }
921
+ .dhl-draw-toolbar .dhl-select > select { height: 26px; font-size: 11px; }
824
922
  `;
825
923
 
826
924
  // src/client/locale.ts
@@ -1195,8 +1293,8 @@ function getContext() {
1195
1293
  function setLocaleBinder(binder) {
1196
1294
  localeT = binder;
1197
1295
  }
1198
- function t(key2, fallback) {
1199
- return localeT(key2, fallback);
1296
+ function t(key3, fallback) {
1297
+ return localeT(key3, fallback);
1200
1298
  }
1201
1299
  function tr(zh, en) {
1202
1300
  return ctxRef?.get?.("locale")?.getLocale?.()?.active === "en" ? en : zh;
@@ -1211,12 +1309,87 @@ async function hyperCall(method, request = null) {
1211
1309
  if (envelope && typeof envelope === "object" && envelope.ok === true) return envelope.value;
1212
1310
  throw new Error(t("error.badResponse", "\u8FDC\u7AEF\u54CD\u5E94\u683C\u5F0F\u9519\u8BEF"));
1213
1311
  }
1312
+ try {
1313
+ window.__dhlCall = hyperCall;
1314
+ } catch {
1315
+ }
1214
1316
 
1215
1317
  // src/client/app.tsx
1216
- var React27 = __toESM(require("react"), 1);
1217
- var import_dsh_client_ui_primitives7 = require("@deepseek-ai/dsh-client-ui-primitives");
1318
+ var React32 = __toESM(require("react"), 1);
1319
+ var import_dsh_client_ui_primitives10 = require("@deepseek-ai/dsh-client-ui-primitives");
1218
1320
 
1219
- // src/client/chart-indicators.ts
1321
+ // lib/studies.js
1322
+ var DEFAULT_INDICATOR_SETTINGS = {
1323
+ rsi: { period: 14, lower: 30, upper: 70 },
1324
+ macd: { fast: 12, slow: 26, signal: 9 },
1325
+ bollinger: { period: 20, multiplier: 2 },
1326
+ stochastic: { period: 14, smoothK: 3, smoothD: 3, lower: 20, upper: 80 },
1327
+ adx: { period: 14, smoothing: 14 }
1328
+ };
1329
+ function normalizeIndicatorSettings(input = {}) {
1330
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !Object.hasOwn(DEFAULT_INDICATOR_SETTINGS, key3))) throw Error("\u6307\u6807\u53C2\u6570\u65E0\u6548");
1331
+ const result = {};
1332
+ for (const [section, defaults] of Object.entries(DEFAULT_INDICATOR_SETTINGS)) {
1333
+ const supplied = input[section] ?? {};
1334
+ if (typeof supplied !== "object" || Array.isArray(supplied) || Object.keys(supplied).some((key3) => !Object.hasOwn(defaults, key3))) throw Error("\u6307\u6807\u53C2\u6570\u5B57\u6BB5\u65E0\u6548");
1335
+ const values = { ...defaults, ...supplied };
1336
+ for (const [key3, value] of Object.entries(values)) {
1337
+ if (typeof value !== "number" || !Number.isFinite(value)) throw Error(`${section}.${key3} \u5FC5\u987B\u4E3A\u6570\u503C`);
1338
+ if (key3 === "multiplier") {
1339
+ if (value <= 0 || value > 10) throw Error("\u5E03\u6797\u5E26\u500D\u6570\u5FC5\u987B\u5927\u4E8E 0 \u4E14\u4E0D\u8D85\u8FC7 10");
1340
+ } else if (key3 === "lower" || key3 === "upper") {
1341
+ if (value < 0 || value > 100) throw Error("\u6307\u6807\u9608\u503C\u5FC5\u987B\u4E3A 0\u2013100");
1342
+ } else if (!Number.isInteger(value) || value < 1 || value > 500) throw Error("\u6307\u6807\u5468\u671F\u5FC5\u987B\u4E3A 1\u2013500 \u7684\u6574\u6570");
1343
+ }
1344
+ if ("lower" in values && values.lower >= values.upper) throw Error("\u6307\u6807\u4E0B\u9608\u503C\u5FC5\u987B\u5C0F\u4E8E\u4E0A\u9608\u503C");
1345
+ result[section] = values;
1346
+ }
1347
+ if (result.macd.fast >= result.macd.slow) throw Error("MACD \u5FEB\u7EBF\u5468\u671F\u5FC5\u987B\u5C0F\u4E8E\u6162\u7EBF\u5468\u671F");
1348
+ return result;
1349
+ }
1350
+ function nullableSma(values, period) {
1351
+ return values.map((_, i) => {
1352
+ if (i < period - 1) return null;
1353
+ const window2 = values.slice(i - period + 1, i + 1);
1354
+ return window2.some((value) => value == null) ? null : window2.reduce((sum, value) => sum + value, 0) / period;
1355
+ });
1356
+ }
1357
+ function rma(values, period) {
1358
+ let prior = null, seed = [];
1359
+ return values.map((value) => {
1360
+ if (value == null) return null;
1361
+ if (prior == null) {
1362
+ seed.push(value);
1363
+ if (seed.length < period) return null;
1364
+ prior = seed.reduce((sum, item) => sum + item, 0) / period;
1365
+ } else prior = (prior * (period - 1) + value) / period;
1366
+ return prior;
1367
+ });
1368
+ }
1369
+ function studyDefinitions(input) {
1370
+ const p = normalizeIndicatorSettings(input);
1371
+ return [
1372
+ ["sma20", "sma20", "SMA20"],
1373
+ ["sma60", "sma60", "MA60"],
1374
+ ["ema20", "ema20", "EMA20"],
1375
+ ["ema50", "ema50", "EMA50"],
1376
+ ["bollinger20", "basis", `BOLL ${p.bollinger.period} \xD7 ${p.bollinger.multiplier}`],
1377
+ ["bollinger20", "upper", "BOLL upper"],
1378
+ ["bollinger20", "lower", "BOLL lower"],
1379
+ ["rsi14", "rsi14", `RSI${p.rsi.period}`],
1380
+ ["macd", "macd", p.macd.fast === 12 && p.macd.slow === 26 && p.macd.signal === 9 ? "MACD" : `MACD ${p.macd.fast}/${p.macd.slow}/${p.macd.signal}`],
1381
+ ["macd", "signal", "Signal"],
1382
+ ["macd", "histogram", "Hist"],
1383
+ ["atr14", "atr14", "ATR14"],
1384
+ ["vwap", "vwap", "VWAP (UTC HLC3)"],
1385
+ ["stochastic", "stochK", `Stoch %K ${p.stochastic.period}/${p.stochastic.smoothK}`],
1386
+ ["stochastic", "stochD", `Stoch %D ${p.stochastic.smoothD}`],
1387
+ ["adx", "adx", `ADX ${p.adx.period}/${p.adx.smoothing}`],
1388
+ ["adx", "plusDI", "+DI"],
1389
+ ["adx", "minusDI", "-DI"],
1390
+ ["obv", "obv", "OBV"]
1391
+ ];
1392
+ }
1220
1393
  function points(rows, values) {
1221
1394
  return values.flatMap((value, index) => value == null || !Number.isFinite(value) ? [] : [{ time: Math.floor(rows[index].t / 1e3), value }]);
1222
1395
  }
@@ -1245,16 +1418,17 @@ function ema(values, period) {
1245
1418
  }
1246
1419
  return result;
1247
1420
  }
1248
- function chartIndicators(candles) {
1421
+ function computeStudies(candles, input) {
1422
+ const settings = normalizeIndicatorSettings(input);
1249
1423
  const rows = candles.filter((row) => Number.isFinite(row.c) && row.c > 0 && Number.isFinite(row.t));
1250
1424
  const closes = rows.map((row) => row.c);
1251
- const mean = sma(closes, 20);
1252
- const ema12 = ema(closes, 12);
1253
- const ema26 = ema(closes, 26);
1425
+ const mean = sma(closes, settings.bollinger.period);
1426
+ const ema12 = ema(closes, settings.macd.fast);
1427
+ const ema26 = ema(closes, settings.macd.slow);
1254
1428
  const macd = ema12.map((value, index) => value == null || ema26[index] == null ? null : value - ema26[index]);
1255
- const signal = ema(macd, 9);
1429
+ const signal = ema(macd, settings.macd.signal);
1256
1430
  const deviation = mean.map((value, index) => value == null ? null : Math.sqrt(
1257
- closes.slice(index - 19, index + 1).reduce((total, close) => total + (close - value) ** 2, 0) / 20
1431
+ closes.slice(index - settings.bollinger.period + 1, index + 1).reduce((total, close) => total + (close - value) ** 2, 0) / settings.bollinger.period
1258
1432
  ));
1259
1433
  const rsi = Array(rows.length).fill(null);
1260
1434
  let gain = 0;
@@ -1263,34 +1437,98 @@ function chartIndicators(candles) {
1263
1437
  const delta = closes[i] - closes[i - 1];
1264
1438
  const up = Math.max(delta, 0);
1265
1439
  const down = Math.max(-delta, 0);
1266
- if (i <= 14) {
1440
+ if (i <= settings.rsi.period) {
1267
1441
  gain += up;
1268
1442
  loss += down;
1269
1443
  } else {
1270
- gain = gain * 13 / 14 + up;
1271
- loss = loss * 13 / 14 + down;
1444
+ gain = (gain * (settings.rsi.period - 1) + up) / settings.rsi.period;
1445
+ loss = (loss * (settings.rsi.period - 1) + down) / settings.rsi.period;
1272
1446
  }
1273
- if (i === 14) {
1274
- gain /= 14;
1275
- loss /= 14;
1447
+ if (i === settings.rsi.period) {
1448
+ gain /= settings.rsi.period;
1449
+ loss /= settings.rsi.period;
1276
1450
  }
1277
- if (i >= 14) rsi[i] = loss === 0 ? gain === 0 ? 50 : 100 : 100 - 100 / (1 + gain / loss);
1451
+ if (i >= settings.rsi.period) rsi[i] = loss === 0 ? gain === 0 ? 50 : 100 : 100 - 100 / (1 + gain / loss);
1278
1452
  }
1279
1453
  const point = (values) => points(rows, values);
1454
+ const atr = Array(rows.length).fill(null);
1455
+ let rangeSum = 0;
1456
+ for (let i = 1; i < rows.length; i += 1) {
1457
+ const range = Math.max(rows[i].h - rows[i].l, Math.abs(rows[i].h - rows[i - 1].c), Math.abs(rows[i].l - rows[i - 1].c));
1458
+ if (i <= 14) rangeSum += range;
1459
+ if (i === 14) atr[i] = rangeSum / 14;
1460
+ if (i > 14) atr[i] = (atr[i - 1] * 13 + range) / 14;
1461
+ }
1462
+ let day = -1, weighted = 0, volume = 0, complete = false;
1463
+ const vwap = rows.map((row) => {
1464
+ const nextDay = Math.floor(row.t / 864e5);
1465
+ if (nextDay !== day) {
1466
+ complete = day >= 0 || row.t % 864e5 === 0;
1467
+ day = nextDay;
1468
+ weighted = 0;
1469
+ volume = 0;
1470
+ }
1471
+ if (!Number.isFinite(row.v) || row.v < 0) return null;
1472
+ weighted += (row.h + row.l + row.c) / 3 * row.v;
1473
+ volume += row.v;
1474
+ return complete && volume > 0 ? weighted / volume : null;
1475
+ });
1476
+ const rawK = rows.map((row, index) => {
1477
+ if (index < settings.stochastic.period - 1) return null;
1478
+ const window2 = rows.slice(index - settings.stochastic.period + 1, index + 1);
1479
+ const low = Math.min(...window2.map((bar) => bar.l)), high = Math.max(...window2.map((bar) => bar.h));
1480
+ return high === low ? null : 100 * (row.c - low) / (high - low);
1481
+ });
1482
+ const stochK = nullableSma(rawK, settings.stochastic.smoothK);
1483
+ const stochD = nullableSma(stochK, settings.stochastic.smoothD);
1484
+ const tr2 = rows.map((row, i) => i === 0 ? null : Math.max(row.h - row.l, Math.abs(row.h - rows[i - 1].c), Math.abs(row.l - rows[i - 1].c)));
1485
+ const plus = rows.map((row, i) => {
1486
+ if (!i) return null;
1487
+ const up = row.h - rows[i - 1].h, down = rows[i - 1].l - row.l;
1488
+ return up > down && up > 0 ? up : 0;
1489
+ });
1490
+ const minus = rows.map((row, i) => {
1491
+ if (!i) return null;
1492
+ const up = row.h - rows[i - 1].h, down = rows[i - 1].l - row.l;
1493
+ return down > up && down > 0 ? down : 0;
1494
+ });
1495
+ const atrDm = rma(tr2, settings.adx.period), plusRma = rma(plus, settings.adx.period), minusRma = rma(minus, settings.adx.period);
1496
+ const di = (values) => values.map((value, i) => value == null || atrDm[i] == null ? null : atrDm[i] === 0 ? 0 : 100 * value / atrDm[i]);
1497
+ const plusDI = di(plusRma), minusDI = di(minusRma);
1498
+ const dx = plusDI.map((value, i) => value == null || minusDI[i] == null ? null : value + minusDI[i] === 0 ? 0 : 100 * Math.abs(value - minusDI[i]) / (value + minusDI[i]));
1499
+ let balance = 0;
1500
+ const obv = rows.map((row, i) => {
1501
+ if (i) balance += Math.sign(row.c - rows[i - 1].c) * row.v;
1502
+ return balance;
1503
+ });
1280
1504
  return {
1281
- sma20: point(mean),
1505
+ stochK: point(stochK),
1506
+ stochD: point(stochD),
1507
+ adx: point(rma(dx, settings.adx.smoothing)),
1508
+ plusDI: point(plusDI),
1509
+ minusDI: point(minusDI),
1510
+ obv: point(obv),
1511
+ sma20: point(sma(closes, 20)),
1512
+ basis: point(mean),
1282
1513
  sma60: point(sma(closes, 60)),
1283
1514
  ema20: point(ema(closes, 20)),
1284
1515
  ema50: point(ema(closes, 50)),
1285
- upper: point(mean.map((value, index) => value == null ? null : value + 2 * deviation[index])),
1286
- lower: point(mean.map((value, index) => value == null ? null : value - 2 * deviation[index])),
1516
+ upper: point(mean.map((value, index) => value == null ? null : value + settings.bollinger.multiplier * deviation[index])),
1517
+ lower: point(mean.map((value, index) => value == null ? null : value - settings.bollinger.multiplier * deviation[index])),
1287
1518
  rsi14: point(rsi),
1288
1519
  macd: point(macd),
1289
1520
  signal: point(signal),
1290
- histogram: point(macd.map((value, index) => value == null || signal[index] == null ? null : value - signal[index]))
1521
+ histogram: point(macd.map((value, index) => value == null || signal[index] == null ? null : value - signal[index])),
1522
+ atr14: point(atr),
1523
+ vwap: point(vwap)
1291
1524
  };
1292
1525
  }
1293
1526
 
1527
+ // src/client/chart-indicators.ts
1528
+ function chartIndicators(candles, settings) {
1529
+ return computeStudies(candles, settings);
1530
+ }
1531
+
1294
1532
  // lib/moving-average.js
1295
1533
  var MA_KINDS = ["sma", "ema", "smma", "wma"];
1296
1534
  var MA_SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4"];
@@ -1388,28 +1626,16 @@ function maSeries(candles, line) {
1388
1626
  }
1389
1627
 
1390
1628
  // src/client/chart-reference.ts
1391
- var SERIES = [
1392
- ["sma20", "SMA20", "sma20"],
1393
- ["sma60", "MA60", "sma60"],
1394
- ["ema20", "EMA20", "ema20"],
1395
- ["ema50", "EMA50", "ema50"],
1396
- ["bollinger20", "BOLL\u4E0A\u8F68", "upper"],
1397
- ["bollinger20", "BOLL\u4E0B\u8F68", "lower"],
1398
- ["rsi14", "RSI14", "rsi14"],
1399
- ["macd", "MACD", "macd"],
1400
- ["macd", "\u4FE1\u53F7\u7EBF", "signal"],
1401
- ["macd", "MACD\u67F1", "histogram"]
1402
- ];
1403
- function selectChartCandle(candles, time, active, hit, maLines = []) {
1629
+ function selectChartCandle(candles, time, active, hit, maLines = [], settings) {
1404
1630
  const rows = candles;
1405
1631
  const index = rows.findIndex((candle2) => Math.floor(candle2.t / 1e3) === Math.floor(time / 1e3));
1406
1632
  if (index < 0) return null;
1407
1633
  const candle = rows[index];
1408
- const studies = chartIndicators(rows);
1634
+ const studies = chartIndicators(rows, settings);
1409
1635
  const epoch = Math.floor(candle.t / 1e3);
1410
- const values = SERIES.flatMap(([id, label, key2]) => {
1636
+ const values = studyDefinitions(settings).flatMap(([id, key3, label]) => {
1411
1637
  if (!active.has(id)) return [];
1412
- const point = studies[key2].find((item) => item.time === epoch);
1638
+ const point = studies[key3].find((item) => item.time === epoch);
1413
1639
  return point ? [{ label, value: point.value }] : [];
1414
1640
  });
1415
1641
  for (const line of maLines) {
@@ -1419,7 +1645,7 @@ function selectChartCandle(candles, time, active, hit, maLines = []) {
1419
1645
  }
1420
1646
  const previous = rows.slice(Math.max(0, index - 20), index).map((row) => row.v).filter((value) => Number.isFinite(value) && value >= 0);
1421
1647
  const priorVolumeMean20 = previous.length === 20 ? previous.reduce((sum, value) => sum + value, 0) / 20 : null;
1422
- return { candle, hit, values, priorVolumeMean20 };
1648
+ return { candle, hit, values, priorVolumeMean20, settings };
1423
1649
  }
1424
1650
  function formatChartReference(selection, coin, interval, english = false) {
1425
1651
  const { candle, hit, values, priorVolumeMean20 } = selection;
@@ -1434,10 +1660,15 @@ function formatChartReference(selection, coin, interval, english = false) {
1434
1660
  ema50: "EMA50",
1435
1661
  bollingerUpper: english ? "BOLL upper" : "BOLL\u4E0A\u8F68",
1436
1662
  bollingerLower: english ? "BOLL lower" : "BOLL\u4E0B\u8F68",
1437
- rsi14: "RSI14",
1663
+ rsi14: `RSI${selection.settings?.rsi.period ?? 14}`,
1438
1664
  macd: "MACD",
1439
1665
  macdSignal: english ? "MACD signal" : "\u4FE1\u53F7\u7EBF",
1440
- macdHistogram: english ? "MACD histogram" : "MACD\u67F1"
1666
+ macdHistogram: english ? "MACD histogram" : "MACD\u67F1",
1667
+ atr14: "ATR14",
1668
+ vwap: "VWAP (UTC HLC3)",
1669
+ stochastic: "Stochastic",
1670
+ adx: "ADX / DMI",
1671
+ obv: "OBV"
1441
1672
  };
1442
1673
  const lines = [
1443
1674
  english ? `[Chart point] ${coin} ${interval} candle opened ${time}` : `\u3010\u56FE\u8868\u70B9\u4F4D\u3011${coin} ${interval} K \u7EBF\u5F00\u76D8 ${time}`,
@@ -1447,6 +1678,8 @@ function formatChartReference(selection, coin, interval, english = false) {
1447
1678
  if (priorVolumeMean20 != null && priorVolumeMean20 > 0) {
1448
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`);
1449
1680
  }
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");
1450
1683
  if (values.length) lines.push(`${english ? "Displayed studies" : "\u5DF2\u663E\u793A\u6307\u6807"}\uFF1A${values.map(({ label, value }) => `${label}=${computed(value)}`).join("\uFF0C")}`);
1451
1684
  if (hit && hit !== "volume") lines.push(`${english ? "Selected study" : "\u70B9\u51FB\u6307\u6807"}\uFF1A${hitLabel[hit] ?? hit}`);
1452
1685
  return lines.join("\n");
@@ -1629,6 +1862,7 @@ function applyLocalCoin(coin) {
1629
1862
  void loadCandles();
1630
1863
  void loadSnapshot();
1631
1864
  void loadMarketDetail(coin);
1865
+ void loadChartMarks();
1632
1866
  }
1633
1867
  function liveCandle(value) {
1634
1868
  if (!value || typeof value !== "object") return null;
@@ -1715,9 +1949,9 @@ async function loadMarketDetail(coin) {
1715
1949
  async function loadSnapshot() {
1716
1950
  const { activeCoin: coin, interval, bookGrouping } = store.get();
1717
1951
  const epoch = networkEpoch;
1718
- const key2 = `${epoch}:${coin}:${interval}:${bookGrouping}`;
1719
- if (pendingSnapshots.has(key2)) return;
1720
- pendingSnapshots.add(key2);
1952
+ const key3 = `${epoch}:${coin}:${interval}:${bookGrouping}`;
1953
+ if (pendingSnapshots.has(key3)) return;
1954
+ pendingSnapshots.add(key3);
1721
1955
  try {
1722
1956
  const snapshot = await hyperCall("snapshot", { coin, interval, bookGrouping });
1723
1957
  if (epoch !== networkEpoch || store.get().activeCoin !== coin || store.get().interval !== interval || store.get().bookGrouping !== bookGrouping) return;
@@ -1731,22 +1965,22 @@ async function loadSnapshot() {
1731
1965
  } catch (e) {
1732
1966
  if (epoch === networkEpoch && store.get().activeCoin === coin && store.get().interval === interval && store.get().bookGrouping === bookGrouping) store.patch({ snapshotError: String(e?.message ?? e) });
1733
1967
  } finally {
1734
- pendingSnapshots.delete(key2);
1968
+ pendingSnapshots.delete(key3);
1735
1969
  }
1736
1970
  }
1737
1971
  async function loadOlderCandles() {
1738
1972
  const { activeCoin: coin, interval, candles, candlesState, candlesOlder } = store.get();
1739
1973
  const first = candles[0];
1740
1974
  if (olderPending || !first || candlesState !== "ready" || candlesOlder === "done" || candles.length >= MAX_CANDLES) return;
1741
- const key2 = `${coin}:${interval}`;
1742
- if (candlesFor !== key2) return;
1975
+ const key3 = `${coin}:${interval}`;
1976
+ if (candlesFor !== key3) return;
1743
1977
  olderPending = true;
1744
1978
  const epoch = networkEpoch;
1745
1979
  store.patch({ candlesOlder: "loading" });
1746
1980
  try {
1747
1981
  const endMs = first.t - 1;
1748
1982
  const res = await hyperCall("candles", { coin, interval, startMs: endMs - INTERVAL_MS[interval] * OLDER_PAGE_BARS, endMs });
1749
- if (epoch !== networkEpoch || candlesFor !== key2) return;
1983
+ if (epoch !== networkEpoch || candlesFor !== key3) return;
1750
1984
  const current2 = store.get().candles;
1751
1985
  const head = current2[0]?.t ?? Infinity;
1752
1986
  const older = (res.candles ?? []).filter((row) => row.t < head);
@@ -1754,7 +1988,7 @@ async function loadOlderCandles() {
1754
1988
  const next = older.slice(-room);
1755
1989
  store.patch({ candles: [...next, ...current2], candlesOlder: next.length === 0 || next.length === room ? "done" : "idle" });
1756
1990
  } catch {
1757
- if (epoch === networkEpoch && candlesFor === key2) store.patch({ candlesOlder: "error" });
1991
+ if (epoch === networkEpoch && candlesFor === key3) store.patch({ candlesOlder: "error" });
1758
1992
  } finally {
1759
1993
  olderPending = false;
1760
1994
  }
@@ -1767,13 +2001,13 @@ async function loadCandles() {
1767
2001
  const res = await hyperCall("candles", { coin, interval });
1768
2002
  if (epoch === networkEpoch && store.get().activeCoin === coin && store.get().interval === interval) {
1769
2003
  const fetched = res.candles ?? [];
1770
- const key2 = `${coin}:${interval}`;
2004
+ const key3 = `${coin}:${interval}`;
1771
2005
  const prev = store.get().candles;
1772
- const same = candlesFor === key2;
2006
+ const same = candlesFor === key3;
1773
2007
  const older = same && fetched.length ? prev.filter((row) => row.t < fetched[0].t) : [];
1774
2008
  const rows = [...older, ...fetched];
1775
2009
  const lastLive = same ? prev.at(-1) : void 0;
1776
- candlesFor = key2;
2010
+ candlesFor = key3;
1777
2011
  store.patch({
1778
2012
  candles: lastLive && lastLive.t >= (rows.at(-1)?.t ?? 0) ? mergeCandle(rows.filter((row) => row.t < lastLive.t), lastLive) : rows,
1779
2013
  candlesState: "ready",
@@ -1786,17 +2020,17 @@ async function loadCandles() {
1786
2020
  }
1787
2021
  async function loadChartMarks() {
1788
2022
  const { activeSessionId: sessionId, activeCoin: coin } = store.get();
1789
- const key2 = `${sessionId ?? ""}:${coin}`;
2023
+ const key3 = `${sessionId ?? ""}:${coin}`;
1790
2024
  const version = ++chartMarksLoadVersion;
1791
2025
  if (!sessionId) {
1792
- store.patch({ chartMarks: [], chartMarksKey: key2 });
2026
+ store.patch({ chartMarks: [], chartMarksKey: key3 });
1793
2027
  return;
1794
2028
  }
1795
2029
  try {
1796
2030
  const res = await hyperCall("chartMarks", { sessionId, coin, allIntervals: true });
1797
2031
  const current2 = store.get();
1798
- if (version === chartMarksLoadVersion && `${current2.activeSessionId ?? ""}:${current2.activeCoin}` === key2) {
1799
- store.patch({ chartMarks: res.marks ?? [], chartMarksKey: key2 });
2032
+ if (version === chartMarksLoadVersion && `${current2.activeSessionId ?? ""}:${current2.activeCoin}` === key3) {
2033
+ store.patch({ chartMarks: res.marks ?? [], chartMarksKey: key3 });
1800
2034
  }
1801
2035
  } catch {
1802
2036
  }
@@ -1851,14 +2085,30 @@ async function loadSessions() {
1851
2085
  } catch {
1852
2086
  }
1853
2087
  }
2088
+ var maReloadVersion = 0;
1854
2089
  async function reloadMaLines(sessionId) {
2090
+ const version = ++maReloadVersion;
1855
2091
  try {
1856
2092
  const workspace = await hyperCall("sessionWorkspace", { sessionId });
1857
2093
  const state = store.get();
2094
+ if (version !== maReloadVersion) return;
1858
2095
  if (state.workspaceSessionId === sessionId && state.workspace) store.patch({ workspace: { ...state.workspace, maLines: workspace.maLines ?? [] } });
1859
2096
  } catch {
1860
2097
  }
1861
2098
  }
2099
+ var viewReloadVersion = 0;
2100
+ async function reloadChartView(sessionId) {
2101
+ const version = ++viewReloadVersion;
2102
+ try {
2103
+ const workspace = await hyperCall("sessionWorkspace", { sessionId });
2104
+ const state = store.get();
2105
+ if (version !== viewReloadVersion || state.workspaceSessionId !== sessionId || !state.workspace) return;
2106
+ const intervalChanged = workspace.interval !== state.interval;
2107
+ store.patch({ workspace: { ...state.workspace, interval: workspace.interval, indicators: workspace.indicators }, interval: workspace.interval });
2108
+ if (intervalChanged) void loadCandles();
2109
+ } catch {
2110
+ }
2111
+ }
1862
2112
  async function pollEvents() {
1863
2113
  if (eventsPending) return;
1864
2114
  eventsPending = true;
@@ -1879,6 +2129,7 @@ async function pollEvents() {
1879
2129
  if (ev.data.coin === store.get().activeCoin) void loadChartMarks();
1880
2130
  }
1881
2131
  if (ev.kind === "chart.ma" && ev.data?.sessionId === store.get().activeSessionId) void reloadMaLines(ev.data.sessionId);
2132
+ if (ev.kind === "chart.view" && ev.data?.sessionId === store.get().activeSessionId) void reloadChartView(ev.data.sessionId);
1882
2133
  if (ev.data?.clientId === focusClientId) continue;
1883
2134
  if (ev.kind === "desk.state" && ev.data?.coin && (!ev.data.sessionId || ev.data.sessionId === store.get().activeSessionId) && ev.data.coin !== store.get().activeCoin) {
1884
2135
  focusVersion += 1;
@@ -2212,6 +2463,12 @@ function SearchIcon() {
2212
2463
  }
2213
2464
  function UiIcon({ name, size: size3 = 16, filled = false }) {
2214
2465
  const paths2 = {
2466
+ cursor: /* @__PURE__ */ React4.createElement("path", { d: "m5 3 14 10-7 1-3 7z" }),
2467
+ calendar: /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement("rect", { x: "3", y: "5", width: "18", height: "16", rx: "2" }), /* @__PURE__ */ React4.createElement("path", { d: "M7 3v4m10-4v4M3 11h18M7 15h3m4 0h3M7 18h3" })),
2468
+ download: /* @__PURE__ */ React4.createElement("path", { d: "M12 3v12m-5-5 5 5 5-5M4 16v5h16v-5" }),
2469
+ camera: /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement("path", { d: "M3 7h4l2-3h6l2 3h4v14H3z" }), /* @__PURE__ */ React4.createElement("circle", { cx: "12", cy: "13", r: "4" })),
2470
+ help: /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement("circle", { cx: "12", cy: "12", r: "9" }), /* @__PURE__ */ React4.createElement("path", { d: "M9 9a3 3 0 0 1 6 0c0 2-3 2-3 5m0 3h.01" })),
2471
+ live: /* @__PURE__ */ React4.createElement("path", { d: "m4 4 10 8L4 20zm14 0v16" }),
2215
2472
  chevronDown: /* @__PURE__ */ React4.createElement("path", { d: "m6 9 6 6 6-6" }),
2216
2473
  chevronRight: /* @__PURE__ */ React4.createElement("path", { d: "m9 6 6 6-6 6" }),
2217
2474
  bell: /* @__PURE__ */ React4.createElement("path", { d: "M6 16V11a6 6 0 0 1 12 0v5l2 2H4zM10 20a2 2 0 0 0 4 0" }),
@@ -2342,7 +2599,7 @@ function SessionsColumn({ width, fill = false, onCollapse }) {
2342
2599
  }
2343
2600
 
2344
2601
  // src/client/components/desk-column.tsx
2345
- var React21 = __toESM(require("react"), 1);
2602
+ var React26 = __toESM(require("react"), 1);
2346
2603
 
2347
2604
  // src/client/components/select.tsx
2348
2605
  var React6 = __toESM(require("react"), 1);
@@ -2351,7 +2608,7 @@ function Select({ className, children, ...props }) {
2351
2608
  }
2352
2609
 
2353
2610
  // src/client/components/candle-chart.tsx
2354
- var React9 = __toESM(require("react"), 1);
2611
+ var React10 = __toESM(require("react"), 1);
2355
2612
 
2356
2613
  // node_modules/fancy-canvas/size.mjs
2357
2614
  function size(_a) {
@@ -3459,9 +3716,9 @@ var PriceAxisViewRenderer = class {
3459
3716
  const text = this._private__data._internal_text;
3460
3717
  const actualTextHeight = rendererOptions._internal_fontSize;
3461
3718
  const textMidCorrection = textWidthCache._internal_yMidCorrection(ctx, text);
3462
- const textWidth = Math.ceil(textWidthCache._internal_measureText(ctx, text));
3719
+ const textWidth2 = Math.ceil(textWidthCache._internal_measureText(ctx, text));
3463
3720
  const totalHeight = actualTextHeight + paddingTop + paddingBottom;
3464
- const totalWidth = rendererOptions._internal_borderSize + paddingInner + paddingOuter + textWidth + tickSize;
3721
+ const totalWidth = rendererOptions._internal_borderSize + paddingInner + paddingOuter + textWidth2 + tickSize;
3465
3722
  const tickHeightBitmap = Math.max(1, Math.floor(verticalPixelRatio));
3466
3723
  let totalHeightBitmap = Math.round(totalHeight * verticalPixelRatio);
3467
3724
  if (totalHeightBitmap % 2 !== tickHeightBitmap % 2) {
@@ -3645,15 +3902,15 @@ var TimeAxisViewRenderer = class {
3645
3902
  if (this._private__data === null || this._private__data._internal_visible === false || this._private__data._internal_text.length === 0) {
3646
3903
  return;
3647
3904
  }
3648
- const textWidth = target.useMediaCoordinateSpace(({ context: ctx }) => {
3905
+ const textWidth2 = target.useMediaCoordinateSpace(({ context: ctx }) => {
3649
3906
  ctx.font = rendererOptions._internal_font;
3650
3907
  return Math.round(rendererOptions._internal_widthCache._internal_measureText(ctx, ensureNotNull(this._private__data)._internal_text, optimizationReplacementRe));
3651
3908
  });
3652
- if (textWidth <= 0) {
3909
+ if (textWidth2 <= 0) {
3653
3910
  return;
3654
3911
  }
3655
3912
  const horzMargin = rendererOptions._internal_paddingHorizontal;
3656
- const labelWidth = textWidth + 2 * horzMargin;
3913
+ const labelWidth = textWidth2 + 2 * horzMargin;
3657
3914
  const labelWidthHalf = labelWidth / 2;
3658
3915
  const timeScaleWidth = this._private__data._internal_width;
3659
3916
  let coordinate = this._private__data._internal_coordinate;
@@ -6577,7 +6834,7 @@ var Magnet = class {
6577
6834
  }
6578
6835
  const firstPrice = ensure(series._internal_firstValue());
6579
6836
  const plotRowKeys = this._private__options.mode === 3 ? magnetOHLCPlotRowKeys : magnetPlotRowKeys;
6580
- return acc.concat(plotRowKeys.map((key2) => ps._internal_priceToCoordinate(bar._internal_value[key2], firstPrice._internal_value)));
6837
+ return acc.concat(plotRowKeys.map((key3) => ps._internal_priceToCoordinate(bar._internal_value[key3], firstPrice._internal_value)));
6581
6838
  }, []);
6582
6839
  if (candidates.length === 0) {
6583
6840
  return res;
@@ -14639,9 +14896,9 @@ function checkBarItem(type, barItem) {
14639
14896
  if (!isFulfilledData(barItem)) {
14640
14897
  return;
14641
14898
  }
14642
- ["open", "high", "low", "close"].forEach((key2) => {
14643
- assert(typeof barItem[key2] === "number", `${type} series item data value of ${key2} must be a number, got=${typeof barItem[key2]}, value=${barItem[key2]}`);
14644
- assert(isSafeValue(barItem[key2]), `${type} series item data value of ${key2} must be between ${MIN_SAFE_VALUE.toPrecision(16)} and ${MAX_SAFE_VALUE.toPrecision(16)}, got=${typeof barItem[key2]}, value=${barItem[key2]}`);
14899
+ ["open", "high", "low", "close"].forEach((key3) => {
14900
+ assert(typeof barItem[key3] === "number", `${type} series item data value of ${key3} must be a number, got=${typeof barItem[key3]}, value=${barItem[key3]}`);
14901
+ assert(isSafeValue(barItem[key3]), `${type} series item data value of ${key3} must be between ${MIN_SAFE_VALUE.toPrecision(16)} and ${MAX_SAFE_VALUE.toPrecision(16)}, got=${typeof barItem[key3]}, value=${barItem[key3]}`);
14645
14902
  });
14646
14903
  }
14647
14904
  function checkLineItem(type, lineItem) {
@@ -16786,9 +17043,9 @@ function drawText(ctx, text, x, y, horizontalPixelRatio, verticalPixelRatio) {
16786
17043
  ctx.fillText(text, x, y);
16787
17044
  ctx.restore();
16788
17045
  }
16789
- function hitTestText(textX, textY, textWidth, textHeight, x, y) {
17046
+ function hitTestText(textX, textY, textWidth2, textHeight, x, y) {
16790
17047
  const halfHeight = textHeight / 2;
16791
- return x >= textX && x <= textX + textWidth && y >= textY - halfHeight && y <= textY + halfHeight;
17048
+ return x >= textX && x <= textX + textWidth2 && y >= textY - halfHeight && y <= textY + halfHeight;
16792
17049
  }
16793
17050
  var SeriesMarkersRenderer = class {
16794
17051
  constructor() {
@@ -17356,6 +17613,8 @@ var DRAWING_POINTS = {
17356
17613
  gannfan: 2,
17357
17614
  pitchfork: 3,
17358
17615
  regression: 2,
17616
+ // Fixed range volume profile: the two points only set the time range.
17617
+ vprofile: 2,
17359
17618
  // Annotation and shapes. brush is freehand: 0 = variable (BRUSH_MIN..BRUSH_MAX points).
17360
17619
  arrow: 2,
17361
17620
  callout: 2,
@@ -17392,8 +17651,49 @@ function isSingleEmoji(value) {
17392
17651
  if (typeof value !== "string" || !value || value.length > 32 || !/\p{Extended_Pictographic}|\p{Regional_Indicator}/u.test(value)) return false;
17393
17652
  return [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(value)].length === 1;
17394
17653
  }
17654
+ function volumeProfile(bars, { rows = 48, valueArea = 0.7 } = {}) {
17655
+ 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
+ if (!valid.length) return null;
17657
+ const low = Math.min(...valid.map((bar) => bar.low)), high = Math.max(...valid.map((bar) => bar.high));
17658
+ const count = high > low ? rows : 1;
17659
+ const step = high > low ? (high - low) / count : 0;
17660
+ const out = Array.from({ length: count }, (_, index) => ({ low: low + index * step, high: count === 1 ? high : low + (index + 1) * step, up: 0, down: 0 }));
17661
+ for (const bar of valid) {
17662
+ const side2 = bar.close >= bar.open ? "up" : "down";
17663
+ if (count === 1) {
17664
+ out[0][side2] += bar.volume;
17665
+ continue;
17666
+ }
17667
+ if (bar.high === bar.low) {
17668
+ out[Math.min(count - 1, Math.floor((bar.low - low) / step))][side2] += bar.volume;
17669
+ continue;
17670
+ }
17671
+ const first = Math.max(0, Math.floor((bar.low - low) / step)), last = Math.min(count - 1, Math.floor((bar.high - low) / step));
17672
+ for (let index = first; index <= last; index += 1) {
17673
+ const overlap = Math.min(bar.high, out[index].high) - Math.max(bar.low, out[index].low);
17674
+ if (overlap > 0) out[index][side2] += bar.volume * overlap / (bar.high - bar.low);
17675
+ }
17676
+ }
17677
+ const total = out.reduce((sum, row) => sum + row.up + row.down, 0);
17678
+ if (!(total > 0)) return null;
17679
+ const size3 = (index) => index >= 0 && index < count ? out[index].up + out[index].down : -1;
17680
+ let poc = 0;
17681
+ for (let index = 1; index < count; index += 1) if (size3(index) > size3(poc)) poc = index;
17682
+ let lo = poc, hi = poc, held = size3(poc);
17683
+ while (held < total * valueArea && (lo > 0 || hi < count - 1)) {
17684
+ if (size3(hi + 1) >= size3(lo - 1)) {
17685
+ hi += 1;
17686
+ held += size3(hi);
17687
+ } else {
17688
+ lo -= 1;
17689
+ held += size3(lo);
17690
+ }
17691
+ }
17692
+ return { rows: out, total, poc: (out[poc].low + out[poc].high) / 2, pocIndex: poc, vah: out[hi].high, val: out[lo].low, valueLow: lo, valueHigh: hi };
17693
+ }
17395
17694
  var GANN_BOX_LEVELS = [0, 0.25, 0.382, 0.5, 0.618, 0.75, 1];
17396
17695
  var ALERT_KINDS = ["level", "hray", "trend", "ray", "extended"];
17696
+ var BRUSH_MIN = 2;
17397
17697
  var BRUSH_MAX = 500;
17398
17698
  var TEXT_KINDS = ["text", "callout"];
17399
17699
  var DRAWING_CLICKS = { ...Object.fromEntries(Object.entries(DRAWING_POINTS)), long: 2, short: 2 };
@@ -17403,25 +17703,66 @@ var FIB_EXT_LEVELS = [0, 0.382, 0.618, 1, 1.272, 1.618, 2, 2.618];
17403
17703
  var FIB_TIME_SERIES = [0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89];
17404
17704
  var GANN_RATIOS = [[1, 8], [1, 4], [1, 3], [1, 2], [1, 1], [2, 1], [3, 1], [4, 1], [8, 1]];
17405
17705
  var OLDEST = Date.UTC(2015, 0, 1);
17706
+ function drawingGeometry(kind, request, now = Date.now()) {
17707
+ if (!DRAWING_KINDS.includes(kind)) throw new Error(`\u753B\u7EBF\u7C7B\u578B\u5FC5\u987B\u4E3A ${DRAWING_KINDS.join("/")}`);
17708
+ const latest = now + 365 * 864e5;
17709
+ const point = (time, price, label) => {
17710
+ const t2 = Number(time), p = Number(price);
17711
+ if (!Number.isFinite(t2) || t2 < OLDEST || t2 > latest) throw new Error(`${label}\u65F6\u95F4\u5FC5\u987B\u4E3A 2015 \u5E74\u81F3\u4ECA\u540E\u4E00\u5E74\u5185\u7684\u6BEB\u79D2\u65F6\u95F4\u6233`);
17712
+ if (!Number.isFinite(p) || p <= 0) throw new Error(`${label}\u4EF7\u683C\u5FC5\u987B\u4E3A\u6B63\u6570`);
17713
+ return { time: t2, price: p };
17714
+ };
17715
+ if (kind === "level") {
17716
+ const price = Number(request?.price);
17717
+ if (!Number.isFinite(price) || price <= 0) throw new Error("\u6C34\u5E73\u7EBF price \u5FC5\u987B\u4E3A\u6B63\u6570");
17718
+ return { price };
17719
+ }
17720
+ if (kind === "trend") {
17721
+ const from = point(request?.fromTime, request?.fromPrice, "\u8D8B\u52BF\u7EBF\u8D77\u70B9");
17722
+ const to = point(request?.toTime, request?.toPrice, "\u8D8B\u52BF\u7EBF\u7EC8\u70B9");
17723
+ if (to.time <= from.time) throw new Error("\u8D8B\u52BF\u7EBF\u7EC8\u70B9\u65F6\u95F4\u5FC5\u987B\u665A\u4E8E\u8D77\u70B9");
17724
+ return { fromTime: from.time, fromPrice: from.price, toTime: to.time, toPrice: to.price };
17725
+ }
17726
+ const need = DRAWING_POINTS[kind];
17727
+ const raw = request?.points;
17728
+ if (kind === "brush") {
17729
+ if (!Array.isArray(raw) || raw.length < BRUSH_MIN || raw.length > BRUSH_MAX) throw new Error(`brush \u9700\u8981 ${BRUSH_MIN}-${BRUSH_MAX} \u4E2A points`);
17730
+ } else if (!Array.isArray(raw) || raw.length !== need) throw new Error(`${kind} \u9700\u8981 ${need} \u4E2A points\uFF08{ time, price }\uFF09`);
17731
+ const points2 = raw.map((item, index) => point(item?.time, item?.price, `\u7B2C ${index + 1} \u4E2A\u70B9`));
17732
+ if (["trend", "ray", "extended", "channel", "fibchannel", "gannfan", "regression", "fibtime", "infoline", "trendangle"].includes(kind) && points2[0].time === points2[1].time) throw new Error("\u524D\u4E24\u4E2A\u70B9\u7684\u65F6\u95F4\u4E0D\u80FD\u76F8\u540C");
17733
+ if (kind === "vprofile") {
17734
+ if (points2[0].time === points2[1].time) throw new Error("\u6210\u4EA4\u91CF\u5206\u5E03\u9700\u8981\u4E00\u4E2A\u65F6\u95F4\u533A\u95F4");
17735
+ if (points2[1].time < points2[0].time) points2.reverse();
17736
+ }
17737
+ if ((kind === "regression" || kind === "fibtime" || kind === "gannfan") && points2[1].time < points2[0].time) throw new Error("\u7B2C\u4E8C\u4E2A\u70B9\u5FC5\u987B\u665A\u4E8E\u7B2C\u4E00\u4E2A\u70B9");
17738
+ if (kind === "long" || kind === "short") {
17739
+ const [entry, stop, target] = points2.map((item) => item.price);
17740
+ const ok = kind === "long" ? stop < entry && entry < target : target < entry && entry < stop;
17741
+ if (!ok) throw new Error(kind === "long" ? "\u591A\u5934\u5934\u5BF8\u9700\u8981 \u6B62\u635F < \u5165\u573A < \u6B62\u76C8" : "\u7A7A\u5934\u5934\u5BF8\u9700\u8981 \u6B62\u76C8 < \u5165\u573A < \u6B62\u635F");
17742
+ if (points2[1].time <= points2[0].time) throw new Error("\u5934\u5BF8\u53F3\u8FB9\u754C\u65F6\u95F4\u5FC5\u987B\u665A\u4E8E\u5165\u573A\u65F6\u95F4");
17743
+ }
17744
+ return { points: points2 };
17745
+ }
17406
17746
 
17407
17747
  // src/client/drawing-tools.tsx
17408
17748
  var isTextKind = (kind) => TEXT_KINDS.includes(kind);
17409
17749
  var DRAWING_TOOLS = [
17410
- { group: "lines", kind: "trend", zh: "\u8D8B\u52BF\u7EBF", en: "Trend line", hintZh: "\u4F9D\u6B21\u70B9\u51FB\u8D77\u70B9\u548C\u7EC8\u70B9", hintEn: "Click start and end points", icon: "M4 19 20 5" },
17750
+ { group: "lines", kind: "trend", zh: "\u8D8B\u52BF\u7EBF", en: "Trend line", hintZh: "\u4F9D\u6B21\u70B9\u51FB\u8D77\u70B9\u548C\u7EC8\u70B9\uFF08Alt+T\uFF09", hintEn: "Click start and end points (Alt+T)", icon: "M4 19 20 5" },
17411
17751
  { group: "lines", kind: "ray", zh: "\u5C04\u7EBF", en: "Ray", hintZh: "\u70B9\u51FB\u8D77\u70B9\uFF0C\u518D\u70B9\u51FB\u65B9\u5411\u70B9\uFF0C\u5411\u524D\u65E0\u9650\u5EF6\u4F38", hintEn: "Click origin, then direction; extends forward", icon: "M4 19 20 5M4 19h.01" },
17412
17752
  { group: "lines", kind: "extended", zh: "\u5EF6\u957F\u7EBF", en: "Extended line", hintZh: "\u70B9\u51FB\u4E24\u70B9\uFF0C\u4E24\u7AEF\u65E0\u9650\u5EF6\u4F38", hintEn: "Click two points; extends both ways", icon: "M2 21 22 3" },
17413
- { group: "lines", kind: "level", zh: "\u6C34\u5E73\u7EBF", en: "Horizontal line", hintZh: "\u5355\u51FB\u8BBE\u7F6E\u4EF7\u683C", hintEn: "Click to set the price", icon: "M3 12h18" },
17414
- { group: "lines", kind: "hray", zh: "\u6C34\u5E73\u5C04\u7EBF", en: "Horizontal ray", hintZh: "\u5355\u51FB\u8D77\u70B9\uFF0C\u5411\u53F3\u5EF6\u4F38", hintEn: "Click origin; extends right", icon: "M6 12h15M6 12h.01" },
17753
+ { group: "lines", kind: "level", zh: "\u6C34\u5E73\u7EBF", en: "Horizontal line", hintZh: "\u5355\u51FB\u8BBE\u7F6E\u4EF7\u683C\uFF08Alt+H\uFF09", hintEn: "Click to set the price (Alt+H)", icon: "M3 12h18" },
17754
+ { group: "lines", kind: "hray", zh: "\u6C34\u5E73\u5C04\u7EBF", en: "Horizontal ray", hintZh: "\u5355\u51FB\u8D77\u70B9\uFF0C\u5411\u53F3\u5EF6\u4F38\uFF08Alt+J\uFF09", hintEn: "Click origin; extends right (Alt+J)", icon: "M6 12h15M6 12h.01" },
17415
17755
  { group: "lines", kind: "infoline", zh: "\u4FE1\u606F\u7EBF", en: "Info line", hintZh: "\u4E24\u70B9\u753B\u7EBF\uFF0C\u663E\u793A\u6DA8\u8DCC\u3001\u6839\u6570\u3001\u65F6\u957F\u548C\u89D2\u5EA6", hintEn: "Two points; shows change, bars, time and angle", icon: "M4 18 18 6M14 4h6v5" },
17416
17756
  { group: "lines", kind: "trendangle", zh: "\u8D8B\u52BF\u89D2\u5EA6", en: "Trend angle", hintZh: "\u4E24\u70B9\u753B\u7EBF\uFF0C\u663E\u793A\u4E0E\u6C34\u5E73\u7EBF\u7684\u5939\u89D2", hintEn: "Two points; shows the angle to horizontal", icon: "M4 19 19 6M4 19h15M10 19a6 6 0 0 0-1.6-4" },
17417
- { group: "lines", kind: "crossline", zh: "\u5341\u5B57\u7EBF", en: "Cross line", hintZh: "\u5355\u51FB\u653E\u7F6E\u6C34\u5E73\u4E0E\u5782\u76F4\u4EA4\u53C9\u7EBF", hintEn: "Click to place a horizontal and vertical cross", icon: "M12 3v18M3 12h18" },
17418
- { group: "lines", kind: "vline", zh: "\u5782\u76F4\u7EBF", en: "Vertical line", hintZh: "\u5355\u51FB\u6807\u8BB0\u65F6\u95F4", hintEn: "Click to mark a time", icon: "M12 3v18" },
17757
+ { group: "lines", kind: "crossline", zh: "\u5341\u5B57\u7EBF", en: "Cross line", hintZh: "\u5355\u51FB\u653E\u7F6E\u6C34\u5E73\u4E0E\u5782\u76F4\u4EA4\u53C9\u7EBF\uFF08Alt+C\uFF09", hintEn: "Click to place a horizontal and vertical cross (Alt+C)", icon: "M12 3v18M3 12h18" },
17758
+ { group: "lines", kind: "vline", zh: "\u5782\u76F4\u7EBF", en: "Vertical line", hintZh: "\u5355\u51FB\u6807\u8BB0\u65F6\u95F4\uFF08Alt+V\uFF09", hintEn: "Click to mark a time (Alt+V)", icon: "M12 3v18" },
17419
17759
  { group: "channels", kind: "channel", zh: "\u5E73\u884C\u901A\u9053", en: "Parallel channel", hintZh: "\u70B9\u51FB\u4E24\u70B9\u753B\u57FA\u7EBF\uFF0C\u7B2C\u4E09\u70B9\u8BBE\u7F6E\u901A\u9053\u5BBD\u5EA6", hintEn: "Two points for the base, a third for width", icon: "M3 15 15 5M9 19 21 9" },
17420
17760
  { group: "channels", kind: "pitchfork", zh: "\u5B89\u5FB7\u9C81\u53C9\u5B50\u7EBF", en: "Pitchfork", hintZh: "\u70B9\u51FB\u67A2\u8F74\u70B9\uFF0C\u518D\u70B9\u51FB\u6CE2\u6BB5\u9AD8\u70B9\u4E0E\u4F4E\u70B9", hintEn: "Click the pivot, then the swing high and low", icon: "M4 20 12 4M12 4l8 8M12 4 4 12M8 16l12-4" },
17421
17761
  { group: "channels", kind: "schiff", zh: "Schiff \u53C9\u5B50\u7EBF", en: "Schiff pitchfork", hintZh: "\u540C\u53C9\u5B50\u7EBF\uFF0C\u8D77\u70B9\u79FB\u5230 A\u3001B \u4EF7\u683C\u4E2D\u70B9", hintEn: "Pitchfork with the handle at A\u2013B price midpoint", icon: "M4 14 12 4M12 4l8 8M12 4 4 12M8 16l12-4" },
17422
17762
  { group: "channels", kind: "modschiff", zh: "\u6539\u826F Schiff \u53C9\u5B50\u7EBF", en: "Modified Schiff pitchfork", hintZh: "\u540C\u53C9\u5B50\u7EBF\uFF0C\u8D77\u70B9\u79FB\u5230 A\u3001B \u65F6\u95F4\u4E0E\u4EF7\u683C\u4E2D\u70B9", hintEn: "Pitchfork with the handle at the A\u2013B midpoint", icon: "M6 12 12 4M12 4l8 8M12 4 4 12M8 16l12-4" },
17423
17763
  { group: "channels", kind: "regression", zh: "\u56DE\u5F52\u8D8B\u52BF", en: "Regression trend", hintZh: "\u70B9\u51FB\u533A\u95F4\u8D77\u70B9\u548C\u7EC8\u70B9\uFF0C\u6309\u6536\u76D8\u4EF7\u62DF\u5408 \xB12\u03C3 \u901A\u9053", hintEn: "Click range start and end; fits closes with \xB12\u03C3", icon: "M3 17 21 7M3 13 21 3M3 21 21 11" },
17424
- { group: "fib", kind: "fib", zh: "\u6590\u6CE2\u90A3\u5951\u56DE\u64A4", en: "Fib retracement", hintZh: "\u4ECE\u6CE2\u6BB5\u8D77\u70B9\u70B9\u5230\u7EC8\u70B9\uFF08\u7EC8\u70B9\u4E3A 0\uFF09", hintEn: "Click swing start, then end (end = 0)", icon: "M3 5h18M3 9h18M3 13h18M3 19h18" },
17764
+ { group: "channels", kind: "vprofile", zh: "\u56FA\u5B9A\u8303\u56F4\u6210\u4EA4\u91CF\u5206\u5E03", en: "Fixed range volume profile", hintZh: "\u70B9\u51FB\u533A\u95F4\u8D77\u70B9\u548C\u7EC8\u70B9\uFF0C\u6309\u4EF7\u683C\u7EDF\u8BA1\u6210\u4EA4\u91CF\uFF0C\u6807\u51FA POC \u4E0E 70% \u4EF7\u503C\u533A", hintEn: "Click range start and end; volume by price with POC and 70% value area", icon: "M4 3v18M4 6h7M4 9h12M4 12h16M4 15h9M4 18h5" },
17765
+ { group: "fib", kind: "fib", zh: "\u6590\u6CE2\u90A3\u5951\u56DE\u64A4", en: "Fib retracement", hintZh: "\u4ECE\u6CE2\u6BB5\u8D77\u70B9\u70B9\u5230\u7EC8\u70B9\uFF08\u7EC8\u70B9\u4E3A 0\uFF09\uFF08Alt+F\uFF09", hintEn: "Click swing start, then end (end = 0) (Alt+F)", icon: "M3 5h18M3 9h18M3 13h18M3 19h18" },
17425
17766
  { group: "fib", kind: "fibext", zh: "\u6590\u6CE2\u90A3\u5951\u6269\u5C55", en: "Trend-based fib extension", hintZh: "\u70B9\u51FB\u6CE2\u6BB5\u8D77\u70B9\u3001\u7EC8\u70B9\uFF0C\u518D\u70B9\u51FB\u56DE\u64A4\u70B9\uFF0C\u81EA\u56DE\u64A4\u70B9\u6295\u5C04", hintEn: "Swing start, swing end, then the retracement point", icon: "M3 20 9 6l6 9M15 4h7M15 8h7M15 12h7" },
17426
17767
  { group: "fib", kind: "fibchannel", zh: "\u6590\u6CE2\u90A3\u5951\u901A\u9053", en: "Fib channel", hintZh: "\u4E24\u70B9\u753B\u57FA\u7EBF\uFF0C\u7B2C\u4E09\u70B9\u8BBE\u7F6E\u901A\u9053\u5BBD\u5EA6", hintEn: "Two points for the base, a third for width", icon: "M3 19 21 9M3 15 21 5M3 11 21 1" },
17427
17768
  { group: "fib", kind: "fibtime", zh: "\u6590\u6CE2\u90A3\u5951\u65F6\u95F4\u5468\u671F", en: "Fib time zone", hintZh: "\u70B9\u51FB\u4E24\u70B9\u786E\u5B9A\u5355\u4F4D\u95F4\u9694", hintEn: "Click two points to set the unit spacing", icon: "M4 3v18M7 3v18M10 3v18M15 3v18M22 3v18" },
@@ -17433,7 +17774,7 @@ var DRAWING_TOOLS = [
17433
17774
  { group: "patterns", kind: "xabcd", zh: "XABCD \u8C10\u6CE2", en: "XABCD pattern", hintZh: "\u4F9D\u6B21\u70B9\u51FB X\u3001A\u3001B\u3001C\u3001D\uFF0C\u663E\u793A\u6590\u6CE2\u90A3\u5951\u6BD4\u503C", hintEn: "Click X, A, B, C, D; shows Fibonacci ratios", icon: "M2 18 7 5l4 9 5-7 6 12M2 18l9-4M11 14l11 5" },
17434
17775
  { group: "patterns", kind: "abcd", zh: "ABCD \u5F62\u6001", en: "ABCD pattern", hintZh: "\u4F9D\u6B21\u70B9\u51FB A\u3001B\u3001C\u3001D", hintEn: "Click A, B, C, D", icon: "M3 19 9 6l5 8 7-10" },
17435
17776
  { group: "patterns", kind: "headshoulders", zh: "\u5934\u80A9\u5F62\u6001", en: "Head and shoulders", hintZh: "\u4F9D\u6B21\u70B9\u51FB\u5DE6\u5E95\u3001\u5DE6\u80A9\u3001\u9888\u7EBF\u70B9\u3001\u5934\u3001\u9888\u7EBF\u70B9\u3001\u53F3\u80A9\u3001\u53F3\u5E95", hintEn: "Click 7 points from left base to right base", icon: "M2 18l3-6 3 3 4-10 4 10 3-3 3 6M5 15h14" },
17436
- { group: "shapes", kind: "rect", zh: "\u77E9\u5F62", en: "Rectangle", hintZh: "\u70B9\u51FB\u4E24\u4E2A\u5BF9\u89D2", hintEn: "Click two opposite corners", icon: "M4 6h16v12H4z" },
17777
+ { group: "shapes", kind: "rect", zh: "\u77E9\u5F62", en: "Rectangle", hintZh: "\u70B9\u51FB\u4E24\u4E2A\u5BF9\u89D2\uFF08Alt+Shift+R\uFF09", hintEn: "Click two opposite corners (Alt+Shift+R)", icon: "M4 6h16v12H4z" },
17437
17778
  { group: "shapes", kind: "circle", zh: "\u5706\u5F62", en: "Circle", hintZh: "\u70B9\u51FB\u5706\u5FC3\uFF0C\u518D\u70B9\u51FB\u8FB9\u7F18", hintEn: "Click the center, then the edge", icon: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z" },
17438
17779
  { group: "shapes", kind: "triangle", zh: "\u4E09\u89D2\u5F62", en: "Triangle", hintZh: "\u4F9D\u6B21\u70B9\u51FB\u4E09\u4E2A\u9876\u70B9", hintEn: "Click three corners", icon: "M12 4 21 20H3z" },
17439
17780
  { group: "shapes", kind: "brush", zh: "\u753B\u7B14", en: "Brush", hintZh: "\u6309\u4F4F\u9F20\u6807\u62D6\u52A8\u81EA\u7531\u7ED8\u5236", hintEn: "Press and drag to draw freehand", icon: "M3 17c3-6 5 2 8-3s4-6 7-4 3 4 3 4" },
@@ -17546,6 +17887,27 @@ function duration(ms) {
17546
17887
  if (hours2 < 48) return `${Math.round(hours2 * 10) / 10}h`;
17547
17888
  return `${Math.round(hours2 / 24 * 10) / 10}d`;
17548
17889
  }
17890
+ var textWidth = (value) => [...value].reduce((sum, ch) => sum + (ch.charCodeAt(0) > 255 ? 12 : 6.6), 0);
17891
+ function wrapText(value, max) {
17892
+ const out = [];
17893
+ for (const paragraph of value.split("\n")) {
17894
+ let line = "";
17895
+ for (const ch of paragraph) {
17896
+ if (line && textWidth(line + ch) > max) {
17897
+ const space = line.lastIndexOf(" ");
17898
+ if (ch !== " " && space > 0 && /^[\x20-\x7e]+$/.test(line.slice(space + 1))) {
17899
+ out.push(line.slice(0, space));
17900
+ line = line.slice(space + 1) + ch;
17901
+ } else {
17902
+ out.push(line);
17903
+ line = ch === " " ? "" : ch;
17904
+ }
17905
+ } else line += ch;
17906
+ }
17907
+ out.push(line);
17908
+ }
17909
+ return out.slice(0, 12);
17910
+ }
17549
17911
  function regressionFit(bars) {
17550
17912
  const n2 = bars.length;
17551
17913
  if (n2 < 2) return null;
@@ -17568,12 +17930,13 @@ function wave(p, labels, text) {
17568
17930
  }));
17569
17931
  }
17570
17932
  var ratio = (a, b) => b === 0 ? "\u2014" : (Math.abs(a) / Math.abs(b)).toFixed(3);
17571
- function guide(a, b, value, key2, text) {
17572
- return /* @__PURE__ */ React7.createElement("g", { key: key2 }, /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: a.x, y1: a.y, x2: b.x, y2: b.y }), text((a.x + b.x) / 2, (a.y + b.y) / 2 - 4, value, "middle", "dhl-drawing-text dhl-ratio-label"));
17933
+ function guide(a, b, value, key3, text) {
17934
+ return /* @__PURE__ */ React7.createElement("g", { key: key3 }, /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: a.x, y1: a.y, x2: b.x, y2: b.y }), text((a.x + b.x) / 2, (a.y + b.y) / 2 - 4, value, "middle", "dhl-drawing-text dhl-ratio-label"));
17573
17935
  }
17574
17936
  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" };
17575
17937
  var DASH = { solid: void 0, dashed: "7 5", dotted: "1.5 4" };
17576
- var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, label, text: textValue, className, view, anchors = false, interactive = false, color, lineWidth, lineStyle, id, badges }) {
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 }) {
17939
+ const badges = [...alert ? ["alert"] : [], ...shared ? ["shared"] : []];
17577
17940
  const { width: w, height: h } = view;
17578
17941
  const px = points2.map((point) => view.project(point.time, point.price));
17579
17942
  if (!px.length || px.some((point) => point == null)) return null;
@@ -17625,8 +17988,8 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
17625
17988
  if (!p[1]) break;
17626
17989
  const [start, end] = points2;
17627
17990
  const x0 = Math.min(p[0].x, p[1].x), x1 = Math.max(p[0].x, p[1].x, x0 + 40);
17628
- const rows = 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);
17629
- body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, rows.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, rows[index].y), height: Math.abs(row.y - rows[index].y) })), rows.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 }));
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);
17992
+ 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 }));
17630
17993
  break;
17631
17994
  }
17632
17995
  case "rect": {
@@ -17707,8 +18070,8 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
17707
18070
  }
17708
18071
  const [a, b, c] = points2;
17709
18072
  const x0 = p[2].x, x1 = Math.max(x0 + Math.abs(p[1].x - p[0].x), x0 + 60);
17710
- const rows = 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);
17711
- 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}` }), rows.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)})`))));
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);
18074
+ 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)})`))));
17712
18075
  break;
17713
18076
  }
17714
18077
  case "fibchannel": {
@@ -17823,10 +18186,37 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
17823
18186
  handles = [c0, c1];
17824
18187
  break;
17825
18188
  }
18189
+ case "vprofile": {
18190
+ if (!p[1]) break;
18191
+ const [from, to] = points2[0].time <= points2[1].time ? points2 : [points2[1], points2[0]];
18192
+ const x0 = Math.min(p[0].x, p[1].x), x1 = Math.max(p[0].x, p[1].x);
18193
+ const settings = { rows: rows ?? 48, valueArea: valueArea ?? 0.7 };
18194
+ 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;
18196
+ if (!profile) {
18197
+ 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
+ handles = [p[0], p[1]];
18199
+ break;
18200
+ }
18201
+ const y = (price) => view.project(from.time, price)?.y;
18202
+ const maxRow = Math.max(...profile.rows.map((row) => row.up + row.down)) || 1;
18203
+ const span = Math.max(24, (x1 - x0) * 0.3);
18204
+ 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) => {
18206
+ const top = y(row.high), bottom = y(row.low);
18207
+ if (top == null || bottom == null) return null;
18208
+ const height = Math.max(1, bottom - top - 0.5), inValue = index >= profile.valueLow && index <= profile.valueHigh;
18209
+ 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);
18212
+ handles = [p[0], p[1]];
18213
+ break;
18214
+ }
17826
18215
  case "text": {
17827
18216
  const value = textValue || label || tr("\u6587\u672C", "Text");
17828
- const width = Math.min(260, 14 + [...value].reduce((sum, ch) => sum + (ch.charCodeAt(0) > 255 ? 12 : 6.6), 0));
17829
- body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-text-box", x: p[0].x, y: p[0].y - 20, width, height: "24", rx: "4" }), text(p[0].x + 7, p[0].y - 4, value, "start", "dhl-drawing-text dhl-text-value"));
18217
+ const lines = wrapText(value, 300);
18218
+ const width = 14 + Math.max(...lines.map(textWidth));
18219
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-text-box", x: p[0].x, y: p[0].y - 20, width, height: 8 + lines.length * 16, rx: "4" }), lines.map((line, index) => /* @__PURE__ */ React7.createElement(React7.Fragment, { key: index }, text(p[0].x + 7, p[0].y - 4 + index * 16, line, "start", "dhl-drawing-text dhl-text-value"))));
17830
18220
  break;
17831
18221
  }
17832
18222
  case "measure": {
@@ -17850,7 +18240,7 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
17850
18240
  const reward = target ? Math.abs(target.price - entry.price) : 0;
17851
18241
  const pct = (value) => entry.price > 0 ? (value / entry.price * 100).toFixed(2) : "0.00";
17852
18242
  const x0 = Math.min(left, right), width = Math.max(2, Math.abs(right - left));
17853
- body = /* @__PURE__ */ React7.createElement("g", { className: "dhl-position" }, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-position-stop", x: x0, width, y: Math.min(yEntry, yStop), height: Math.abs(yStop - yEntry) }), yTarget != null ? /* @__PURE__ */ React7.createElement("rect", { className: "dhl-position-target", x: x0, width, y: Math.min(yEntry, yTarget), height: Math.abs(yTarget - yEntry) }) : null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-position-entry", x1: x0, x2: x0 + width, y1: yEntry, y2: yEntry }), yTarget != null ? text(x0 + width / 2, yTarget + (kind === "long" ? 14 : -6), `${tr("\u6B62\u76C8", "Target")} ${fmtPrice(target.price)} (+${pct(reward)}%)`, "middle", "dhl-drawing-text dhl-position-text") : null, text(x0 + width / 2, yStop + (kind === "long" ? -6 : 14), `${tr("\u6B62\u635F", "Stop")} ${fmtPrice(stop.price)} (-${pct(risk)}%)`, "middle", "dhl-drawing-text dhl-position-text"), text(x0 + width / 2, yEntry + (kind === "long" ? 14 : -6), `${kind === "long" ? tr("\u591A", "Long") : tr("\u7A7A", "Short")} ${fmtPrice(entry.price)} \xB7 R:R ${risk > 0 ? (reward / risk).toFixed(2) : "\u2014"}`, "middle", "dhl-drawing-text dhl-position-text"));
18243
+ body = /* @__PURE__ */ React7.createElement("g", { className: "dhl-position" }, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-position-stop", x: x0, width, y: Math.min(yEntry, yStop), height: Math.abs(yStop - yEntry) }), yTarget != null ? /* @__PURE__ */ React7.createElement("rect", { className: "dhl-position-target", x: x0, width, y: Math.min(yEntry, yTarget), height: Math.abs(yTarget - yEntry) }) : null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-position-entry", x1: x0, x2: x0 + width, y1: yEntry, y2: yEntry }), yTarget != null ? text(x0 + width / 2, yTarget + (kind === "long" ? -6 : 14), `${tr("\u6B62\u76C8", "Target")} ${fmtPrice(target.price)} (+${pct(reward)}%)`, "middle", "dhl-drawing-text dhl-position-text") : null, text(x0 + width / 2, yStop + (kind === "long" ? 14 : -6), `${tr("\u6B62\u635F", "Stop")} ${fmtPrice(stop.price)} (-${pct(risk)}%)`, "middle", "dhl-drawing-text dhl-position-text"), text(x0 + width / 2, yEntry + (kind === "long" ? -6 : 14), `${kind === "long" ? tr("\u591A", "Long") : tr("\u7A7A", "Short")} ${fmtPrice(entry.price)} \xB7 R:R ${risk > 0 ? (reward / risk).toFixed(2) : "\u2014"}`, "middle", "dhl-drawing-text dhl-position-text"));
17854
18244
  handles = [p[0], p[1], ...p[2] ? [p[2]] : []];
17855
18245
  break;
17856
18246
  }
@@ -17887,9 +18277,9 @@ function saveStyleTemplate(kind, style) {
17887
18277
  }
17888
18278
 
17889
18279
  // src/client/components/draw-toolbar.tsx
17890
- var COLORS = ["#4c82d8", "#2fd3a0", "#ef5f6c", "#e2a943", "#9b86db", "#26b5c8", "#8a8f98"];
18280
+ var DRAW_COLORS = ["#4c82d8", "#2fd3a0", "#ef5f6c", "#e2a943", "#9b86db", "#26b5c8", "#8a8f98"];
17891
18281
  var STYLES = [["solid", "\u5B9E\u7EBF", "Solid"], ["dashed", "\u865A\u7EBF", "Dashed"], ["dotted", "\u70B9\u7EBF", "Dotted"]];
17892
- function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone }) {
18282
+ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone, onCoordinates }) {
17893
18283
  const [savedDefault, setSavedDefault] = React8.useState(false);
17894
18284
  React8.useEffect(() => {
17895
18285
  setSavedDefault(false);
@@ -17920,7 +18310,7 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone
17920
18310
  if (text.trim() && text !== mark.label) onEdit({ label: text.trim() });
17921
18311
  }
17922
18312
  }
17923
- ) : null, /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u989C\u8272", "Color") }, COLORS.map((color) => /* @__PURE__ */ React8.createElement(
18313
+ ) : null, /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u989C\u8272", "Color") }, DRAW_COLORS.map((color) => /* @__PURE__ */ React8.createElement(
17924
18314
  "button",
17925
18315
  {
17926
18316
  type: "button",
@@ -17958,7 +18348,7 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone
17958
18348
  onClick: () => onEdit({ lineStyle: value })
17959
18349
  },
17960
18350
  /* @__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" }))
17961
- ))) : null, /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group" }, /* @__PURE__ */ React8.createElement(
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(
17962
18352
  "button",
17963
18353
  {
17964
18354
  type: "button",
@@ -18010,6 +18400,46 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone
18010
18400
  ), /* @__PURE__ */ React8.createElement("button", { type: "button", disabled: locked, title: tr("\u5220\u9664\uFF08Delete\uFF09", "Delete (Del)"), "aria-label": tr("\u5220\u9664\u753B\u7EBF", "Delete drawing"), onClick: onRemove }, /* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("path", { d: "M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" }))), /* @__PURE__ */ React8.createElement("button", { type: "button", title: tr("\u53D6\u6D88\u9009\u62E9\uFF08Esc\uFF09", "Deselect (Esc)"), "aria-label": tr("\u53D6\u6D88\u9009\u62E9", "Deselect"), onClick: onClose }, /* @__PURE__ */ React8.createElement(UiIcon, { name: "close", size: 14 }))));
18011
18401
  }
18012
18402
 
18403
+ // src/client/components/drawing-editor.tsx
18404
+ var React9 = __toESM(require("react"), 1);
18405
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
18406
+ function localDate(time) {
18407
+ const date = new Date(time);
18408
+ return new Date(time - date.getTimezoneOffset() * 6e4).toISOString().slice(0, 23);
18409
+ }
18410
+ function DrawingEditor({ mark, onApply, onClose }) {
18411
+ const [label, setLabel] = React9.useState(mark.label);
18412
+ const [anchors, setAnchors] = React9.useState(() => markPoints(mark).map((point) => ({ time: localDate(point.time), price: String(point.price) })));
18413
+ const [error, setError] = React9.useState("");
18414
+ const update = (index, key3, value) => setAnchors((rows) => rows.map((row, at) => at === index ? { ...row, [key3]: value } : row));
18415
+ const apply2 = () => {
18416
+ try {
18417
+ if (!label.trim()) throw new Error(tr("\u8BF7\u8F93\u5165\u540D\u79F0\u6216\u6587\u672C", "Enter a name or text"));
18418
+ const points2 = anchors.map((row) => ({ time: new Date(row.time).getTime(), price: Number(row.price) }));
18419
+ const geometry = drawingGeometry(mark.kind, geometryPatch(mark.kind, points2));
18420
+ onApply({ ...geometry, label: label.trim() });
18421
+ onClose();
18422
+ } catch (problem) {
18423
+ setError(String(problem.message ?? problem));
18424
+ }
18425
+ };
18426
+ return /* @__PURE__ */ React9.createElement(
18427
+ import_dsh_client_ui_primitives2.Modal,
18428
+ {
18429
+ open: true,
18430
+ onClose,
18431
+ title: `${tr(toolOf(mark.kind).zh, toolOf(mark.kind).en)} \xB7 ${tr("\u5750\u6807\u4E0E\u540D\u79F0", "Coordinates & name")}`,
18432
+ closeLabel: tr("\u5173\u95ED", "Close"),
18433
+ className: "dhl-coordinate-modal",
18434
+ footer: /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(import_dsh_client_ui_primitives2.Button, { variant: "outline", onClick: onClose }, tr("\u53D6\u6D88", "Cancel")), /* @__PURE__ */ React9.createElement(import_dsh_client_ui_primitives2.Button, { variant: "primary", onClick: apply2 }, tr("\u5E94\u7528\u5750\u6807", "Apply coordinates")))
18435
+ },
18436
+ /* @__PURE__ */ React9.createElement("form", { className: "dhl-coordinate-editor", onSubmit: (event) => {
18437
+ event.preventDefault();
18438
+ apply2();
18439
+ } }, /* @__PURE__ */ React9.createElement("label", null, tr("\u540D\u79F0 / \u6587\u672C", "Name / text"), /* @__PURE__ */ React9.createElement(import_dsh_client_ui_primitives2.Input, { "aria-label": tr("\u753B\u7EBF\u540D\u79F0", "Drawing name"), value: label, readOnly: mark.kind === "icon", maxLength: isTextKind(mark.kind) ? 200 : 60, onChange: (event) => setLabel(event.target.value) })), /* @__PURE__ */ React9.createElement("small", null, tr("\u65F6\u95F4\u91C7\u7528\u672C\u5730\u65F6\u533A", "Times use your local timezone"), " \xB7 ", Intl.DateTimeFormat().resolvedOptions().timeZone), /* @__PURE__ */ React9.createElement("div", { className: "dhl-coordinate-anchors" }, anchors.map((row, index) => /* @__PURE__ */ React9.createElement("fieldset", { key: index }, /* @__PURE__ */ React9.createElement("legend", null, tr(`\u951A\u70B9 ${index + 1}`, `Anchor ${index + 1}`)), mark.kind !== "level" ? /* @__PURE__ */ React9.createElement("label", null, tr("\u65F6\u95F4", "Time"), /* @__PURE__ */ React9.createElement(import_dsh_client_ui_primitives2.Input, { type: "datetime-local", step: "0.001", value: row.time, "aria-label": tr(`\u951A\u70B9 ${index + 1} \u65F6\u95F4`, `Anchor ${index + 1} time`), onChange: (event) => update(index, "time", event.target.value), required: true })) : null, /* @__PURE__ */ React9.createElement("label", null, tr("\u4EF7\u683C", "Price"), /* @__PURE__ */ React9.createElement(import_dsh_client_ui_primitives2.Input, { type: "number", step: "any", min: "0", value: row.price, "aria-label": tr(`\u951A\u70B9 ${index + 1} \u4EF7\u683C`, `Anchor ${index + 1} price`), onChange: (event) => update(index, "price", event.target.value), required: true }))))), error ? /* @__PURE__ */ React9.createElement("p", { role: "alert", className: "dhl-inline-error" }, error) : null)
18440
+ );
18441
+ }
18442
+
18013
18443
  // src/client/click-guard.ts
18014
18444
  var LIVE_SHIFT_BARS = 2;
18015
18445
  function legacyPressStillAClick(atPress, atRelease) {
@@ -18024,6 +18454,100 @@ function pressStillAClick(atPress, atRelease) {
18024
18454
  return !zoomed && !panned;
18025
18455
  }
18026
18456
 
18457
+ // src/client/chart-basics.ts
18458
+ function chartBars(candles, type) {
18459
+ if (type !== "heikinAshi") return [...candles];
18460
+ const result = [];
18461
+ for (const candle of candles) {
18462
+ const prior = result.at(-1);
18463
+ const o = prior ? (prior.o + prior.c) / 2 : (candle.o + candle.c) / 2;
18464
+ const c = (candle.o + candle.h + candle.l + candle.c) / 4;
18465
+ result.push({ ...candle, o, c, h: Math.max(candle.h, o, c), l: Math.min(candle.l, o, c) });
18466
+ }
18467
+ return result;
18468
+ }
18469
+ function candleIndex(candles, time) {
18470
+ let low = 0, high = candles.length;
18471
+ while (low < high) {
18472
+ const mid = low + high >>> 1;
18473
+ if (candles[mid].t < time) low = mid + 1;
18474
+ else high = mid;
18475
+ }
18476
+ return Math.min(low, candles.length - 1);
18477
+ }
18478
+ function candlesCsv(candles, coin, interval) {
18479
+ const cell = (value) => '"' + value.replaceAll('"', '""') + '"';
18480
+ return "\uFEFF" + ["symbol,interval,time_utc,open,high,low,close,volume", ...candles.map((bar) => [cell(coin), cell(interval), new Date(bar.t).toISOString(), bar.o, bar.h, bar.l, bar.c, bar.v].join(","))].join("\r\n") + "\r\n";
18481
+ }
18482
+ function downloadBlob(blob, filename) {
18483
+ const url = URL.createObjectURL(blob);
18484
+ const link = document.createElement("a");
18485
+ link.href = url;
18486
+ link.download = filename;
18487
+ document.body.append(link);
18488
+ link.click();
18489
+ link.remove();
18490
+ setTimeout(() => URL.revokeObjectURL(url), 1e4);
18491
+ }
18492
+
18493
+ // src/client/chart-export.ts
18494
+ async function chartImage(chart, box, title) {
18495
+ const source = chart.takeScreenshot();
18496
+ const scale = source.width / box.clientWidth;
18497
+ const canvas = document.createElement("canvas");
18498
+ canvas.width = source.width;
18499
+ canvas.height = source.height + Math.round(30 * scale);
18500
+ const ctx = canvas.getContext("2d");
18501
+ const theme = getComputedStyle(box);
18502
+ let background = "#151517";
18503
+ for (let node = box; node; node = node.parentElement) {
18504
+ const color = getComputedStyle(node).backgroundColor;
18505
+ if (color !== "transparent" && color !== "rgba(0, 0, 0, 0)") {
18506
+ background = color;
18507
+ break;
18508
+ }
18509
+ }
18510
+ ctx.fillStyle = background;
18511
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
18512
+ ctx.drawImage(source, 0, 30 * scale);
18513
+ const overlay = box.querySelector(".dhl-chart-drawing-overlay");
18514
+ if (overlay) {
18515
+ const clone2 = overlay.cloneNode(true);
18516
+ const originals = [overlay, ...overlay.querySelectorAll("*")];
18517
+ const copies = [clone2, ...clone2.querySelectorAll("*")];
18518
+ originals.forEach((original, index) => {
18519
+ const css2 = getComputedStyle(original);
18520
+ for (const key3 of ["color", "fill", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "opacity", "font-family", "font-size", "font-weight", "text-anchor", "dominant-baseline", "paint-order"]) {
18521
+ copies[index].style.setProperty(key3, css2.getPropertyValue(key3));
18522
+ }
18523
+ });
18524
+ clone2.querySelectorAll(".dhl-drawing-hit, .dhl-chart-anchor, .dhl-drawing-preview").forEach((node) => node.remove());
18525
+ clone2.setAttribute("xmlns", "http://www.w3.org/2000/svg");
18526
+ clone2.setAttribute("width", String(box.clientWidth));
18527
+ clone2.setAttribute("height", String(box.clientHeight));
18528
+ const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone2)], { type: "image/svg+xml" }));
18529
+ try {
18530
+ const image = new Image();
18531
+ image.src = url;
18532
+ await image.decode();
18533
+ ctx.drawImage(image, 0, 30 * scale, source.width, source.height);
18534
+ } finally {
18535
+ URL.revokeObjectURL(url);
18536
+ }
18537
+ }
18538
+ ctx.fillStyle = theme.color;
18539
+ ctx.font = `${11 * scale}px sans-serif`;
18540
+ ctx.fillText(title, 10 * scale, 19 * scale);
18541
+ for (const selector of [".dhl-chart-ohlc", ".dhl-chart-ma-legend", ".dhl-chart-study-legend"]) {
18542
+ const element = box.querySelector(selector);
18543
+ if (!element) continue;
18544
+ const rect = element.getBoundingClientRect(), base = box.getBoundingClientRect();
18545
+ ctx.font = `${10 * scale}px sans-serif`;
18546
+ ctx.fillText(element.innerText.replaceAll("\n", " "), (rect.left - base.left) * scale, (rect.top - base.top + 11 + 30) * scale, source.width - 24 * scale);
18547
+ }
18548
+ return new Promise((resolve, reject) => canvas.toBlob((blob) => blob ? resolve(blob) : reject(new Error("PNG export failed")), "image/png"));
18549
+ }
18550
+
18027
18551
  // src/client/components/candle-chart.tsx
18028
18552
  var MAGNET_PX = 14;
18029
18553
  var MA_PALETTE = ["#29b6f6", "#ff7eb6", "#c6e377", "#ffb74d", "#b39ddb", "#4dd0e1", "#f48fb1", "#aed581"];
@@ -18058,53 +18582,81 @@ function cssVar(el, name, fallback) {
18058
18582
  function chartData(points2) {
18059
18583
  return points2.map((point) => ({ time: point.time, value: point.value }));
18060
18584
  }
18061
- function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMode, onDraw, onSelect, priceScale, visibleRange, onVisibleRange, tradeEvents, onNeedOlder, olderState, focus, maLines, magnet, drawingsLocked, onEditMark, onRemoveMark, showReasons, onAnalyzeFill, onCloneMark, selectRequest }) {
18062
- const boxRef = React9.useRef(null);
18063
- const refs = React9.useRef(null);
18064
- const priceLines = React9.useRef(/* @__PURE__ */ new Map());
18065
- const tradeMarkers = React9.useRef(null);
18066
- const fitted = React9.useRef(false);
18067
- const firstBarTime = React9.useRef(null);
18068
- const handledFocus = React9.useRef(0);
18069
- const maRefs = React9.useRef(/* @__PURE__ */ new Map());
18070
- const restoring = React9.useRef(false);
18071
- const initialRange = React9.useRef(visibleRange);
18072
- const drawModeRef = React9.useRef(drawMode);
18073
- const onDrawRef = React9.useRef(onDraw);
18074
- const onSelectRef = React9.useRef(onSelect);
18075
- const selectionData = React9.useRef({ candles, indicators, maLines });
18076
- const onRangeRef = React9.useRef(onVisibleRange);
18077
- const onNeedOlderRef = React9.useRef(onNeedOlder);
18585
+ var profileCache = /* @__PURE__ */ new WeakMap();
18586
+ function cachedProfile(candles, from, to, options = {}) {
18587
+ let byRange = profileCache.get(candles);
18588
+ if (!byRange) {
18589
+ byRange = /* @__PURE__ */ new Map();
18590
+ profileCache.set(candles, byRange);
18591
+ }
18592
+ const key3 = `${from}:${to}:${options.rows ?? ""}:${options.valueArea ?? ""}`;
18593
+ if (!byRange.has(key3)) byRange.set(key3, volumeProfile(candles.filter((c) => c.t >= from && c.t <= to).map((c) => ({ open: c.o, high: c.h, low: c.l, close: c.c, volume: c.v })), options));
18594
+ return byRange.get(key3);
18595
+ }
18596
+ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMode, onDraw, onSelect, priceScale, visibleRange, onVisibleRange, tradeEvents, onNeedOlder, olderState, focus, maLines, magnet, drawingsLocked, onEditMark, onRemoveMark, onRemoveMarks, onEditMarks, onEditEach, showReasons, onAnalyzeFill, onCloneMark, selectRequest, chartType = "candles", command = null, caption = "", indicatorSettings, showDataWindow = false, link, compact = false, readOnly = false }) {
18597
+ const boxRef = React10.useRef(null);
18598
+ const clipId = React10.useRef("dhl-plot-" + Math.random().toString(36).slice(2));
18599
+ const linkRef = React10.useRef(link);
18600
+ linkRef.current = link;
18601
+ const syncingCursor = React10.useRef(false);
18602
+ const externalRangeUntil = React10.useRef(0);
18603
+ const settings = React10.useMemo(() => normalizeIndicatorSettings(indicatorSettings), [indicatorSettings]);
18604
+ const refs = React10.useRef(null);
18605
+ const priceLines = React10.useRef(/* @__PURE__ */ new Map());
18606
+ const tradeMarkers = React10.useRef(null);
18607
+ const fitted = React10.useRef(false);
18608
+ const firstBarTime = React10.useRef(null);
18609
+ const handledFocus = React10.useRef(0);
18610
+ const handledCommand = React10.useRef(0);
18611
+ const maRefs = React10.useRef(/* @__PURE__ */ new Map());
18612
+ const restoring = React10.useRef(false);
18613
+ const initialRange = React10.useRef(visibleRange);
18614
+ const drawModeRef = React10.useRef(drawMode);
18615
+ const onDrawRef = React10.useRef(onDraw);
18616
+ const onSelectRef = React10.useRef(onSelect);
18617
+ const selectionData = React10.useRef({ candles, indicators, maLines, settings });
18618
+ const onRangeRef = React10.useRef(onVisibleRange);
18619
+ const onNeedOlderRef = React10.useRef(onNeedOlder);
18078
18620
  onNeedOlderRef.current = onNeedOlder;
18079
- const pending = React9.useRef([]);
18080
- const magnetRef = React9.useRef(magnet);
18621
+ const pending = React10.useRef([]);
18622
+ const magnetRef = React10.useRef(magnet);
18081
18623
  magnetRef.current = magnet;
18082
- const candlesRef = React9.useRef(candles);
18624
+ const candlesRef = React10.useRef(candles);
18083
18625
  candlesRef.current = candles;
18084
- const [selectedId, setSelectedId] = React9.useState(null);
18085
- const selectedRef = React9.useRef(null);
18626
+ const [selectedId, setSelectedId] = React10.useState(null);
18627
+ const [editingId, setEditingId] = React10.useState(null);
18628
+ const selectedRef = React10.useRef(null);
18086
18629
  selectedRef.current = selectedId;
18087
- const [draft, setDraft] = React9.useState(null);
18088
- const [selectedFill, setSelectedFill] = React9.useState(null);
18089
- const selectedFillRef = React9.useRef(null);
18630
+ const [group, setGroup] = React10.useState([]);
18631
+ const groupRef = React10.useRef([]);
18632
+ groupRef.current = group;
18633
+ const [hoverSnapped, setHoverSnapped] = React10.useState(false);
18634
+ const lastSnapped = React10.useRef(false);
18635
+ const [draft, setDraft] = React10.useState(null);
18636
+ const [groupDraft, setGroupDraft] = React10.useState(null);
18637
+ const [selectedFill, setSelectedFill] = React10.useState(null);
18638
+ const selectedFillRef = React10.useRef(null);
18090
18639
  selectedFillRef.current = selectedFill;
18091
- const snapRef = React9.useRef(null);
18092
- const hoveredHit = React9.useRef(null);
18640
+ const snapRef = React10.useRef(null);
18641
+ const hoveredHit = React10.useRef(null);
18093
18642
  drawModeRef.current = drawMode;
18094
18643
  onDrawRef.current = onDraw;
18095
18644
  onSelectRef.current = onSelect;
18096
- selectionData.current = { candles, indicators, maLines };
18097
- onRangeRef.current = onVisibleRange;
18098
- const [crosshair, setCrosshair] = React9.useState(null);
18099
- const [selectedTime, setSelectedTime] = React9.useState(null);
18100
- const [themeVersion, setThemeVersion] = React9.useState(0);
18101
- const [viewportVersion, setViewportVersion] = React9.useState(0);
18102
- const [pendingPoints, setPendingPoints] = React9.useState([]);
18103
- const [drawingHover, setDrawingHover] = React9.useState(null);
18645
+ selectionData.current = { candles, indicators, maLines, settings };
18646
+ onRangeRef.current = (range) => {
18647
+ onVisibleRange(range);
18648
+ if (performance.now() > externalRangeUntil.current) linkRef.current?.onRange(range);
18649
+ };
18650
+ const [crosshair, setCrosshair] = React10.useState(null);
18651
+ const [selectedTime, setSelectedTime] = React10.useState(null);
18652
+ const [themeVersion, setThemeVersion] = React10.useState(0);
18653
+ const [viewportVersion, setViewportVersion] = React10.useState(0);
18654
+ const [pendingPoints, setPendingPoints] = React10.useState([]);
18655
+ const [drawingHover, setDrawingHover] = React10.useState(null);
18104
18656
  const showVolume = indicators.has("volume");
18105
- const studies = React9.useMemo(() => chartIndicators(candles), [candles]);
18106
- const maComputed = React9.useMemo(() => maLines.map((line, index) => ({ line, color: maColor(line, index), ...maSeries(candles, line) })), [candles, maLines]);
18107
- const timeline = React9.useMemo(() => {
18657
+ const studies = React10.useMemo(() => chartIndicators(candles, settings), [candles, settings]);
18658
+ const maComputed = React10.useMemo(() => maLines.map((line, index) => ({ line, color: maColor(line, index), ...maSeries(candles, line) })), [candles, maLines]);
18659
+ const timeline = React10.useMemo(() => {
18108
18660
  const times = [...new Set(candles.map((candle) => Math.floor(candle.t / 1e3)))].sort((a, b) => a - b);
18109
18661
  const step = times.length > 1 ? times[times.length - 1] - times[times.length - 2] : 60;
18110
18662
  const toLogical = (ms) => {
@@ -18129,9 +18681,9 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18129
18681
  };
18130
18682
  return { toLogical, toTime };
18131
18683
  }, [candles]);
18132
- const timelineRef = React9.useRef(timeline);
18684
+ const timelineRef = React10.useRef(timeline);
18133
18685
  timelineRef.current = timeline;
18134
- React9.useEffect(() => {
18686
+ React10.useEffect(() => {
18135
18687
  const box = boxRef.current;
18136
18688
  if (!box) return void 0;
18137
18689
  const readTheme = () => ({
@@ -18150,7 +18702,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18150
18702
  rightPriceScale: { borderColor: theme.border },
18151
18703
  timeScale: { borderColor: theme.border, timeVisible: true, secondsVisible: false }
18152
18704
  });
18153
- const candleSeries = chart.addSeries(candlestickSeries, {
18705
+ const candleSeries = chartType === "line" ? chart.addSeries(lineSeries, { color: "#4c82d8", lineWidth: 2 }) : chartType === "area" ? chart.addSeries(areaSeries, { lineColor: "#4c82d8", topColor: "rgba(76,130,216,.28)", bottomColor: "rgba(76,130,216,0)" }) : chartType === "bars" ? chart.addSeries(barSeries, { upColor: theme.up, downColor: theme.down }) : chart.addSeries(candlestickSeries, {
18154
18706
  upColor: theme.up,
18155
18707
  downColor: theme.down,
18156
18708
  borderUpColor: theme.up,
@@ -18170,13 +18722,14 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18170
18722
  lastValueVisible: false,
18171
18723
  priceLineVisible: false
18172
18724
  });
18725
+ const basis = chart.addSeries(lineSeries, { color: "#7aa4d4", lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
18173
18726
  const sma60 = chart.addSeries(lineSeries, { color: "#d78c78", lineWidth: 2, lastValueVisible: false, priceLineVisible: false });
18174
18727
  const ema20 = chart.addSeries(lineSeries, { color: "#e2a943", lineWidth: 2, lastValueVisible: false, priceLineVisible: false });
18175
18728
  const ema50 = chart.addSeries(lineSeries, { color: "#9b86db", lineWidth: 2, lastValueVisible: false, priceLineVisible: false });
18176
18729
  const upper = chart.addSeries(lineSeries, { color: "#7aa4d4", lineWidth: 1, lineStyle: LineStyle.Dashed, lastValueVisible: false, priceLineVisible: false });
18177
18730
  const lower = chart.addSeries(lineSeries, { color: "#7aa4d4", lineWidth: 1, lineStyle: LineStyle.Dashed, lastValueVisible: false, priceLineVisible: false });
18178
18731
  chart.priceScale("dhl-vol").applyOptions({ scaleMargins: { top: 0.82, bottom: 0 } });
18179
- refs.current = { chart, candles: candleSeries, volume: volumeSeries, sma: smaSeries, sma60, ema20, ema50, upper, lower };
18732
+ refs.current = { chart, candles: candleSeries, volume: volumeSeries, sma: smaSeries, basis, extra: {}, sma60, ema20, ema50, upper, lower };
18180
18733
  tradeMarkers.current = createSeriesMarkers(candleSeries, []);
18181
18734
  fitted.current = false;
18182
18735
  const identifyHit = (hovered) => {
@@ -18189,7 +18742,13 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18189
18742
  if (hovered === r.ema50) return "ema50";
18190
18743
  if (hovered === r.upper) return "bollingerUpper";
18191
18744
  if (hovered === r.lower) return "bollingerLower";
18745
+ if (hovered === r.basis) return "bollinger20";
18746
+ if (hovered === r.extra.stochK || hovered === r.extra.stochD) return "stochastic";
18747
+ if (hovered === r.extra.adx || hovered === r.extra.plusDI || hovered === r.extra.minusDI) return "adx";
18748
+ if (hovered === r.extra.obv) return "obv";
18192
18749
  if (hovered === r.rsi) return "rsi14";
18750
+ if (hovered === r.atr) return "atr14";
18751
+ if (hovered === r.vwap) return "vwap";
18193
18752
  if (hovered === r.macd) return "macd";
18194
18753
  if (hovered === r.signal) return "macdSignal";
18195
18754
  if (hovered === r.histogram) return "macdHistogram";
@@ -18197,10 +18756,9 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18197
18756
  };
18198
18757
  chart.subscribeCrosshairMove((param) => {
18199
18758
  hoveredHit.current = identifyHit(param.hoveredSeries);
18200
- const point = param.seriesData.get(candleSeries);
18201
- if (point?.open != null && point.high != null && point.low != null && point.close != null) {
18202
- setCrosshair({ t: Number(param.time ?? 0) * 1e3, o: point.open, h: point.high, l: point.low, c: point.close, v: 0 });
18203
- } else setCrosshair(null);
18759
+ const time = typeof param.time === "number" ? param.time * 1e3 : null;
18760
+ setCrosshair(time != null ? barAt(candlesRef.current, time) : null);
18761
+ if (!syncingCursor.current) linkRef.current?.onCursor(time);
18204
18762
  });
18205
18763
  let press = null;
18206
18764
  const down = (event) => {
@@ -18217,7 +18775,10 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18217
18775
  if (press?.id === event.pointerId && Math.hypot(event.clientX - press.x, event.clientY - press.y) > 5) press.dragged = true;
18218
18776
  if (drawModeRef.current && event.target instanceof HTMLCanvasElement) {
18219
18777
  const point = pointAt2(event, pending.current.at(-1));
18220
- if (point) setDrawingHover(point);
18778
+ if (point) {
18779
+ setDrawingHover(point);
18780
+ setHoverSnapped(lastSnapped.current);
18781
+ }
18221
18782
  }
18222
18783
  };
18223
18784
  const cancel = () => {
@@ -18230,7 +18791,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18230
18791
  let price = Number(candleSeries.coordinateToPrice(y));
18231
18792
  if (logical == null || !Number.isFinite(price) || price <= 0) return null;
18232
18793
  const time = timelineRef.current.toTime(Number(logical));
18233
- const mode = magnetRef.current;
18794
+ const mode = event.ctrlKey || event.metaKey ? magnetRef.current === "off" ? "strong" : "off" : magnetRef.current;
18795
+ lastSnapped.current = false;
18234
18796
  if (useMagnet && mode !== "off") {
18235
18797
  const bar = barAt(candlesRef.current, time);
18236
18798
  if (bar) {
@@ -18239,7 +18801,10 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18239
18801
  const coordinate = candleSeries.priceToCoordinate(value);
18240
18802
  if (coordinate != null && Math.abs(coordinate - y) < best.distance) best = { price: value, distance: Math.abs(coordinate - y) };
18241
18803
  }
18242
- if (mode === "strong" || best.distance <= MAGNET_PX) price = best.price;
18804
+ if (mode === "strong" || best.distance <= MAGNET_PX) {
18805
+ price = best.price;
18806
+ lastSnapped.current = true;
18807
+ }
18243
18808
  }
18244
18809
  }
18245
18810
  if (event.shiftKey && previous) price = previous.price;
@@ -18288,6 +18853,10 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18288
18853
  } else onDrawRef.current({ kind: mode, points: points2 });
18289
18854
  return;
18290
18855
  }
18856
+ if (groupRef.current.length) {
18857
+ setGroup([]);
18858
+ return;
18859
+ }
18291
18860
  if (selectedRef.current) {
18292
18861
  setSelectedId(null);
18293
18862
  return;
@@ -18297,7 +18866,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18297
18866
  return;
18298
18867
  }
18299
18868
  if (typeof barTime !== "number") return;
18300
- const selection = selectChartCandle(selectionData.current.candles, barTime * 1e3, selectionData.current.indicators, hoveredHit.current, selectionData.current.maLines);
18869
+ const selection = selectChartCandle(selectionData.current.candles, barTime * 1e3, selectionData.current.indicators, hoveredHit.current, selectionData.current.maLines, selectionData.current.settings);
18301
18870
  if (selection) {
18302
18871
  setSelectedTime(selection.candle.t);
18303
18872
  onSelectRef.current(selection);
@@ -18311,12 +18880,21 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18311
18880
  viewportFrame = requestAnimationFrame(() => setViewportVersion((value) => value + 1));
18312
18881
  };
18313
18882
  chart.timeScale().subscribeVisibleLogicalRangeChange(refreshOverlay);
18883
+ const scaleDrag = (event) => {
18884
+ if (event.buttons) refreshOverlay();
18885
+ };
18886
+ box.addEventListener("pointermove", scaleDrag, { passive: true });
18887
+ box.addEventListener("wheel", refreshOverlay, { passive: true });
18888
+ box.addEventListener("dblclick", refreshOverlay);
18314
18889
  const sizeObserver = new ResizeObserver(refreshOverlay);
18315
18890
  sizeObserver.observe(box);
18316
18891
  chart.timeScale().subscribeVisibleTimeRangeChange((range) => {
18317
18892
  if (!fitted.current || restoring.current || !range || typeof range.from !== "number" || typeof range.to !== "number") return;
18318
18893
  if (rangeTimer) clearTimeout(rangeTimer);
18319
- rangeTimer = setTimeout(() => onRangeRef.current({ from: Number(range.from), to: Number(range.to) }), 650);
18894
+ rangeTimer = setTimeout(() => {
18895
+ const current2 = chart.timeScale().getVisibleRange();
18896
+ if (current2 && typeof current2.from === "number" && typeof current2.to === "number") onRangeRef.current({ from: current2.from, to: current2.to });
18897
+ }, 650);
18320
18898
  });
18321
18899
  chart.timeScale().subscribeVisibleLogicalRangeChange((range) => {
18322
18900
  if (!fitted.current || restoring.current || !range) return;
@@ -18351,17 +18929,22 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18351
18929
  box.removeEventListener("pointercancel", cancel, true);
18352
18930
  box.removeEventListener("click", chartClick);
18353
18931
  chart.timeScale().unsubscribeVisibleLogicalRangeChange(refreshOverlay);
18932
+ box.removeEventListener("pointermove", scaleDrag);
18933
+ box.removeEventListener("wheel", refreshOverlay);
18934
+ box.removeEventListener("dblclick", refreshOverlay);
18354
18935
  sizeObserver.disconnect();
18355
18936
  if (viewportFrame) cancelAnimationFrame(viewportFrame);
18356
18937
  if (rangeTimer) clearTimeout(rangeTimer);
18938
+ const range = chart.timeScale().getVisibleRange();
18939
+ if (range && typeof range.from === "number" && typeof range.to === "number") initialRange.current = { from: range.from, to: range.to };
18357
18940
  refs.current = null;
18358
18941
  maRefs.current.clear();
18359
18942
  priceLines.current.clear();
18360
18943
  chart.remove();
18361
18944
  tradeMarkers.current = null;
18362
18945
  };
18363
- }, []);
18364
- React9.useEffect(() => {
18946
+ }, [chartType]);
18947
+ React10.useEffect(() => {
18365
18948
  const markers = tradeEvents.filter((event) => event.kind === "order").map((event) => ({
18366
18949
  time: Math.floor(event.candleTime / 1e3),
18367
18950
  position: event.isBuy ? "belowBar" : "aboveBar",
@@ -18378,11 +18961,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18378
18961
  });
18379
18962
  markers.sort((a, b) => Number(a.time) - Number(b.time));
18380
18963
  tradeMarkers.current?.setMarkers(markers);
18381
- }, [tradeEvents, selectedTime, candles]);
18382
- React9.useEffect(() => {
18964
+ }, [tradeEvents, selectedTime, candles, chartType]);
18965
+ React10.useEffect(() => {
18383
18966
  const r = refs.current;
18384
18967
  if (!r) return;
18385
- const rows = candles;
18968
+ const rows = chartBars(candles, chartType);
18386
18969
  if (rows.length < 2) return;
18387
18970
  const seen = /* @__PURE__ */ new Set();
18388
18971
  const bars = [];
@@ -18406,9 +18989,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18406
18989
  const prevFirst = firstBarTime.current;
18407
18990
  const prepended = prevFirst != null && bars[0].time < prevFirst ? Math.max(0, bars.findIndex((bar) => bar.time >= prevFirst)) : 0;
18408
18991
  firstBarTime.current = bars[0].time;
18409
- r.candles.setData(bars);
18992
+ r.candles.setData(chartType === "line" || chartType === "area" ? bars.map((bar) => ({ time: bar.time, value: bar.close })) : bars);
18410
18993
  r.volume.setData(vols.map((v) => ({ ...v, color: v.color === FALLBACK_UP ? th.up : th.down })));
18411
18994
  r.sma.setData(chartData(studies.sma20));
18995
+ r.basis.setData(chartData(studies.basis));
18996
+ r.basis.applyOptions({ visible: indicators.has("bollinger20") });
18412
18997
  r.sma60.setData(chartData(studies.sma60));
18413
18998
  r.ema20.setData(chartData(studies.ema20));
18414
18999
  r.ema50.setData(chartData(studies.ema50));
@@ -18421,13 +19006,32 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18421
19006
  r.ema50.applyOptions({ visible: indicators.has("ema50") });
18422
19007
  r.upper.applyOptions({ visible: indicators.has("bollinger20") });
18423
19008
  r.lower.applyOptions({ visible: indicators.has("bollinger20") });
19009
+ if (indicators.has("vwap")) {
19010
+ r.vwap ??= r.chart.addSeries(lineSeries, { color: "#e89a56", lineWidth: 2, lastValueVisible: false, priceLineVisible: false });
19011
+ r.vwap.setData(chartData(studies.vwap));
19012
+ } else if (r.vwap) {
19013
+ r.chart.removeSeries(r.vwap);
19014
+ r.vwap = void 0;
19015
+ }
19016
+ if (indicators.has("atr14")) {
19017
+ if (!r.atr) {
19018
+ const pane = r.chart.addPane();
19019
+ pane.setStretchFactor(0.28);
19020
+ r.atr = pane.addSeries(lineSeries, { color: "#56bca7", lineWidth: 2, priceLineVisible: false });
19021
+ }
19022
+ r.atr.setData(chartData(studies.atr14));
19023
+ } else if (r.atr) {
19024
+ r.chart.removeSeries(r.atr);
19025
+ r.atr = void 0;
19026
+ }
18424
19027
  if (indicators.has("rsi14")) {
18425
19028
  if (!r.rsi) {
18426
19029
  const pane = r.chart.addPane();
18427
19030
  pane.setStretchFactor(0.28);
18428
19031
  r.rsi = pane.addSeries(lineSeries, { color: "#a788df", lineWidth: 2, priceLineVisible: false, lastValueVisible: true });
18429
- for (const price of [30, 70]) r.rsi.createPriceLine({ price, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false });
19032
+ r.rsiBounds = [settings.rsi.lower, settings.rsi.upper].map((price) => r.rsi.createPriceLine({ price, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false }));
18430
19033
  }
19034
+ r.rsiBounds?.forEach((line, index) => line.applyOptions({ price: index ? settings.rsi.upper : settings.rsi.lower }));
18431
19035
  r.rsi.setData(chartData(studies.rsi14));
18432
19036
  } else if (r.rsi) {
18433
19037
  r.chart.removeSeries(r.rsi);
@@ -18452,6 +19056,30 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18452
19056
  r.signal = void 0;
18453
19057
  r.histogram = void 0;
18454
19058
  }
19059
+ for (const group2 of [
19060
+ { id: "stochastic", keys: ["stochK", "stochD"], colors: ["#55a6ce", "#e2a943"] },
19061
+ { id: "adx", keys: ["adx", "plusDI", "minusDI"], colors: ["#a788df", "#2fd3a0", "#ef5f6c"] },
19062
+ { id: "obv", keys: ["obv"], colors: ["#56bca7"] }
19063
+ ]) {
19064
+ if (indicators.has(group2.id)) {
19065
+ if (!r.extra[group2.keys[0]]) {
19066
+ const pane = r.chart.addPane();
19067
+ pane.setStretchFactor(0.28);
19068
+ group2.keys.forEach((key3, index) => {
19069
+ r.extra[key3] = pane.addSeries(lineSeries, { color: group2.colors[index], lineWidth: 2, priceLineVisible: false, lastValueVisible: false });
19070
+ });
19071
+ 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
+ if (group2.id === "adx") r.extra.adx.createPriceLine({ price: 25, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false });
19073
+ }
19074
+ for (const key3 of group2.keys) r.extra[key3].setData(chartData(studies[key3]));
19075
+ if (group2.id === "stochastic") r.stochBounds?.forEach((line, index) => line.applyOptions({ price: index ? settings.stochastic.upper : settings.stochastic.lower }));
19076
+ } else for (const key3 of group2.keys) {
19077
+ if (r.extra[key3]) {
19078
+ r.chart.removeSeries(r.extra[key3]);
19079
+ delete r.extra[key3];
19080
+ }
19081
+ }
19082
+ }
18455
19083
  if (viewBefore && prepended > 0) {
18456
19084
  restoring.current = true;
18457
19085
  r.chart.timeScale().setVisibleLogicalRange({ from: viewBefore.from + prepended, to: viewBefore.to + prepended });
@@ -18509,8 +19137,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18509
19137
  restoring.current = false;
18510
19138
  }, 100);
18511
19139
  }
18512
- }, [candles, studies, indicators, showVolume, maComputed]);
18513
- React9.useEffect(() => {
19140
+ }, [candles, studies, indicators, showVolume, maComputed, chartType, settings]);
19141
+ React10.useEffect(() => {
18514
19142
  const r = refs.current;
18515
19143
  if (!r || !focus || focus.at === handledFocus.current || candles.length < 2) return;
18516
19144
  if (focus.t < candles[0].t) {
@@ -18518,7 +19146,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18518
19146
  else if (olderState === "idle") onNeedOlderRef.current();
18519
19147
  return;
18520
19148
  }
18521
- const index = candles.findIndex((candle) => candle.t >= focus.t);
19149
+ let index = candleIndex(candles, focus.t);
19150
+ if (index > 0 && candles[index].t > focus.t) index--;
18522
19151
  if (index < 0) return;
18523
19152
  handledFocus.current = focus.at;
18524
19153
  const view2 = r.chart.timeScale().getVisibleLogicalRange();
@@ -18530,19 +19159,105 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18530
19159
  }, 100);
18531
19160
  setSelectedTime(candles[index].t);
18532
19161
  }, [focus, candles, olderState]);
18533
- React9.useEffect(() => {
19162
+ React10.useEffect(() => {
18534
19163
  refs.current?.chart.priceScale("right").applyOptions({ mode: priceScale === "log" ? PriceScaleMode.Logarithmic : priceScale === "percentage" ? PriceScaleMode.Percentage : PriceScaleMode.Normal });
18535
19164
  setViewportVersion((value) => value + 1);
18536
- }, [priceScale]);
18537
- React9.useEffect(() => {
19165
+ }, [priceScale, chartType]);
19166
+ React10.useEffect(() => {
19167
+ const r = refs.current, box = boxRef.current;
19168
+ if (!command || command.at === handledCommand.current || !r || !box || candles.length < 2) return;
19169
+ const last = candles.at(-1);
19170
+ if (command.kind === "snapshot") {
19171
+ handledCommand.current = command.at;
19172
+ void chartImage(r.chart, box, caption).then((blob) => downloadBlob(blob, `${caption.replace(/[^a-zA-Z0-9_-]/g, "_")}.png`)).catch(() => notify(tr("\u56FE\u8868\u56FE\u7247\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5", "Chart image export failed; please retry")));
19173
+ return;
19174
+ }
19175
+ if (command.kind === "live") {
19176
+ r.chart.priceScale("right").applyOptions({ autoScale: true });
19177
+ r.chart.timeScale().scrollToPosition(0, false);
19178
+ } else if (command.kind === "fit") r.chart.timeScale().fitContent();
19179
+ else {
19180
+ const target = command.kind === "date" ? command.time : last.t - command.duration;
19181
+ if (target > Date.now()) {
19182
+ handledCommand.current = command.at;
19183
+ notify(tr("\u8BF7\u9009\u62E9\u5DF2\u53D1\u751F\u7684\u65E5\u671F", "Choose a past date"));
19184
+ return;
19185
+ }
19186
+ if (target < candles[0].t && olderState !== "done" && olderState !== "error") {
19187
+ if (olderState === "idle") onNeedOlderRef.current();
19188
+ return;
19189
+ }
19190
+ if (target < candles[0].t) notify(olderState === "error" ? tr("\u5386\u53F2\u52A0\u8F7D\u5931\u8D25\uFF0C\u4EC5\u663E\u793A\u5DF2\u52A0\u8F7D\u7684\u6570\u636E\uFF1B\u53EF\u91CD\u65B0\u5B9A\u4F4D\u91CD\u8BD5", "History load failed; showing loaded data. Select the date again to retry.") : tr("\u5DF2\u5230\u53EF\u7528\u5386\u53F2\u8FB9\u754C\uFF0C\u663E\u793A\u6700\u65E9\u53EF\u7528\u7684\u6570\u636E", "History limit reached; showing the earliest available data"));
19191
+ const index = candleIndex(candles, target);
19192
+ if (command.kind === "date") {
19193
+ const view2 = r.chart.timeScale().getVisibleLogicalRange();
19194
+ const half = Math.max(15, view2 ? (view2.to - view2.from) / 2 : 40);
19195
+ r.chart.timeScale().setVisibleLogicalRange({ from: index - half, to: index + half });
19196
+ } else r.chart.timeScale().setVisibleLogicalRange({ from: index, to: candles.length - 1 + 3 });
19197
+ }
19198
+ handledCommand.current = command.at;
19199
+ requestAnimationFrame(() => requestAnimationFrame(() => {
19200
+ if (refs.current !== r || handledCommand.current !== command.at) return;
19201
+ const range = r.chart.timeScale().getVisibleRange();
19202
+ if (range && typeof range.from === "number" && typeof range.to === "number") onRangeRef.current({ from: range.from, to: range.to });
19203
+ }));
19204
+ setViewportVersion((value) => value + 1);
19205
+ }, [command, candles, olderState, caption]);
19206
+ React10.useEffect(() => {
19207
+ const r = refs.current, cursor = link?.cursor;
19208
+ if (!r || !cursor || cursor.source === link?.id) return;
19209
+ syncingCursor.current = true;
19210
+ try {
19211
+ if (cursor.time == null || !candles.length || cursor.time < candles[0].t || cursor.time >= candles[candles.length - 1].t + (candles.length > 1 ? candles[1].t - candles[0].t : 6e4)) {
19212
+ r.chart.clearCrosshairPosition();
19213
+ setCrosshair(null);
19214
+ return;
19215
+ }
19216
+ let lo = 0, hi = candles.length - 1;
19217
+ while (lo < hi) {
19218
+ const mid = Math.ceil((lo + hi) / 2);
19219
+ if (candles[mid].t <= cursor.time) lo = mid;
19220
+ else hi = mid - 1;
19221
+ }
19222
+ const candle = candles[lo];
19223
+ r.chart.setCrosshairPosition(candle.c, Math.floor(candle.t / 1e3), r.candles);
19224
+ setCrosshair(candle);
19225
+ } finally {
19226
+ syncingCursor.current = false;
19227
+ }
19228
+ }, [link?.cursor, candles, chartType]);
19229
+ const handledLinkRange = React10.useRef("");
19230
+ React10.useEffect(() => {
19231
+ const r = refs.current, range = link?.range;
19232
+ if (!r || !range || range.source === link?.id || candles.length < 2 || handledLinkRange.current === `${range.at}:${chartType}`) return;
19233
+ if (range.from * 1e3 < candles[0].t && olderState === "idle") {
19234
+ onNeedOlderRef.current();
19235
+ return;
19236
+ }
19237
+ handledLinkRange.current = `${range.at}:${chartType}`;
19238
+ externalRangeUntil.current = performance.now() + 900;
19239
+ restoring.current = true;
19240
+ const line = timelineRef.current;
19241
+ r.chart.timeScale().setVisibleLogicalRange({ from: line.toLogical(range.from * 1e3), to: line.toLogical(range.to * 1e3) });
19242
+ const actual = r.chart.timeScale().getVisibleRange();
19243
+ if (actual && typeof actual.from === "number" && typeof actual.to === "number") onRangeRef.current({ from: actual.from, to: actual.to });
19244
+ const timer = setTimeout(() => {
19245
+ restoring.current = false;
19246
+ }, 120);
19247
+ return () => {
19248
+ clearTimeout(timer);
19249
+ restoring.current = false;
19250
+ };
19251
+ }, [link?.range, candles, olderState, chartType]);
19252
+ React10.useEffect(() => {
18538
19253
  pending.current = [];
18539
19254
  setPendingPoints([]);
18540
19255
  setDrawingHover(null);
18541
19256
  }, [drawMode]);
18542
- React9.useEffect(() => {
19257
+ React10.useEffect(() => {
18543
19258
  if (resetToken > 0) refs.current?.chart.timeScale().fitContent();
18544
19259
  }, [resetToken]);
18545
- React9.useEffect(() => {
19260
+ React10.useEffect(() => {
18546
19261
  const r = refs.current;
18547
19262
  const box = boxRef.current;
18548
19263
  if (!r || !box) return;
@@ -18561,20 +19276,24 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18561
19276
  title: mark.label
18562
19277
  }));
18563
19278
  }
18564
- }, [marks, themeVersion]);
19279
+ }, [marks, themeVersion, chartType]);
18565
19280
  const warming = candles.length < 2;
18566
- const markById = React9.useMemo(() => new Map(marks.map((mark) => [mark.id, mark])), [marks]);
18567
- const markByIdRef = React9.useRef(markById);
19281
+ const markById = React10.useMemo(() => new Map(marks.map((mark) => [mark.id, mark])), [marks]);
19282
+ const markByIdRef = React10.useRef(markById);
18568
19283
  markByIdRef.current = markById;
18569
- const lockedRef = React9.useRef(drawingsLocked);
19284
+ const lockedRef = React10.useRef(drawingsLocked);
18570
19285
  lockedRef.current = drawingsLocked;
18571
- const onEditRef = React9.useRef(onEditMark);
19286
+ const onEditRef = React10.useRef(onEditMark);
18572
19287
  onEditRef.current = onEditMark;
18573
- const onRemoveRef = React9.useRef(onRemoveMark);
19288
+ const onRemoveRef = React10.useRef(onRemoveMark);
18574
19289
  onRemoveRef.current = onRemoveMark;
18575
- const shapes = React9.useMemo(() => marks.filter((mark) => !mark.hidden).map((mark) => ({ mark, points: markPoints(mark) })), [marks]);
19290
+ const onRemoveMarksRef = React10.useRef(onRemoveMarks);
19291
+ onRemoveMarksRef.current = onRemoveMarks;
19292
+ const onEditEachRef = React10.useRef(onEditEach);
19293
+ onEditEachRef.current = onEditEach;
19294
+ const shapes = React10.useMemo(() => marks.filter((mark) => !mark.hidden).map((mark) => ({ mark, points: markPoints(mark) })), [marks]);
18576
19295
  const lastTime = candles.at(-1)?.t ?? Date.now();
18577
- const view = React9.useMemo(() => {
19296
+ const view = React10.useMemo(() => {
18578
19297
  const chartApi = refs.current, box = boxRef.current;
18579
19298
  if (!chartApi || !box) return null;
18580
19299
  return {
@@ -18586,26 +19305,60 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18586
19305
  width: Math.max(0, box.clientWidth - chartApi.chart.priceScale("right").width()),
18587
19306
  height: chartApi.chart.panes()[0]?.getHeight() ?? box.clientHeight,
18588
19307
  bars: (from, to) => timeline.toLogical(to) - timeline.toLogical(from),
18589
- closes: (from, to) => candles.filter((candle) => candle.t >= from && candle.t <= to).map((candle) => ({ time: candle.t, price: candle.c }))
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
18590
19313
  };
18591
19314
  }, [viewportVersion, timeline, themeVersion, expanded, indicators]);
18592
- React9.useEffect(() => {
19315
+ React10.useEffect(() => {
18593
19316
  if (selectedId && !markById.has(selectedId)) setSelectedId(null);
18594
19317
  }, [markById, selectedId]);
18595
- React9.useEffect(() => {
19318
+ React10.useEffect(() => {
18596
19319
  if (drawMode) setSelectedId(null);
18597
19320
  }, [drawMode]);
18598
- React9.useEffect(() => {
19321
+ React10.useEffect(() => {
18599
19322
  if (!draft) return;
18600
19323
  const mark = markById.get(draft.id);
18601
19324
  if (mark?.kind === "level") priceLines.current.get(draft.id)?.applyOptions({ price: draft.points[0].price });
18602
19325
  }, [draft, markById]);
18603
- React9.useEffect(() => {
19326
+ React10.useEffect(() => {
19327
+ setGroup((current2) => {
19328
+ const next = current2.filter((id) => markById.has(id));
19329
+ return next.length === current2.length ? current2 : next;
19330
+ });
19331
+ }, [markById]);
19332
+ React10.useEffect(() => {
19333
+ if (drawMode) setGroup([]);
19334
+ }, [drawMode]);
19335
+ React10.useEffect(() => {
19336
+ if (group.length < 2) return void 0;
19337
+ const onKey = (event) => {
19338
+ const element = event.target;
19339
+ if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
19340
+ if (event.key === "Escape") {
19341
+ event.preventDefault();
19342
+ setGroup([]);
19343
+ return;
19344
+ }
19345
+ if (event.key !== "Delete" && event.key !== "Backspace") return;
19346
+ if (lockedRef.current) return;
19347
+ event.preventDefault();
19348
+ const ids = groupRef.current.filter((id) => !markByIdRef.current.get(id)?.locked);
19349
+ if (ids.length) onRemoveMarksRef.current?.(ids);
19350
+ setGroup([]);
19351
+ };
19352
+ window.addEventListener("keydown", onKey, true);
19353
+ return () => window.removeEventListener("keydown", onKey, true);
19354
+ }, [group.length]);
19355
+ React10.useEffect(() => {
18604
19356
  if (!selectedId) return void 0;
18605
19357
  const onKey = (event) => {
18606
19358
  const element = event.target;
18607
19359
  if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
18608
19360
  if (event.key === "Escape") {
19361
+ event.preventDefault();
18609
19362
  setSelectedId(null);
18610
19363
  return;
18611
19364
  }
@@ -18616,10 +19369,12 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18616
19369
  onRemoveRef.current(selectedId);
18617
19370
  setSelectedId(null);
18618
19371
  };
18619
- window.addEventListener("keydown", onKey);
18620
- return () => window.removeEventListener("keydown", onKey);
19372
+ window.addEventListener("keydown", onKey, true);
19373
+ return () => window.removeEventListener("keydown", onKey, true);
18621
19374
  }, [selectedId]);
18622
- const [brushPoints, setBrushPoints] = React9.useState([]);
19375
+ const abortGesture = React10.useRef(null);
19376
+ React10.useEffect(() => () => abortGesture.current?.(), []);
19377
+ const [brushPoints, setBrushPoints] = React10.useState([]);
18623
19378
  const beginBrush = (event) => {
18624
19379
  const snap = snapRef.current;
18625
19380
  const first = snap?.(event.nativeEvent, void 0, false);
@@ -18638,41 +19393,56 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18638
19393
  cancelAnimationFrame(frame);
18639
19394
  frame = requestAnimationFrame(() => setBrushPoints([...collected]));
18640
19395
  };
18641
- const up = () => {
19396
+ const detach = () => {
18642
19397
  window.removeEventListener("pointermove", move);
18643
19398
  window.removeEventListener("pointerup", up);
18644
- window.removeEventListener("pointercancel", up);
19399
+ window.removeEventListener("pointercancel", cancel);
19400
+ window.removeEventListener("keydown", onKey, true);
18645
19401
  cancelAnimationFrame(frame);
18646
19402
  setBrushPoints([]);
19403
+ abortGesture.current = null;
19404
+ };
19405
+ const cancel = () => detach();
19406
+ const onKey = (ev) => {
19407
+ if (ev.key === "Escape") {
19408
+ ev.stopPropagation();
19409
+ detach();
19410
+ }
19411
+ };
19412
+ const up = () => {
19413
+ detach();
18647
19414
  if (collected.length < 2) return;
18648
19415
  const stride = Math.ceil(collected.length / BRUSH_MAX);
18649
19416
  const points2 = collected.filter((_, index) => index % stride === 0 || index === collected.length - 1).slice(-BRUSH_MAX);
18650
19417
  onDrawRef.current({ kind: "brush", points: points2 });
18651
19418
  };
19419
+ abortGesture.current?.();
19420
+ abortGesture.current = cancel;
18652
19421
  window.addEventListener("pointermove", move);
18653
19422
  window.addEventListener("pointerup", up);
18654
- window.addEventListener("pointercancel", up);
19423
+ window.addEventListener("pointercancel", cancel);
19424
+ window.addEventListener("keydown", onKey, true);
18655
19425
  };
18656
- const cloneOf = React9.useCallback((mark) => {
19426
+ const cloneOf = React10.useCallback((mark) => {
18657
19427
  const line = timelineRef.current;
18658
19428
  const shifted = markPoints(mark).map((point) => ({ time: line.toTime(line.toLogical(point.time) + 5), price: point.price }));
18659
19429
  const style = Object.fromEntries(Object.entries({ color: mark.color, lineWidth: mark.lineWidth, lineStyle: mark.lineStyle }).filter(([, value]) => value != null));
18660
19430
  return { kind: mark.kind, geometry: geometryPatch(mark.kind, shifted), style, label: mark.label };
18661
19431
  }, []);
18662
- const clipboard = React9.useRef(null);
18663
- const onCloneRef = React9.useRef(onCloneMark);
19432
+ const clipboard = React10.useRef(null);
19433
+ const onCloneRef = React10.useRef(onCloneMark);
18664
19434
  onCloneRef.current = onCloneMark;
18665
- React9.useEffect(() => {
19435
+ React10.useEffect(() => {
18666
19436
  const onKey = (event) => {
18667
19437
  if (!(event.metaKey || event.ctrlKey) || event.altKey) return;
18668
19438
  const element = event.target;
18669
19439
  if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
18670
- const key2 = event.key.toLowerCase();
18671
- if (key2 === "c" && selectedRef.current) {
19440
+ const key3 = event.key.toLowerCase();
19441
+ if (key3 === "c" && selectedRef.current) {
18672
19442
  clipboard.current = markByIdRef.current.get(selectedRef.current) ?? null;
18673
19443
  if (clipboard.current) event.preventDefault();
18674
19444
  }
18675
- if (key2 === "v" && clipboard.current) {
19445
+ if (key3 === "v" && clipboard.current) {
18676
19446
  event.preventDefault();
18677
19447
  const copy = cloneOf(clipboard.current);
18678
19448
  onCloneRef.current(copy);
@@ -18682,8 +19452,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18682
19452
  window.addEventListener("keydown", onKey);
18683
19453
  return () => window.removeEventListener("keydown", onKey);
18684
19454
  }, [cloneOf]);
18685
- const handledSelect = React9.useRef(0);
18686
- React9.useEffect(() => {
19455
+ const handledSelect = React10.useRef(0);
19456
+ React10.useEffect(() => {
18687
19457
  const r = refs.current;
18688
19458
  if (!selectRequest || selectRequest.at === handledSelect.current || !r) return;
18689
19459
  const mark = markById.get(selectRequest.id);
@@ -18702,7 +19472,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18702
19472
  }, 100);
18703
19473
  }
18704
19474
  }, [selectRequest, markById, timeline]);
18705
- const beginDrag = React9.useCallback((event) => {
19475
+ const beginDrag = React10.useCallback((event) => {
18706
19476
  if (drawModeRef.current === "brush" && event.button === 0) {
18707
19477
  beginBrush(event);
18708
19478
  return;
@@ -18722,6 +19492,81 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18722
19492
  if (!id || !mark) return;
18723
19493
  event.preventDefault();
18724
19494
  event.stopPropagation();
19495
+ if (event.shiftKey || event.ctrlKey || event.metaKey) {
19496
+ const base = groupRef.current.length ? groupRef.current : selectedRef.current ? [selectedRef.current] : [];
19497
+ const next = base.includes(id) ? base.filter((item) => item !== id) : [...base, id];
19498
+ if (next.length >= 2) {
19499
+ setGroup(next);
19500
+ setSelectedId(null);
19501
+ } else {
19502
+ setGroup([]);
19503
+ setSelectedId(next[0] ?? null);
19504
+ }
19505
+ return;
19506
+ }
19507
+ const anchorHit = target.getAttribute("data-anchor") != null;
19508
+ if (groupRef.current.includes(id) && groupRef.current.length >= 2 && !anchorHit) {
19509
+ const snapGroup = snapRef.current;
19510
+ if (lockedRef.current || !snapGroup) return;
19511
+ const startGroup = snapGroup(event.nativeEvent, void 0, false);
19512
+ if (!startGroup) return;
19513
+ const line2 = timelineRef.current, startLogical2 = line2.toLogical(startGroup.time);
19514
+ const originals = new Map(groupRef.current.map((member) => markByIdRef.current.get(member)).filter((item) => Boolean(item) && !item.locked).map((item) => [item.id, item]));
19515
+ const origin2 = { x: event.clientX, y: event.clientY };
19516
+ let moved2 = false, frame2 = 0, latest2 = null;
19517
+ const compute2 = (ev) => {
19518
+ const point = snapGroup(ev, void 0, false);
19519
+ if (!point) return;
19520
+ const shift = Math.round(line2.toLogical(point.time) - startLogical2), delta = point.price - startGroup.price;
19521
+ const next = /* @__PURE__ */ new Map();
19522
+ for (const [memberId, member] of originals) {
19523
+ const moved3 = markPoints(member).map((item) => ({ time: line2.toTime(line2.toLogical(item.time) + shift), price: item.price + delta }));
19524
+ if (moved3.some((item) => item.price <= 0)) return;
19525
+ next.set(memberId, moved3);
19526
+ }
19527
+ latest2 = next;
19528
+ setGroupDraft(next);
19529
+ };
19530
+ const move2 = (ev) => {
19531
+ if (!moved2 && Math.hypot(ev.clientX - origin2.x, ev.clientY - origin2.y) < 3) return;
19532
+ moved2 = true;
19533
+ cancelAnimationFrame(frame2);
19534
+ frame2 = requestAnimationFrame(() => compute2(ev));
19535
+ };
19536
+ const detach2 = () => {
19537
+ window.removeEventListener("pointermove", move2);
19538
+ window.removeEventListener("pointerup", up2);
19539
+ window.removeEventListener("pointercancel", cancel2);
19540
+ window.removeEventListener("keydown", onKey2, true);
19541
+ cancelAnimationFrame(frame2);
19542
+ abortGesture.current = null;
19543
+ };
19544
+ const cancel2 = () => {
19545
+ detach2();
19546
+ setGroupDraft(null);
19547
+ };
19548
+ const onKey2 = (ev) => {
19549
+ if (ev.key === "Escape" && moved2) {
19550
+ ev.stopPropagation();
19551
+ cancel2();
19552
+ }
19553
+ };
19554
+ const up2 = (ev) => {
19555
+ detach2();
19556
+ if (moved2) compute2(ev);
19557
+ const final = latest2;
19558
+ if (moved2 && final) onEditEachRef.current?.([...final].map(([memberId, points2]) => [memberId, geometryPatch(originals.get(memberId).kind, points2)]));
19559
+ setGroupDraft(null);
19560
+ };
19561
+ abortGesture.current?.();
19562
+ abortGesture.current = cancel2;
19563
+ window.addEventListener("pointermove", move2);
19564
+ window.addEventListener("pointerup", up2);
19565
+ window.addEventListener("pointercancel", cancel2);
19566
+ window.addEventListener("keydown", onKey2, true);
19567
+ return;
19568
+ }
19569
+ setGroup([]);
18725
19570
  setSelectedId(id);
18726
19571
  const snap = snapRef.current;
18727
19572
  if (mark.locked || lockedRef.current || !snap) return;
@@ -18767,25 +19612,44 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18767
19612
  cancelAnimationFrame(frame);
18768
19613
  frame = requestAnimationFrame(() => compute(ev));
18769
19614
  };
18770
- const up = () => {
19615
+ const detach = () => {
18771
19616
  window.removeEventListener("pointermove", move);
18772
19617
  window.removeEventListener("pointerup", up);
18773
- window.removeEventListener("pointercancel", up);
19618
+ window.removeEventListener("pointercancel", cancel);
19619
+ window.removeEventListener("keydown", onKey, true);
18774
19620
  cancelAnimationFrame(frame);
19621
+ abortGesture.current = null;
19622
+ };
19623
+ const cancel = () => {
19624
+ detach();
19625
+ if (mark.kind === "level") priceLines.current.get(id)?.applyOptions({ price: original[0].price });
19626
+ setDraft(null);
19627
+ };
19628
+ const onKey = (ev) => {
19629
+ if (ev.key === "Escape" && moved) {
19630
+ ev.stopPropagation();
19631
+ cancel();
19632
+ }
19633
+ };
19634
+ const up = () => {
19635
+ detach();
18775
19636
  if (moved && last) compute(last);
18776
19637
  if (moved && latest) onEditRef.current(id, geometryPatch(mark.kind, latest));
18777
19638
  setDraft(null);
18778
19639
  };
19640
+ abortGesture.current?.();
19641
+ abortGesture.current = cancel;
18779
19642
  window.addEventListener("pointermove", move);
18780
19643
  window.addEventListener("pointerup", up);
18781
- window.addEventListener("pointercancel", up);
19644
+ window.addEventListener("pointercancel", cancel);
19645
+ window.addEventListener("keydown", onKey, true);
18782
19646
  }, []);
18783
- const fills = React9.useMemo(() => tradeEvents.filter((event) => event.kind === "fill"), [tradeEvents]);
19647
+ const fills = React10.useMemo(() => tradeEvents.filter((event) => event.kind === "fill"), [tradeEvents]);
18784
19648
  const fillCard = selectedFill ? fills.find((event) => event.id === selectedFill) ?? null : null;
18785
- React9.useEffect(() => {
19649
+ React10.useEffect(() => {
18786
19650
  if (selectedFill && !fills.some((event) => event.id === selectedFill)) setSelectedFill(null);
18787
19651
  }, [fills, selectedFill]);
18788
- React9.useEffect(() => {
19652
+ React10.useEffect(() => {
18789
19653
  if (!selectedFill) return void 0;
18790
19654
  const onKey = (event) => {
18791
19655
  if (event.key === "Escape") setSelectedFill(null);
@@ -18797,11 +19661,17 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18797
19661
  const previewPoints = drawMode && drawingHover ? [...pendingPoints, drawingHover] : pendingPoints;
18798
19662
  const roleClass = (mark) => mark.source === "user" ? "role-user" : `role-${mark.role}`;
18799
19663
  const selectedMark = selectedId ? markById.get(selectedId) ?? null : null;
18800
- return /* @__PURE__ */ React9.createElement("div", { className: "dhl-chartbox", ref: boxRef, style: { height: expanded ? "calc(100dvh - 160px)" : CHART_HEIGHT + Number(indicators.has("rsi14")) * 125 + Number(indicators.has("macd")) * 125 } }, view ? /* @__PURE__ */ React9.createElement("svg", { className: "dhl-chart-drawing-overlay" + (drawMode ? " drawing" : "") + (drawMode === "brush" ? " brushing" : ""), "aria-hidden": "true", onPointerDown: beginDrag }, /* @__PURE__ */ React9.createElement("defs", null, /* @__PURE__ */ React9.createElement("clipPath", { id: "dhl-plot-clip" }, /* @__PURE__ */ React9.createElement("rect", { x: "0", y: "0", width: view.width, height: view.height })), /* @__PURE__ */ React9.createElement("marker", { id: "dhl-arrow", viewBox: "0 0 10 10", refX: "8", refY: "5", markerWidth: "7", markerHeight: "7", orient: "auto-start-reverse" }, /* @__PURE__ */ React9.createElement("path", { d: "M0 0 10 5 0 10z", fill: "currentColor" }))), /* @__PURE__ */ React9.createElement("g", { clipPath: "url(#dhl-plot-clip)" }, shapes.map(({ mark, points: points2 }) => {
18801
- const live = draft?.id === mark.id ? draft.points : points2;
19664
+ const editingMark = editingId ? markById.get(editingId) ?? null : null;
19665
+ const inspected = crosshair ?? candles.at(-1);
19666
+ const inspectedTime = inspected ? Math.floor(inspected.t / 1e3) : null;
19667
+ const studyAt = (key3) => studies[key3].find((point) => point.time === inspectedTime)?.value;
19668
+ const inspectedStudies = studyDefinitions(settings).filter(([id]) => indicators.has(id)).map(([, key3, label]) => [label, studyAt(key3)]);
19669
+ const paneCount = ["rsi14", "macd", "atr14", "stochastic", "adx", "obv"].filter((id) => indicators.has(id)).length;
19670
+ return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement("div", { className: "dhl-chartbox", ref: boxRef, style: { height: expanded && !compact ? void 0 : (compact ? 310 : CHART_HEIGHT) + paneCount * (compact ? 65 : 125) } }, view ? /* @__PURE__ */ React10.createElement("svg", { className: "dhl-chart-drawing-overlay" + (drawMode ? " drawing" : "") + (drawMode === "brush" ? " brushing" : ""), "aria-hidden": "true", onPointerDown: beginDrag }, /* @__PURE__ */ React10.createElement("defs", null, /* @__PURE__ */ React10.createElement("clipPath", { id: clipId.current }, /* @__PURE__ */ React10.createElement("rect", { x: "0", y: "0", width: view.width, height: view.height })), /* @__PURE__ */ React10.createElement("marker", { id: `${clipId.current}-arrow`, viewBox: "0 0 10 10", refX: "8", refY: "5", markerWidth: "7", markerHeight: "7", orient: "auto-start-reverse" }, /* @__PURE__ */ React10.createElement("path", { d: "M0 0 10 5 0 10z", fill: "currentColor" }))), /* @__PURE__ */ React10.createElement("g", { clipPath: `url(#${clipId.current})` }, shapes.map(({ mark, points: points2 }) => {
19671
+ const live = draft?.id === mark.id ? draft.points : groupDraft?.get(mark.id) ?? points2;
18802
19672
  const shown = mark.kind === "level" ? [{ time: lastTime, price: live[0].price }] : live;
18803
- const selected = selectedId === mark.id;
18804
- return /* @__PURE__ */ React9.createElement(
19673
+ const selected = selectedId === mark.id || group.includes(mark.id);
19674
+ return /* @__PURE__ */ React10.createElement(
18805
19675
  DrawingShape,
18806
19676
  {
18807
19677
  key: mark.id,
@@ -18817,15 +19687,21 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18817
19687
  color: mark.color,
18818
19688
  lineWidth: mark.lineWidth,
18819
19689
  lineStyle: mark.lineStyle,
18820
- badges: [...mark.alert ? ["alert"] : [], ...mark.shared ? ["shared"] : []]
19690
+ alert: mark.alert,
19691
+ shared: mark.shared,
19692
+ rows: mark.rows,
19693
+ valueArea: mark.valueArea
18821
19694
  }
18822
19695
  );
18823
- }), /* @__PURE__ */ React9.createElement("g", { className: "dhl-fills" }, fills.map((event) => {
19696
+ }), /* @__PURE__ */ React10.createElement("g", { className: "dhl-fills" }, fills.map((event) => {
18824
19697
  const point = view.project(event.time, event.px);
18825
19698
  if (!point) return null;
18826
19699
  const short = event.reason.length > 18 ? event.reason.slice(0, 17) + "\u2026" : event.reason;
18827
- return /* @__PURE__ */ React9.createElement("g", { key: event.id, className: `dhl-fill ${event.isBuy ? "buy" : "sell"}${event.reason ? " has-reason" : ""}${selectedFill === event.id ? " selected" : ""}`, "data-fill-id": event.id }, event.reason ? /* @__PURE__ */ React9.createElement("circle", { className: "dhl-fill-ring", cx: point.x, cy: point.y, r: "9" }) : null, /* @__PURE__ */ React9.createElement("circle", { className: "dhl-fill-dot", cx: point.x, cy: point.y, r: "5" }), showReasons && short ? /* @__PURE__ */ React9.createElement("text", { className: "dhl-fill-reason", x: point.x + 12, y: point.y + 4 }, short) : null);
18828
- })), brushPoints.length > 1 ? /* @__PURE__ */ React9.createElement(DrawingShape, { kind: "brush", points: brushPoints, view, className: "dhl-drawing dhl-drawing-preview role-user" }) : null, drawMode && drawMode !== "brush" && previewPoints.length ? /* @__PURE__ */ React9.createElement(DrawingShape, { kind: drawMode, points: completePreview(drawMode, previewPoints), view, className: "dhl-drawing dhl-drawing-preview role-user", anchors: true }) : null)) : null, fillCard && fillAt && view ? /* @__PURE__ */ React9.createElement(
19700
+ return /* @__PURE__ */ React10.createElement("g", { key: event.id, className: `dhl-fill ${event.isBuy ? "buy" : "sell"}${event.reason ? " has-reason" : ""}${selectedFill === event.id ? " selected" : ""}`, "data-fill-id": event.id }, event.reason ? /* @__PURE__ */ React10.createElement("circle", { className: "dhl-fill-ring", cx: point.x, cy: point.y, r: "9" }) : null, /* @__PURE__ */ React10.createElement("circle", { className: "dhl-fill-dot", cx: point.x, cy: point.y, r: "5" }), showReasons && short ? /* @__PURE__ */ React10.createElement("text", { className: "dhl-fill-reason", x: point.x + 12, y: point.y + 4 }, short) : null);
19701
+ })), brushPoints.length > 1 ? /* @__PURE__ */ React10.createElement(DrawingShape, { kind: "brush", points: brushPoints, view, className: "dhl-drawing dhl-drawing-preview role-user" }) : null, drawMode && drawingHover && hoverSnapped ? (() => {
19702
+ const at = view.project(drawingHover.time, drawingHover.price);
19703
+ return at ? /* @__PURE__ */ React10.createElement("circle", { className: "dhl-snap-ring", cx: at.x, cy: at.y, r: "6" }) : null;
19704
+ })() : null, drawMode && drawMode !== "brush" && previewPoints.length ? /* @__PURE__ */ React10.createElement(DrawingShape, { kind: drawMode, points: completePreview(drawMode, previewPoints), view, className: "dhl-drawing dhl-drawing-preview role-user", anchors: true }) : null)) : null, fillCard && fillAt && view ? /* @__PURE__ */ React10.createElement(
18829
19705
  "div",
18830
19706
  {
18831
19707
  className: "dhl-fill-card",
@@ -18835,19 +19711,29 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18835
19711
  onPointerDown: (event) => event.stopPropagation(),
18836
19712
  onClick: (event) => event.stopPropagation()
18837
19713
  },
18838
- /* @__PURE__ */ React9.createElement("header", null, /* @__PURE__ */ React9.createElement("b", { className: fillCard.isBuy ? "dhl-up" : "dhl-down" }, fillCard.isBuy ? tr("\u4E70\u5165", "Buy") : tr("\u5356\u51FA", "Sell"), " ", Number.isFinite(fillCard.sz) ? fillCard.sz : "\u2014", " ", fillCard.coin, " @ ", fmtPrice(fillCard.px)), /* @__PURE__ */ React9.createElement("button", { type: "button", "aria-label": tr("\u5173\u95ED", "Close"), onClick: () => setSelectedFill(null) }, /* @__PURE__ */ React9.createElement(UiIcon, { name: "close", size: 14 }))),
18839
- /* @__PURE__ */ React9.createElement("time", null, new Date(fillCard.time).toLocaleString(tr("zh-CN", "en-US"), { hour12: false })),
18840
- /* @__PURE__ */ React9.createElement("strong", null, tr("AI \u4EA4\u6613\u51B3\u7B56\u539F\u56E0", "AI decision rationale")),
18841
- fillCard.reason ? /* @__PURE__ */ React9.createElement("p", null, fillCard.reason) : /* @__PURE__ */ React9.createElement("p", { className: "muted" }, tr("\u8FD9\u7B14\u6210\u4EA4\u6CA1\u6709\u8BB0\u5F55\u539F\u56E0\uFF08\u65E7\u8BA2\u5355\uFF0C\u6216\u5728\u5176\u4ED6\u5BA2\u6237\u7AEF\u4E0B\u5355\uFF09\u3002", "No rationale recorded (older order or placed elsewhere).")),
18842
- /* @__PURE__ */ React9.createElement("footer", null, /* @__PURE__ */ React9.createElement("button", { type: "button", className: "dhl-fill-analyze", onClick: () => {
19714
+ /* @__PURE__ */ React10.createElement("header", null, /* @__PURE__ */ React10.createElement("b", { className: fillCard.isBuy ? "dhl-up" : "dhl-down" }, fillCard.isBuy ? tr("\u4E70\u5165", "Buy") : tr("\u5356\u51FA", "Sell"), " ", Number.isFinite(fillCard.sz) ? fillCard.sz : "\u2014", " ", fillCard.coin, " @ ", fmtPrice(fillCard.px)), /* @__PURE__ */ React10.createElement("button", { type: "button", "aria-label": tr("\u5173\u95ED", "Close"), onClick: () => setSelectedFill(null) }, /* @__PURE__ */ React10.createElement(UiIcon, { name: "close", size: 14 }))),
19715
+ /* @__PURE__ */ React10.createElement("time", null, new Date(fillCard.time).toLocaleString(tr("zh-CN", "en-US"), { hour12: false })),
19716
+ /* @__PURE__ */ React10.createElement("strong", null, tr("AI \u4EA4\u6613\u51B3\u7B56\u539F\u56E0", "AI decision rationale")),
19717
+ fillCard.reason ? /* @__PURE__ */ React10.createElement("p", null, fillCard.reason) : /* @__PURE__ */ React10.createElement("p", { className: "muted" }, tr("\u8FD9\u7B14\u6210\u4EA4\u6CA1\u6709\u8BB0\u5F55\u539F\u56E0\uFF08\u65E7\u8BA2\u5355\uFF0C\u6216\u5728\u5176\u4ED6\u5BA2\u6237\u7AEF\u4E0B\u5355\uFF09\u3002", "No rationale recorded (older order or placed elsewhere).")),
19718
+ /* @__PURE__ */ React10.createElement("footer", null, /* @__PURE__ */ React10.createElement("button", { type: "button", className: "dhl-fill-analyze", onClick: () => {
18843
19719
  onAnalyzeFill(fillCard);
18844
19720
  setSelectedFill(null);
18845
19721
  } }, fillCard.sessionId ? tr("\u56DE\u5230\u51B3\u7B56\u4F1A\u8BDD\u5206\u6790", "Analyze in its session") : tr("\u5728\u5F53\u524D\u4F1A\u8BDD\u5206\u6790", "Analyze in this session")))
18846
- ) : null, selectedMark && !drawMode ? /* @__PURE__ */ React9.createElement(
19722
+ ) : null, group.length >= 2 && !drawMode && !readOnly ? (() => {
19723
+ const members = group.map((id) => markById.get(id)).filter((mark) => Boolean(mark));
19724
+ const editable = members.filter((mark) => !mark.locked).map((mark) => mark.id);
19725
+ const allHidden = members.every((mark) => mark.hidden), allLocked = members.every((mark) => mark.locked);
19726
+ const stop = (event) => event.stopPropagation();
19727
+ return /* @__PURE__ */ React10.createElement("div", { className: "dhl-draw-toolbar", role: "toolbar", "aria-label": tr("\u6279\u91CF\u7F16\u8F91\u753B\u7EBF", "Edit selected drawings"), onPointerDown: stop, onClick: stop }, /* @__PURE__ */ React10.createElement("span", { className: "dhl-draw-toolbar-name" }, tr(`\u5DF2\u9009 ${members.length} \u6761\u753B\u7EBF`, `${members.length} drawings selected`)), /* @__PURE__ */ React10.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u989C\u8272", "Color") }, DRAW_COLORS.map((color) => /* @__PURE__ */ React10.createElement("button", { type: "button", key: color, className: "dhl-swatch", style: { background: color }, disabled: drawingsLocked || editable.length === 0, "aria-label": `${tr("\u5168\u90E8\u6539\u4E3A", "Recolor all")} ${color}`, onClick: () => onEditMarks?.(editable, { color }) }))), /* @__PURE__ */ React10.createElement("span", { className: "dhl-draw-toolbar-group" }, /* @__PURE__ */ React10.createElement("button", { type: "button", "aria-label": allHidden ? tr("\u5168\u90E8\u663E\u793A", "Show all") : tr("\u5168\u90E8\u9690\u85CF", "Hide all"), title: allHidden ? tr("\u5168\u90E8\u663E\u793A", "Show all") : tr("\u5168\u90E8\u9690\u85CF", "Hide all"), disabled: drawingsLocked, onClick: () => onEditMarks?.(group, { hidden: !allHidden }) }, /* @__PURE__ */ React10.createElement(UiIcon, { name: allHidden ? "eye" : "eyeOff", size: 15 })), /* @__PURE__ */ React10.createElement("button", { type: "button", className: allLocked ? "on" : "", "aria-pressed": allLocked, "aria-label": allLocked ? tr("\u5168\u90E8\u89E3\u9501", "Unlock all") : tr("\u5168\u90E8\u9501\u5B9A", "Lock all"), title: allLocked ? tr("\u5168\u90E8\u89E3\u9501", "Unlock all") : tr("\u5168\u90E8\u9501\u5B9A", "Lock all"), disabled: drawingsLocked, onClick: () => onEditMarks?.(group, { locked: !allLocked }) }, /* @__PURE__ */ React10.createElement(UiIcon, { name: allLocked ? "lock" : "unlock", size: 15 })), /* @__PURE__ */ React10.createElement("button", { type: "button", "aria-label": tr("\u5220\u9664\u6240\u9009\u753B\u7EBF", "Delete selected drawings"), title: tr("\u5220\u9664\uFF08Delete\uFF09", "Delete (Del)"), disabled: drawingsLocked || editable.length === 0, onClick: () => {
19728
+ onRemoveMarks?.(editable);
19729
+ setGroup([]);
19730
+ } }, /* @__PURE__ */ React10.createElement(UiIcon, { name: "trash", size: 15 })), /* @__PURE__ */ React10.createElement("button", { type: "button", "aria-label": tr("\u53D6\u6D88\u9009\u62E9", "Deselect"), title: tr("\u53D6\u6D88\u9009\u62E9\uFF08Esc\uFF09", "Deselect (Esc)"), onClick: () => setGroup([]) }, /* @__PURE__ */ React10.createElement(UiIcon, { name: "close", size: 14 }))));
19731
+ })() : null, selectedMark && !drawMode && !readOnly ? /* @__PURE__ */ React10.createElement(
18847
19732
  DrawToolbar,
18848
19733
  {
18849
19734
  mark: selectedMark,
18850
19735
  globallyLocked: drawingsLocked,
19736
+ onCoordinates: () => setEditingId(selectedMark.id),
18851
19737
  onEdit: (patch) => onEditMark(selectedMark.id, patch),
18852
19738
  onClone: () => onCloneMark(cloneOf(selectedMark)),
18853
19739
  onRemove: () => {
@@ -18856,21 +19742,36 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
18856
19742
  },
18857
19743
  onClose: () => setSelectedId(null)
18858
19744
  }
18859
- ) : null, !warming && /* @__PURE__ */ React9.createElement("div", { className: "dhl-chart-ohlc", "aria-live": "off" }, /* @__PURE__ */ React9.createElement("span", null, crosshair ? new Date(crosshair.t).toLocaleString(tr("zh-CN", "en-US"), { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false }) : tr("\u6700\u65B0 K \u7EBF", "Latest candle")), ["o", "h", "l", "c"].map((key2) => /* @__PURE__ */ React9.createElement("span", { key: key2 }, key2.toUpperCase(), " ", /* @__PURE__ */ React9.createElement("b", { className: crosshair && crosshair.c < crosshair.o ? "dhl-down" : "dhl-up" }, fmtPrice((crosshair ?? candles.at(-1))?.[key2]))))), !warming && olderState === "loading" ? /* @__PURE__ */ React9.createElement("div", { className: "dhl-chart-older", role: "status" }, tr("\u6B63\u5728\u52A0\u8F7D\u66F4\u65E9\u7684 K \u7EBF\u2026", "Loading earlier candles\u2026")) : null, !warming && olderState === "error" ? /* @__PURE__ */ React9.createElement("div", { className: "dhl-chart-older", role: "status" }, tr("\u66F4\u65E9\u7684 K \u7EBF\u52A0\u8F7D\u5931\u8D25\uFF0C\u7EE7\u7EED\u5411\u5DE6\u62D6\u52A8\u53EF\u91CD\u8BD5", "Could not load earlier candles; drag left again to retry")) : null, !warming && maComputed.length > 0 ? /* @__PURE__ */ React9.createElement("div", { className: "dhl-chart-ma-legend", "aria-label": tr("\u5747\u7EBF\u6570\u503C", "Moving average values") }, maComputed.map((item) => {
19745
+ ) : null, editingMark ? /* @__PURE__ */ React10.createElement(DrawingEditor, { key: editingMark.id, mark: editingMark, onApply: (patch) => {
19746
+ if (!drawingsLocked && !editingMark.locked) onEditMark(editingMark.id, patch);
19747
+ }, onClose: () => setEditingId(null) }) : null, !warming && /* @__PURE__ */ React10.createElement("div", { className: "dhl-chart-ohlc", "data-time": inspected?.t, "aria-live": "off" }, /* @__PURE__ */ React10.createElement("span", null, crosshair ? new Date(crosshair.t).toLocaleString(tr("zh-CN", "en-US"), { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false }) : tr("\u6700\u65B0 K \u7EBF", "Latest candle")), ["o", "h", "l", "c"].map((key3) => /* @__PURE__ */ React10.createElement("span", { key: key3 }, key3.toUpperCase(), " ", /* @__PURE__ */ React10.createElement("b", { className: inspected && inspected.c < inspected.o ? "dhl-down" : "dhl-up" }, fmtPrice(inspected?.[key3])))), /* @__PURE__ */ React10.createElement("span", null, tr("\u91CF", "Vol"), " ", /* @__PURE__ */ React10.createElement("b", null, inspected?.v.toLocaleString(void 0, { maximumFractionDigits: 4 }) ?? "\u2014")), inspected ? /* @__PURE__ */ React10.createElement("span", { className: inspected.c >= inspected.o ? "dhl-up" : "dhl-down" }, ((inspected.c / inspected.o - 1) * 100).toFixed(2), "%") : null, chartType === "heikinAshi" ? /* @__PURE__ */ React10.createElement("span", null, tr("\u5E73\u5747K\u7EBF \xB7 \u6570\u503C\u4E3A\u539F\u59CB\u884C\u60C5", "Heikin Ashi \xB7 values show exchange OHLC")) : null), !warming && olderState === "loading" ? /* @__PURE__ */ React10.createElement("div", { className: "dhl-chart-older", role: "status" }, tr("\u6B63\u5728\u52A0\u8F7D\u66F4\u65E9\u7684 K \u7EBF\u2026", "Loading earlier candles\u2026")) : null, !warming && olderState === "error" ? /* @__PURE__ */ React10.createElement("div", { className: "dhl-chart-older", role: "status" }, tr("\u66F4\u65E9\u7684 K \u7EBF\u52A0\u8F7D\u5931\u8D25\uFF0C\u7EE7\u7EED\u5411\u5DE6\u62D6\u52A8\u53EF\u91CD\u8BD5", "Could not load earlier candles; drag left again to retry")) : null, !warming && maComputed.length > 0 ? /* @__PURE__ */ React10.createElement("div", { className: "dhl-chart-ma-legend", "aria-label": tr("\u5747\u7EBF\u6570\u503C", "Moving average values") }, maComputed.map((item) => {
18860
19748
  const index = crosshair ? candles.findIndex((candle) => Math.floor(candle.t / 1e3) === Math.floor(crosshair.t / 1e3)) : candles.length - 1;
18861
19749
  const at = (series) => series && index >= 0 ? series[index] : null;
18862
- return /* @__PURE__ */ React9.createElement("span", { key: item.line.id, style: { color: item.color } }, maLabel(item.line), " ", /* @__PURE__ */ React9.createElement("b", null, fmtPrice(at(item.values))), item.smoothed ? /* @__PURE__ */ React9.createElement(React9.Fragment, null, " / ", /* @__PURE__ */ React9.createElement("b", null, fmtPrice(at(item.smoothed)))) : null);
18863
- })) : null, marks.length > 0 ? /* @__PURE__ */ React9.createElement("div", { className: "dhl-chart-annotations", "aria-label": tr("\u4F1A\u8BDD\u56FE\u8868\u6807\u6CE8", "Session chart annotations") }, marks.slice(-4).map((mark) => /* @__PURE__ */ React9.createElement("span", { key: mark.id, className: `role-${mark.role}`, title: `${mark.label} \xB7 ${mark.kind === "level" ? fmtPrice(mark.price) : tr("\u8D8B\u52BF\u7EBF", "Trend line")}` }, mark.label)), marks.length > 4 ? /* @__PURE__ */ React9.createElement("span", null, "+", marks.length - 4) : null) : null, crosshair && tradeEvents.some((event) => event.candleTime === crosshair.t) ? /* @__PURE__ */ React9.createElement("div", { className: "dhl-chart-trade-detail", "aria-live": "off" }, tradeEvents.filter((event) => event.candleTime === crosshair.t).slice(0, 4).map((event) => /* @__PURE__ */ React9.createElement("div", { key: event.id }, /* @__PURE__ */ React9.createElement("strong", null, event.kind === "order" ? tr("\u59D4\u6258", "Order") : tr("\u6210\u4EA4", "Fill"), " \xB7 ", event.isBuy ? tr("\u4E70\u5165", "Buy") : tr("\u5356\u51FA", "Sell")), /* @__PURE__ */ React9.createElement("span", null, Number.isFinite(event.sz) ? event.sz : "\u2014", " @ ", fmtPrice(event.px), " \xB7 ", new Date(event.time).toLocaleString()), event.reason ? /* @__PURE__ */ React9.createElement("span", { title: event.reason }, tr("\u539F\u56E0", "Reason"), "\uFF1A", event.reason) : null))) : null, indicators.has("rsi14") || indicators.has("macd") ? /* @__PURE__ */ React9.createElement("div", { className: "dhl-chart-study-legend", "aria-label": tr("\u526F\u56FE\u6307\u6807\u6570\u503C", "Study values") }, indicators.has("rsi14") ? /* @__PURE__ */ React9.createElement("span", null, "RSI 14 ", /* @__PURE__ */ React9.createElement("b", null, studies.rsi14.at(-1)?.value.toFixed(1) ?? "\u2014")) : null, indicators.has("macd") ? /* @__PURE__ */ React9.createElement("span", null, "MACD ", /* @__PURE__ */ React9.createElement("b", null, studies.macd.at(-1)?.value.toFixed(4) ?? "\u2014"), " / ", studies.signal.at(-1)?.value.toFixed(4) ?? "\u2014") : null) : null, warming ? /* @__PURE__ */ React9.createElement("div", { className: "dhl-empty", style: { height: CHART_HEIGHT } }, t("chart.loading", "K \u7EBF\u52A0\u8F7D\u4E2D\u2026")) : null);
19750
+ return /* @__PURE__ */ React10.createElement("span", { key: item.line.id, style: { color: item.color } }, maLabel(item.line), " ", /* @__PURE__ */ React10.createElement("b", null, fmtPrice(at(item.values))), item.smoothed ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, " / ", /* @__PURE__ */ React10.createElement("b", null, fmtPrice(at(item.smoothed)))) : null);
19751
+ })) : null, crosshair && tradeEvents.some((event) => event.candleTime === crosshair.t) ? /* @__PURE__ */ React10.createElement("div", { className: "dhl-chart-trade-detail", "aria-live": "off" }, tradeEvents.filter((event) => event.candleTime === crosshair.t).slice(0, 4).map((event) => /* @__PURE__ */ React10.createElement("div", { key: event.id }, /* @__PURE__ */ React10.createElement("strong", null, event.kind === "order" ? tr("\u59D4\u6258", "Order") : tr("\u6210\u4EA4", "Fill"), " \xB7 ", event.isBuy ? tr("\u4E70\u5165", "Buy") : tr("\u5356\u51FA", "Sell")), /* @__PURE__ */ React10.createElement("span", null, Number.isFinite(event.sz) ? event.sz : "\u2014", " @ ", fmtPrice(event.px), " \xB7 ", new Date(event.time).toLocaleString()), event.reason ? /* @__PURE__ */ React10.createElement("span", { title: event.reason }, tr("\u539F\u56E0", "Reason"), "\uFF1A", event.reason) : null))) : null, inspectedStudies.length ? /* @__PURE__ */ React10.createElement("div", { className: "dhl-chart-study-legend", "aria-label": tr("\u6307\u6807\u70B9\u4F4D\u6570\u503C", "Study values at crosshair") }, inspectedStudies.map(([label, value]) => /* @__PURE__ */ React10.createElement("span", { key: label, "data-study": label }, label, " ", /* @__PURE__ */ React10.createElement("b", null, value == null ? "\u2014" : Number(value.toPrecision(7)).toLocaleString(void 0, { maximumFractionDigits: 7 }))))) : null, warming ? /* @__PURE__ */ React10.createElement("div", { className: "dhl-empty", style: { height: CHART_HEIGHT } }, t("chart.loading", "K \u7EBF\u52A0\u8F7D\u4E2D\u2026")) : null), showDataWindow && inspected ? /* @__PURE__ */ React10.createElement("section", { className: "dhl-data-window", "aria-label": tr("\u56FE\u8868\u6570\u636E\u7A97\u53E3", "Chart data window"), "data-time": inspected.t }, /* @__PURE__ */ React10.createElement("header", null, /* @__PURE__ */ React10.createElement("strong", null, tr("\u6570\u636E\u7A97\u53E3", "Data window")), /* @__PURE__ */ React10.createElement("span", null, new Date(inspected.t).toLocaleString(), " \xB7 ", crosshair ? tr("\u5149\u6807 K \u7EBF", "Cursor bar") : tr("\u6700\u65B0 K \u7EBF\uFF08\u53EF\u80FD\u672A\u6536\u76D8\uFF09", "Latest bar (may be forming)"))), /* @__PURE__ */ React10.createElement("dl", null, [
19752
+ ["Open", inspected.o],
19753
+ ["High", inspected.h],
19754
+ ["Low", inspected.l],
19755
+ ["Close", inspected.c],
19756
+ ["Volume", inspected.v],
19757
+ ...inspectedStudies,
19758
+ ...maComputed.flatMap((item) => {
19759
+ const index = candles.findIndex((bar) => bar.t === inspected.t);
19760
+ return [[maLabel(item.line), item.values[index]], ...item.smoothed ? [[maLabel(item.line) + " smooth", item.smoothed[index]]] : []];
19761
+ })
19762
+ ].map(([label, value]) => /* @__PURE__ */ React10.createElement("div", { key: String(label) }, /* @__PURE__ */ React10.createElement("dt", null, label), /* @__PURE__ */ React10.createElement("dd", null, value == null ? "\u2014" : Number(value).toLocaleString(void 0, { maximumSignificantDigits: 10 }))))), indicators.has("obv") ? /* @__PURE__ */ React10.createElement("small", null, tr("OBV \u4EE5\u5DF2\u52A0\u8F7D\u9996\u6839\u4E3A\u96F6\u70B9\uFF1B\u8865\u53D6\u5386\u53F2\u540E\u57FA\u51C6\u4F1A\u53D8\u5316\u3002", "OBV starts at zero on the first loaded bar; loading history changes its baseline.")) : null) : null);
18864
19763
  }
18865
19764
 
18866
19765
  // src/client/drawing-history.ts
18867
- var React10 = __toESM(require("react"), 1);
19766
+ var React11 = __toESM(require("react"), 1);
18868
19767
  var HISTORY_LIMIT = 100;
18869
19768
  var GEOMETRY = ["price", "fromTime", "fromPrice", "toTime", "toPrice", "points"];
18870
- var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden"];
19769
+ var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea"];
19770
+ var FLAGS = /* @__PURE__ */ new Set(["locked", "hidden", "shared", "alert"]);
18871
19771
  function recreateRequest(mark) {
18872
19772
  const out = { kind: mark.kind, label: mark.label, role: mark.role, coin: mark.coin, interval: mark.interval };
18873
- for (const key2 of [...GEOMETRY, ...STYLE]) if (mark[key2] != null) out[key2] = mark[key2];
19773
+ for (const key3 of [...GEOMETRY, ...STYLE]) if (mark[key3] != null) out[key3] = mark[key3];
19774
+ if (mark.source === "agent") out.restoreSource = "agent";
18874
19775
  return out;
18875
19776
  }
18876
19777
  function remap(ops, from, to) {
@@ -18881,138 +19782,176 @@ function remap(ops, from, to) {
18881
19782
  } else if (op.mark.id === from) op.mark = { ...op.mark, id: to };
18882
19783
  }
18883
19784
  }
19785
+ var beforeOf = (mark, patch) => Object.fromEntries(Object.keys(patch).map((key3) => [key3, mark[key3] ?? (FLAGS.has(key3) ? false : null)]));
18884
19786
  function useDrawingHistory(scope) {
18885
- const undoStack = React10.useRef([]);
18886
- const redoStack = React10.useRef([]);
18887
- const [, setVersion] = React10.useState(0);
19787
+ const undoStack = React11.useRef([]);
19788
+ const redoStack = React11.useRef([]);
19789
+ const scopeRef = React11.useRef(scope);
19790
+ const queue = React11.useRef(Promise.resolve());
19791
+ const [, setVersion] = React11.useState(0);
18888
19792
  const bump = () => setVersion((value) => value + 1);
18889
- React10.useEffect(() => {
19793
+ React11.useEffect(() => {
19794
+ scopeRef.current = scope;
18890
19795
  undoStack.current = [];
18891
19796
  redoStack.current = [];
18892
19797
  bump();
18893
19798
  }, [scope]);
18894
- const session = () => store.get().activeSessionId;
19799
+ const ctxNow = () => {
19800
+ const { activeSessionId, activeCoin, interval } = store.get();
19801
+ return activeSessionId ? { sessionId: activeSessionId, coin: activeCoin, interval, scope: scopeRef.current } : null;
19802
+ };
19803
+ const serial = (task, fallback) => {
19804
+ const ctx = ctxNow();
19805
+ if (!ctx) {
19806
+ notify(tr("\u8BF7\u5148\u9009\u62E9\u4EA4\u6613\u4F1A\u8BDD", "Choose a trading session first"));
19807
+ return Promise.resolve(fallback);
19808
+ }
19809
+ const run = queue.current.catch(() => {
19810
+ }).then(() => ctx.scope === scopeRef.current ? task(ctx) : fallback);
19811
+ queue.current = run;
19812
+ return run;
19813
+ };
19814
+ const onScreen = (ctx) => store.get().activeSessionId === ctx.sessionId && store.get().activeCoin === ctx.coin;
18895
19815
  const fail = (what, error) => {
18896
19816
  notify(`${what}\uFF1A${String(error?.message ?? error)}`);
18897
19817
  void loadChartMarks();
18898
19818
  };
18899
- const apiCreate = async (request) => {
18900
- const sessionId = session();
18901
- if (!sessionId) {
18902
- notify(tr("\u8BF7\u5148\u9009\u62E9\u4EA4\u6613\u4F1A\u8BDD", "Choose a trading session first"));
18903
- return null;
18904
- }
18905
- const { activeCoin, interval } = store.get();
19819
+ const apiCreate = async (ctx, request) => {
18906
19820
  try {
18907
- const saved = await hyperCall("drawChartMark", { coin: activeCoin, interval, role: "neutral", ...request, sessionId });
18908
- store.patch((current3) => ({ chartMarks: [...current3.chartMarks.filter((item) => item.id !== saved.id), saved] }));
19821
+ const saved = await hyperCall("drawChartMark", { interval: ctx.interval, role: "neutral", ...request, coin: request.coin ?? ctx.coin, sessionId: ctx.sessionId });
19822
+ if (onScreen(ctx) && saved.coin === ctx.coin) store.patch((current3) => ({ chartMarks: [...current3.chartMarks.filter((item) => item.id !== saved.id), saved] }));
18909
19823
  return saved;
18910
19824
  } catch (error) {
18911
19825
  fail(tr("\u753B\u7EBF\u5931\u8D25", "Could not draw"), error);
18912
19826
  return null;
18913
19827
  }
18914
19828
  };
18915
- const apiDelete = async (id) => {
18916
- const sessionId = session();
18917
- if (!sessionId) return;
18918
- store.patch((current3) => ({ chartMarks: current3.chartMarks.filter((item) => item.id !== id) }));
19829
+ const apiDelete = async (ctx, id) => {
19830
+ if (onScreen(ctx)) store.patch((current3) => ({ chartMarks: current3.chartMarks.filter((item) => item.id !== id) }));
18919
19831
  try {
18920
- await hyperCall("eraseChartMark", { sessionId, id });
19832
+ await hyperCall("eraseChartMark", { sessionId: ctx.sessionId, id });
19833
+ return true;
18921
19834
  } catch (error) {
18922
19835
  fail(tr("\u5220\u9664\u753B\u7EBF\u5931\u8D25", "Could not remove drawing"), error);
19836
+ return false;
18923
19837
  }
18924
19838
  };
18925
- const apiEdit = async (id, patch) => {
18926
- const sessionId = session();
18927
- if (!sessionId) return;
18928
- store.patch((current3) => ({ chartMarks: current3.chartMarks.map((item) => {
19839
+ const apiEdit = async (ctx, id, patch) => {
19840
+ if (onScreen(ctx)) store.patch((current3) => ({ chartMarks: current3.chartMarks.map((item) => {
18929
19841
  if (item.id !== id) return item;
18930
19842
  const next = { ...item, ...patch };
18931
- for (const [key2, value] of Object.entries(patch)) if (value === null) delete next[key2];
19843
+ for (const [key3, value] of Object.entries(patch)) if (value === null) delete next[key3];
18932
19844
  return next;
18933
19845
  }) }));
18934
19846
  try {
18935
- await hyperCall("updateChartMark", { sessionId, id, patch });
19847
+ await hyperCall("updateChartMark", { sessionId: ctx.sessionId, id, patch });
19848
+ return true;
18936
19849
  } catch (error) {
18937
19850
  fail(tr("\u4FEE\u6539\u753B\u7EBF\u5931\u8D25", "Could not edit drawing"), error);
19851
+ return false;
18938
19852
  }
18939
19853
  };
18940
- const record = (op) => {
19854
+ const record = (op, ctx) => {
19855
+ if (ctx.scope !== scopeRef.current) return;
18941
19856
  undoStack.current.push(op);
18942
19857
  if (undoStack.current.length > HISTORY_LIMIT) undoStack.current.shift();
18943
19858
  redoStack.current = [];
18944
19859
  bump();
18945
19860
  };
18946
19861
  const current2 = (id) => store.get().chartMarks.find((item) => item.id === id);
18947
- const apply2 = async (op, forward) => {
19862
+ const apply2 = async (ctx, op, forward) => {
18948
19863
  if (op.type === "batch") {
18949
- for (const inner of forward ? op.ops : [...op.ops].reverse()) await apply2(inner, forward);
18950
- return;
18951
- }
18952
- if (op.type === "edit") {
18953
- await apiEdit(op.id, forward ? op.after : op.before);
18954
- return;
19864
+ let ok = true;
19865
+ for (const inner of forward ? op.ops : [...op.ops].reverse()) ok = await apply2(ctx, inner, forward) && ok;
19866
+ return ok;
18955
19867
  }
19868
+ if (op.type === "edit") return apiEdit(ctx, op.id, forward ? op.after : op.before);
18956
19869
  const shouldExist = op.type === "create" === forward;
18957
19870
  if (shouldExist) {
18958
- const saved = await apiCreate(recreateRequest(op.mark));
18959
- if (saved) {
18960
- const old = op.mark.id;
18961
- remap(undoStack.current, old, saved.id);
18962
- remap(redoStack.current, old, saved.id);
18963
- op.mark = { ...op.mark, id: saved.id };
18964
- }
18965
- } else await apiDelete(op.mark.id);
19871
+ const saved = await apiCreate(ctx, recreateRequest(op.mark));
19872
+ if (!saved) return false;
19873
+ const old = op.mark.id;
19874
+ remap(undoStack.current, old, saved.id);
19875
+ remap(redoStack.current, old, saved.id);
19876
+ op.mark = { ...op.mark, id: saved.id };
19877
+ return true;
19878
+ }
19879
+ return apiDelete(ctx, op.mark.id);
18966
19880
  };
18967
19881
  return {
18968
- async create(request) {
18969
- const saved = await apiCreate(request);
18970
- if (saved) record({ type: "create", mark: saved });
19882
+ create: (request) => serial(async (ctx) => {
19883
+ const saved = await apiCreate(ctx, request);
19884
+ if (saved) record({ type: "create", mark: saved }, ctx);
18971
19885
  return saved;
18972
- },
18973
- async edit(id, patch) {
19886
+ }, null),
19887
+ edit: (id, patch) => serial(async (ctx) => {
18974
19888
  const mark = current2(id);
18975
19889
  if (!mark) return;
18976
- const before = Object.fromEntries(Object.keys(patch).map((key2) => [key2, mark[key2] ?? null]));
18977
- record({ type: "edit", id, before, after: patch });
18978
- await apiEdit(id, patch);
18979
- },
18980
- async remove(id) {
19890
+ const before = beforeOf(mark, patch);
19891
+ if (await apiEdit(ctx, id, patch)) record({ type: "edit", id, before, after: patch }, ctx);
19892
+ }, void 0),
19893
+ remove: (id) => serial(async (ctx) => {
18981
19894
  const mark = current2(id);
18982
19895
  if (!mark) return;
18983
- record({ type: "delete", mark });
18984
- await apiDelete(id);
18985
- },
18986
- async removeMany(ids) {
18987
- const marks = ids.map(current2).filter((mark) => Boolean(mark));
18988
- if (!marks.length) return;
18989
- record({ type: "batch", ops: marks.map((mark) => ({ type: "delete", mark })) });
18990
- for (const mark of marks) await apiDelete(mark.id);
18991
- },
18992
- async undo() {
19896
+ if (await apiDelete(ctx, id)) record({ type: "delete", mark }, ctx);
19897
+ }, void 0),
19898
+ // Batches record only the members the host accepted, so an undo never re-creates something that still exists.
19899
+ removeMany: (ids) => serial(async (ctx) => {
19900
+ const done = [];
19901
+ for (const mark of ids.map(current2).filter((item) => Boolean(item))) if (await apiDelete(ctx, mark.id)) done.push({ type: "delete", mark });
19902
+ if (done.length) record({ type: "batch", ops: done }, ctx);
19903
+ }, void 0),
19904
+ editMany: (ids, patch) => serial(async (ctx) => {
19905
+ const done = [];
19906
+ for (const mark of ids.map(current2).filter((item) => Boolean(item))) {
19907
+ const before = beforeOf(mark, patch);
19908
+ if (await apiEdit(ctx, mark.id, patch)) done.push({ type: "edit", id: mark.id, before, after: patch });
19909
+ }
19910
+ if (done.length) record({ type: "batch", ops: done }, ctx);
19911
+ }, void 0),
19912
+ editEach: (patches) => serial(async (ctx) => {
19913
+ const done = [];
19914
+ for (const [id, patch] of patches) {
19915
+ const mark = current2(id);
19916
+ if (!mark) continue;
19917
+ const before = beforeOf(mark, patch);
19918
+ if (await apiEdit(ctx, id, patch)) done.push({ type: "edit", id, before, after: patch });
19919
+ }
19920
+ if (done.length) record({ type: "batch", ops: done }, ctx);
19921
+ }, void 0),
19922
+ // A failed undo/redo goes back where it was, so the stacks keep describing the host.
19923
+ undo: () => serial(async (ctx) => {
18993
19924
  const op = undoStack.current.pop();
18994
19925
  if (!op) return;
18995
19926
  redoStack.current.push(op);
18996
19927
  bump();
18997
- await apply2(op, false);
18998
- },
18999
- async redo() {
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) => {
19000
19935
  const op = redoStack.current.pop();
19001
19936
  if (!op) return;
19002
19937
  undoStack.current.push(op);
19003
19938
  bump();
19004
- await apply2(op, true);
19005
- },
19939
+ if (!await apply2(ctx, op, true)) {
19940
+ undoStack.current.pop();
19941
+ redoStack.current.push(op);
19942
+ bump();
19943
+ }
19944
+ }, void 0),
19006
19945
  canUndo: undoStack.current.length > 0,
19007
19946
  canRedo: redoStack.current.length > 0
19008
19947
  };
19009
19948
  }
19010
19949
 
19011
19950
  // src/client/components/drawing-list.tsx
19012
- var React11 = __toESM(require("react"), 1);
19951
+ var React12 = __toESM(require("react"), 1);
19013
19952
  var OPEN_KEY = "dsh-hyperliquid.draw.listOpen";
19014
19953
  function DrawingList({ marks, locked, onSelect, onEdit, onRemove }) {
19015
- const [open, setOpen] = React11.useState(() => {
19954
+ const [open, setOpen] = React12.useState(() => {
19016
19955
  try {
19017
19956
  return localStorage.getItem(OPEN_KEY) !== "0";
19018
19957
  } catch {
@@ -19027,31 +19966,31 @@ function DrawingList({ marks, locked, onSelect, onEdit, onRemove }) {
19027
19966
  return !value;
19028
19967
  });
19029
19968
  const hidden = marks.filter((mark) => mark.hidden).length;
19030
- return /* @__PURE__ */ React11.createElement("section", { className: "dhl-drawing-list", "aria-label": tr("\u753B\u7EBF\u5217\u8868", "Drawings") }, /* @__PURE__ */ React11.createElement("button", { type: "button", className: "dhl-drawing-list-head", "aria-expanded": open, onClick: toggle }, /* @__PURE__ */ React11.createElement("span", null, tr("\u753B\u7EBF", "Drawings"), " ", marks.length, hidden ? tr(` \xB7 \u9690\u85CF ${hidden}`, ` \xB7 ${hidden} hidden`) : ""), /* @__PURE__ */ React11.createElement(UiIcon, { name: open ? "chevronDown" : "chevronRight", size: 14 })), open ? /* @__PURE__ */ React11.createElement("ul", null, marks.map((mark) => {
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) => {
19031
19970
  const tool = toolOf(mark.kind);
19032
- return /* @__PURE__ */ React11.createElement("li", { key: mark.id, className: mark.hidden ? "is-hidden" : "" }, /* @__PURE__ */ React11.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__ */ React11.createElement("span", { style: { color: mark.color } }, /* @__PURE__ */ React11.createElement(ToolIcon, { kind: mark.kind, size: 14 })), /* @__PURE__ */ React11.createElement("b", null, mark.label), /* @__PURE__ */ React11.createElement("small", null, mark.alert ? /* @__PURE__ */ React11.createElement("span", { className: "dhl-list-flag", title: tr("\u4EF7\u683C\u63D0\u9192\u5DF2\u5F00\u542F", "Price alert armed") }, /* @__PURE__ */ React11.createElement(UiIcon, { name: "bell", size: 12 })) : null, mark.shared ? /* @__PURE__ */ React11.createElement("span", { className: "dhl-list-flag", title: tr("\u5DF2\u540C\u6B65\u5230\u6240\u6709\u4F1A\u8BDD", "Synced to all sessions") }, /* @__PURE__ */ React11.createElement(UiIcon, { name: "sync", size: 12 })) : null, tr(tool.zh, tool.en), " \xB7 ", mark.interval, mark.source === "agent" ? " \xB7 agent" : "")), /* @__PURE__ */ React11.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__ */ React11.createElement(UiIcon, { name: mark.hidden ? "eyeOff" : "eye", size: 14 })), /* @__PURE__ */ React11.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__ */ React11.createElement(UiIcon, { name: mark.locked ? "lock" : "unlock", size: 14 })), /* @__PURE__ */ React11.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__ */ React11.createElement(UiIcon, { name: "trash", size: 14 })));
19971
+ 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 })));
19033
19972
  })) : null);
19034
19973
  }
19035
19974
 
19036
19975
  // src/client/components/sticker-picker.tsx
19037
- var React12 = __toESM(require("react"), 1);
19976
+ var React13 = __toESM(require("react"), 1);
19038
19977
  var TABS = [["icons", "\u56FE\u6807", "Icons"], ["trading", "\u4EA4\u6613", "Trading"], ["faces", "\u8868\u60C5", "Faces"], ["symbols", "\u7B26\u53F7", "Symbols"]];
19039
19978
  function StickerPicker({ onPick }) {
19040
- const [tab, setTab] = React12.useState("icons");
19041
- const [custom, setCustom] = React12.useState("");
19979
+ const [tab, setTab] = React13.useState("icons");
19980
+ const [custom, setCustom] = React13.useState("");
19042
19981
  const value = custom.trim();
19043
19982
  const valid = isSingleEmoji(value);
19044
- return /* @__PURE__ */ React12.createElement("div", { className: "dhl-sticker-picker" }, /* @__PURE__ */ React12.createElement("form", { className: "dhl-sticker-custom", onSubmit: (event) => {
19983
+ return /* @__PURE__ */ React13.createElement("div", { className: "dhl-sticker-picker" }, /* @__PURE__ */ React13.createElement("form", { className: "dhl-sticker-custom", onSubmit: (event) => {
19045
19984
  event.preventDefault();
19046
19985
  if (valid) onPick(`emoji:${value}`);
19047
- } }, /* @__PURE__ */ React12.createElement("input", { value: custom, onChange: (event) => setCustom(event.target.value), placeholder: tr("\u8F93\u5165\u6216\u7C98\u8D34\u4EFB\u610F emoji\uFF0C\u4F8B\u5982 \u{1F984}", "Type or paste any emoji, e.g. \u{1F984}"), "aria-label": tr("\u81EA\u5B9A\u4E49 emoji \u8D34\u7EB8", "Custom emoji sticker"), maxLength: 16 }), /* @__PURE__ */ React12.createElement("button", { type: "submit", disabled: !valid }, tr("\u653E\u7F6E", "Place"))), /* @__PURE__ */ React12.createElement("div", { className: "dhl-sticker-tabs", role: "tablist" }, TABS.map(([id, zh, en]) => /* @__PURE__ */ React12.createElement("button", { type: "button", role: "tab", key: id, "aria-selected": tab === id, className: tab === id ? "on" : "", onClick: () => setTab(id) }, tr(zh, en)))), tab === "icons" ? /* @__PURE__ */ React12.createElement("div", { className: "dhl-icon-grid", role: "group", "aria-label": tr("\u56FE\u6807", "Icons") }, ICON_LIST.map((name) => /* @__PURE__ */ React12.createElement("button", { type: "button", key: name, "aria-label": tr(ICONS[name][0], ICONS[name][1]), title: tr(ICONS[name][0], ICONS[name][1]), onClick: () => onPick(name) }, /* @__PURE__ */ React12.createElement("svg", { viewBox: "0 0 24 24", width: "22", height: "22", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React12.createElement("path", { d: ICONS[name][2] })), /* @__PURE__ */ React12.createElement("span", null, tr(ICONS[name][0], ICONS[name][1]))))) : /* @__PURE__ */ React12.createElement("div", { className: "dhl-sticker-grid", role: "group", "aria-label": tr("\u8D34\u7EB8", "Stickers") }, STICKERS[tab].map((emoji) => /* @__PURE__ */ React12.createElement("button", { type: "button", key: emoji, "aria-label": `${tr("\u8D34\u7EB8", "Sticker")} ${emoji}`, onClick: () => onPick(`emoji:${emoji}`) }, emoji))));
19986
+ } }, /* @__PURE__ */ React13.createElement("input", { value: custom, onChange: (event) => setCustom(event.target.value), placeholder: tr("\u8F93\u5165\u6216\u7C98\u8D34\u4EFB\u610F emoji\uFF0C\u4F8B\u5982 \u{1F984}", "Type or paste any emoji, e.g. \u{1F984}"), "aria-label": tr("\u81EA\u5B9A\u4E49 emoji \u8D34\u7EB8", "Custom emoji sticker"), maxLength: 16 }), /* @__PURE__ */ React13.createElement("button", { type: "submit", disabled: !valid }, tr("\u653E\u7F6E", "Place"))), /* @__PURE__ */ React13.createElement("div", { className: "dhl-sticker-tabs", role: "tablist" }, TABS.map(([id, zh, en]) => /* @__PURE__ */ React13.createElement("button", { type: "button", role: "tab", key: id, "aria-selected": tab === id, className: tab === id ? "on" : "", onClick: () => setTab(id) }, tr(zh, en)))), tab === "icons" ? /* @__PURE__ */ React13.createElement("div", { className: "dhl-icon-grid", role: "group", "aria-label": tr("\u56FE\u6807", "Icons") }, ICON_LIST.map((name) => /* @__PURE__ */ React13.createElement("button", { type: "button", key: name, "aria-label": tr(ICONS[name][0], ICONS[name][1]), title: tr(ICONS[name][0], ICONS[name][1]), onClick: () => onPick(name) }, /* @__PURE__ */ React13.createElement("svg", { viewBox: "0 0 24 24", width: "22", height: "22", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React13.createElement("path", { d: ICONS[name][2] })), /* @__PURE__ */ React13.createElement("span", null, tr(ICONS[name][0], ICONS[name][1]))))) : /* @__PURE__ */ React13.createElement("div", { className: "dhl-sticker-grid", role: "group", "aria-label": tr("\u8D34\u7EB8", "Stickers") }, STICKERS[tab].map((emoji) => /* @__PURE__ */ React13.createElement("button", { type: "button", key: emoji, "aria-label": `${tr("\u8D34\u7EB8", "Sticker")} ${emoji}`, onClick: () => onPick(`emoji:${emoji}`) }, emoji))));
19048
19987
  }
19049
19988
 
19050
19989
  // src/client/components/desk-column.tsx
19051
- var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
19990
+ var import_dsh_client_ui_primitives6 = require("@deepseek-ai/dsh-client-ui-primitives");
19052
19991
 
19053
19992
  // src/client/components/ma-editor.tsx
19054
- var React13 = __toESM(require("react"), 1);
19993
+ var React14 = __toESM(require("react"), 1);
19055
19994
  var KIND_NAMES = {
19056
19995
  sma: ["SMA", "SMA"],
19057
19996
  ema: ["EMA", "EMA"],
@@ -19065,11 +20004,11 @@ var PRESETS = [
19065
20004
  { kind: "ema", period: 200 }
19066
20005
  ];
19067
20006
  function MaEditor({ lines, onChange }) {
19068
- const [kind, setKind] = React13.useState("smma");
19069
- const [period, setPeriod] = React13.useState("21");
19070
- const [source, setSource] = React13.useState("close");
19071
- const [smoothKind, setSmoothKind] = React13.useState("");
19072
- const [smoothPeriod, setSmoothPeriod] = React13.useState("9");
20007
+ const [kind, setKind] = React14.useState("smma");
20008
+ const [period, setPeriod] = React14.useState("21");
20009
+ const [source, setSource] = React14.useState("close");
20010
+ const [smoothKind, setSmoothKind] = React14.useState("");
20011
+ const [smoothPeriod, setSmoothPeriod] = React14.useState("9");
19073
20012
  const add2 = (input) => {
19074
20013
  let line;
19075
20014
  try {
@@ -19085,17 +20024,17 @@ function MaEditor({ lines, onChange }) {
19085
20024
  }
19086
20025
  onChange([...rest, line]);
19087
20026
  };
19088
- return /* @__PURE__ */ React13.createElement("div", { className: "dhl-ma-editor" }, /* @__PURE__ */ React13.createElement("strong", null, tr("\u5747\u7EBF", "Moving averages")), lines.length ? /* @__PURE__ */ React13.createElement("ul", { className: "dhl-ma-list" }, lines.map((line, index) => /* @__PURE__ */ React13.createElement("li", { key: line.id }, /* @__PURE__ */ React13.createElement("i", { style: { background: maColor(line, index) }, "aria-hidden": "true" }), /* @__PURE__ */ React13.createElement("span", null, maLabel(line)), /* @__PURE__ */ React13.createElement("button", { type: "button", "aria-label": `${tr("\u79FB\u9664", "Remove")} ${maLabel(line)}`, onClick: () => onChange(lines.filter((item) => item.id !== line.id)) }, /* @__PURE__ */ React13.createElement(UiIcon, { name: "close", size: 12 }))))) : null, /* @__PURE__ */ React13.createElement("div", { className: "dhl-ma-presets" }, PRESETS.map((preset) => {
20027
+ return /* @__PURE__ */ React14.createElement("div", { className: "dhl-ma-editor" }, /* @__PURE__ */ React14.createElement("strong", null, tr("\u5747\u7EBF", "Moving averages")), lines.length ? /* @__PURE__ */ React14.createElement("ul", { className: "dhl-ma-list" }, lines.map((line, index) => /* @__PURE__ */ React14.createElement("li", { key: line.id }, /* @__PURE__ */ React14.createElement("i", { style: { background: maColor(line, index) }, "aria-hidden": "true" }), /* @__PURE__ */ React14.createElement("span", null, maLabel(line)), /* @__PURE__ */ React14.createElement("button", { type: "button", "aria-label": `${tr("\u79FB\u9664", "Remove")} ${maLabel(line)}`, onClick: () => onChange(lines.filter((item) => item.id !== line.id)) }, /* @__PURE__ */ React14.createElement(UiIcon, { name: "close", size: 12 }))))) : null, /* @__PURE__ */ React14.createElement("div", { className: "dhl-ma-presets" }, PRESETS.map((preset) => {
19089
20028
  const id = `${preset.kind}${preset.period}`;
19090
- return /* @__PURE__ */ React13.createElement("button", { type: "button", key: id, disabled: lines.some((line) => line.id === id), onClick: () => add2(preset) }, `${preset.kind.toUpperCase()} ${preset.period}`);
19091
- })), /* @__PURE__ */ React13.createElement("div", { className: "dhl-ma-form" }, /* @__PURE__ */ React13.createElement(Select, { "aria-label": tr("\u5747\u7EBF\u7C7B\u578B", "MA type"), value: kind, onChange: (event) => setKind(event.target.value) }, MA_KINDS.map((item) => /* @__PURE__ */ React13.createElement("option", { key: item, value: item }, tr(KIND_NAMES[item][0], KIND_NAMES[item][1])))), /* @__PURE__ */ React13.createElement("input", { "aria-label": tr("\u5468\u671F", "Length"), type: "number", min: 1, max: 500, value: period, onChange: (event) => setPeriod(event.target.value) }), /* @__PURE__ */ React13.createElement(Select, { "aria-label": tr("\u4EF7\u683C\u6E90", "Source"), value: source, onChange: (event) => setSource(event.target.value) }, MA_SOURCES.map((item) => /* @__PURE__ */ React13.createElement("option", { key: item, value: item }, item)))), /* @__PURE__ */ React13.createElement("div", { className: "dhl-ma-form" }, /* @__PURE__ */ React13.createElement(Select, { "aria-label": tr("\u5E73\u6ED1", "Smoothing"), value: smoothKind, onChange: (event) => setSmoothKind(event.target.value) }, /* @__PURE__ */ React13.createElement("option", { value: "" }, tr("\u4E0D\u5E73\u6ED1", "No smoothing")), MA_KINDS.map((item) => /* @__PURE__ */ React13.createElement("option", { key: item, value: item }, tr("\u5E73\u6ED1\uFF1A", "Smooth: ") + item.toUpperCase()))), /* @__PURE__ */ React13.createElement("input", { "aria-label": tr("\u5E73\u6ED1\u5468\u671F", "Smoothing length"), type: "number", min: 1, max: 500, value: smoothPeriod, disabled: !smoothKind, onChange: (event) => setSmoothPeriod(event.target.value) }), /* @__PURE__ */ React13.createElement("button", { type: "button", className: "dhl-ma-add", onClick: () => add2({ kind, period: Number(period), source, smoothing: smoothKind ? { kind: smoothKind, period: Number(smoothPeriod) } : null }) }, tr("\u6DFB\u52A0", "Add"))));
20029
+ return /* @__PURE__ */ React14.createElement("button", { type: "button", key: id, disabled: lines.some((line) => line.id === id), onClick: () => add2(preset) }, `${preset.kind.toUpperCase()} ${preset.period}`);
20030
+ })), /* @__PURE__ */ React14.createElement("div", { className: "dhl-ma-form" }, /* @__PURE__ */ React14.createElement(Select, { "aria-label": tr("\u5747\u7EBF\u7C7B\u578B", "MA type"), value: kind, onChange: (event) => setKind(event.target.value) }, MA_KINDS.map((item) => /* @__PURE__ */ React14.createElement("option", { key: item, value: item }, tr(KIND_NAMES[item][0], KIND_NAMES[item][1])))), /* @__PURE__ */ React14.createElement("input", { "aria-label": tr("\u5468\u671F", "Length"), type: "number", min: 1, max: 500, value: period, onChange: (event) => setPeriod(event.target.value) }), /* @__PURE__ */ React14.createElement(Select, { "aria-label": tr("\u4EF7\u683C\u6E90", "Source"), value: source, onChange: (event) => setSource(event.target.value) }, MA_SOURCES.map((item) => /* @__PURE__ */ React14.createElement("option", { key: item, value: item }, item)))), /* @__PURE__ */ React14.createElement("div", { className: "dhl-ma-form" }, /* @__PURE__ */ React14.createElement(Select, { "aria-label": tr("\u5E73\u6ED1", "Smoothing"), value: smoothKind, onChange: (event) => setSmoothKind(event.target.value) }, /* @__PURE__ */ React14.createElement("option", { value: "" }, tr("\u4E0D\u5E73\u6ED1", "No smoothing")), MA_KINDS.map((item) => /* @__PURE__ */ React14.createElement("option", { key: item, value: item }, tr("\u5E73\u6ED1\uFF1A", "Smooth: ") + item.toUpperCase()))), /* @__PURE__ */ React14.createElement("input", { "aria-label": tr("\u5E73\u6ED1\u5468\u671F", "Smoothing length"), type: "number", min: 1, max: 500, value: smoothPeriod, disabled: !smoothKind, onChange: (event) => setSmoothPeriod(event.target.value) }), /* @__PURE__ */ React14.createElement("button", { type: "button", className: "dhl-ma-add", onClick: () => add2({ kind, period: Number(period), source, smoothing: smoothKind ? { kind: smoothKind, period: Number(smoothPeriod) } : null }) }, tr("\u6DFB\u52A0", "Add"))));
19092
20031
  }
19093
20032
 
19094
20033
  // src/client/components/market-tape.tsx
19095
- var React16 = __toESM(require("react"), 1);
20034
+ var React17 = __toESM(require("react"), 1);
19096
20035
 
19097
20036
  // src/client/components/book-card.tsx
19098
- var React14 = __toESM(require("react"), 1);
20037
+ var React15 = __toESM(require("react"), 1);
19099
20038
  var GROUPINGS = [
19100
20039
  ["full", null],
19101
20040
  ["5", 1],
@@ -19146,7 +20085,7 @@ function BookCard({ snapshot }) {
19146
20085
  saveSessionWorkspace({ bookGrouping: next });
19147
20086
  void loadSnapshot();
19148
20087
  };
19149
- const row = (level, side2) => /* @__PURE__ */ React14.createElement("div", { className: "dhl-lvl", key: `${side2}:${level.px}` }, /* @__PURE__ */ React14.createElement("div", { className: "bar " + side2, style: { width: `${level.total / maxTotal * 100}%` } }), /* @__PURE__ */ React14.createElement(
20088
+ const row = (level, side2) => /* @__PURE__ */ React15.createElement("div", { className: "dhl-lvl", key: `${side2}:${level.px}` }, /* @__PURE__ */ React15.createElement("div", { className: "bar " + side2, style: { width: `${level.total / maxTotal * 100}%` } }), /* @__PURE__ */ React15.createElement(
19150
20089
  "button",
19151
20090
  {
19152
20091
  type: "button",
@@ -19156,35 +20095,181 @@ function BookCard({ snapshot }) {
19156
20095
  onClick: () => store.patch({ ticketDraft: { coin, isBuy: side2 === "bid", px: level.px, at: Date.now() } })
19157
20096
  },
19158
20097
  exchangePrice(level.px)
19159
- ), /* @__PURE__ */ React14.createElement("span", null, fmt(level.sz, 4)), /* @__PURE__ */ React14.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, fmt(level.total, 3)));
19160
- return /* @__PURE__ */ React14.createElement("div", { className: "dhl-card" }, /* @__PURE__ */ React14.createElement("h4", null, t("book.title", "\u76D8\u53E3")), /* @__PURE__ */ React14.createElement("div", { className: "dhl-book-options" }, /* @__PURE__ */ React14.createElement("label", null, tr("\u4EF7\u683C\u6863\u4F4D", "Price grouping"), " ", /* @__PURE__ */ React14.createElement(Select, { "aria-label": tr("\u76D8\u53E3\u4EF7\u683C\u6863\u4F4D", "Order book price grouping"), value: grouping, onChange: (event) => chooseGrouping(event.target.value) }, GROUPINGS.map(([key2, factor]) => /* @__PURE__ */ React14.createElement("option", { key: key2, value: key2 }, factor == null ? tr("\u539F\u59CB", "Full") : stepLabel(baseStep * factor))))), /* @__PURE__ */ React14.createElement("span", null, coin)), asks.length === 0 && bids.length === 0 ? /* @__PURE__ */ React14.createElement("div", { className: "dhl-book-wait" }, grouping === "full" ? t("book.waiting", "\u7B49\u5F85\u4E0A\u6E38\u884C\u60C5\u2026") : tr("\u6B63\u5728\u8BFB\u53D6\u4EA4\u6613\u6240\u805A\u5408\u76D8\u53E3\u2026", "Loading grouped order book\u2026")) : /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement("div", { className: "dhl-lvl dhl-bookhead" }, /* @__PURE__ */ React14.createElement("span", null, t("book.price", "\u4EF7\u683C")), /* @__PURE__ */ React14.createElement("span", null, t("book.size", "\u6570\u91CF")), /* @__PURE__ */ React14.createElement("span", null, t("book.total", "\u7D2F\u8BA1"))), asks.slice().reverse().map((level) => row(level, "ask")), /* @__PURE__ */ React14.createElement("div", { className: "dhl-spread-row" }, /* @__PURE__ */ React14.createElement("span", null, t("book.spread", "\u4EF7\u5DEE")), /* @__PURE__ */ React14.createElement("strong", null, spread != null ? fmtPrice(spread) : "\u2014"), /* @__PURE__ */ React14.createElement("span", null, spread != null && mid ? `${(spread / mid * 100).toFixed(3)}%` : "\u2014")), bids.map((level) => row(level, "bid"))));
20098
+ ), /* @__PURE__ */ React15.createElement("span", null, fmt(level.sz, 4)), /* @__PURE__ */ React15.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, fmt(level.total, 3)));
20099
+ return /* @__PURE__ */ React15.createElement("div", { className: "dhl-card" }, /* @__PURE__ */ React15.createElement("h4", null, t("book.title", "\u76D8\u53E3")), /* @__PURE__ */ React15.createElement("div", { className: "dhl-book-options" }, /* @__PURE__ */ React15.createElement("label", null, tr("\u4EF7\u683C\u6863\u4F4D", "Price grouping"), " ", /* @__PURE__ */ React15.createElement(Select, { "aria-label": tr("\u76D8\u53E3\u4EF7\u683C\u6863\u4F4D", "Order book price grouping"), value: grouping, onChange: (event) => chooseGrouping(event.target.value) }, GROUPINGS.map(([key3, factor]) => /* @__PURE__ */ React15.createElement("option", { key: key3, value: key3 }, factor == null ? tr("\u539F\u59CB", "Full") : stepLabel(baseStep * factor))))), /* @__PURE__ */ React15.createElement("span", null, coin)), asks.length === 0 && bids.length === 0 ? /* @__PURE__ */ React15.createElement("div", { className: "dhl-book-wait" }, grouping === "full" ? t("book.waiting", "\u7B49\u5F85\u4E0A\u6E38\u884C\u60C5\u2026") : tr("\u6B63\u5728\u8BFB\u53D6\u4EA4\u6613\u6240\u805A\u5408\u76D8\u53E3\u2026", "Loading grouped order book\u2026")) : /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement("div", { className: "dhl-lvl dhl-bookhead" }, /* @__PURE__ */ React15.createElement("span", null, t("book.price", "\u4EF7\u683C")), /* @__PURE__ */ React15.createElement("span", null, t("book.size", "\u6570\u91CF")), /* @__PURE__ */ React15.createElement("span", null, t("book.total", "\u7D2F\u8BA1"))), asks.slice().reverse().map((level) => row(level, "ask")), /* @__PURE__ */ React15.createElement("div", { className: "dhl-spread-row" }, /* @__PURE__ */ React15.createElement("span", null, t("book.spread", "\u4EF7\u5DEE")), /* @__PURE__ */ React15.createElement("strong", null, spread != null ? fmtPrice(spread) : "\u2014"), /* @__PURE__ */ React15.createElement("span", null, spread != null && mid ? `${(spread / mid * 100).toFixed(3)}%` : "\u2014")), bids.map((level) => row(level, "bid"))));
19161
20100
  }
19162
20101
 
19163
20102
  // src/client/components/trades-card.tsx
19164
- var React15 = __toESM(require("react"), 1);
20103
+ var React16 = __toESM(require("react"), 1);
19165
20104
  function TradesCard({ snapshot }) {
19166
20105
  const trades = (Array.isArray(snapshot?.trades) ? snapshot.trades : []).slice(0, 24);
19167
- return /* @__PURE__ */ React15.createElement("div", { className: "dhl-card" }, /* @__PURE__ */ React15.createElement("h4", null, t("trades.title", "\u6700\u8FD1\u6210\u4EA4")), /* @__PURE__ */ React15.createElement("div", { className: "dhl-lvl dhl-bookhead" }, /* @__PURE__ */ React15.createElement("span", null, t("book.price", "\u4EF7\u683C")), /* @__PURE__ */ React15.createElement("span", null, t("book.size", "\u6570\u91CF")), /* @__PURE__ */ React15.createElement("span", null, tr("\u65F6\u95F4", "Time"))), trades.length === 0 ? /* @__PURE__ */ React15.createElement("div", { style: { color: "var(--dsw-alias-label-secondary)", fontSize: 11 } }, "\u2014") : trades.map((tr2, i) => /* @__PURE__ */ React15.createElement("div", { className: "dhl-lvl", key: i, style: { position: "relative" } }, /* @__PURE__ */ React15.createElement("span", { style: { color: tr2.side === "B" ? "var(--dsw-alias-state-success-primary)" : "var(--dsw-alias-state-error-primary)" } }, fmtPrice(tr2.px)), /* @__PURE__ */ React15.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, fmt(tr2.sz, 4)), /* @__PURE__ */ React15.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, Number.isFinite(Number(tr2.time)) ? new Date(Number(tr2.time)).toLocaleTimeString(tr("zh-CN", "en-US"), { hour12: false }) : "\u2014"))));
20106
+ return /* @__PURE__ */ React16.createElement("div", { className: "dhl-card" }, /* @__PURE__ */ React16.createElement("h4", null, t("trades.title", "\u6700\u8FD1\u6210\u4EA4")), /* @__PURE__ */ React16.createElement("div", { className: "dhl-lvl dhl-bookhead" }, /* @__PURE__ */ React16.createElement("span", null, t("book.price", "\u4EF7\u683C")), /* @__PURE__ */ React16.createElement("span", null, t("book.size", "\u6570\u91CF")), /* @__PURE__ */ React16.createElement("span", null, tr("\u65F6\u95F4", "Time"))), trades.length === 0 ? /* @__PURE__ */ React16.createElement("div", { style: { color: "var(--dsw-alias-label-secondary)", fontSize: 11 } }, "\u2014") : trades.map((tr2, i) => /* @__PURE__ */ React16.createElement("div", { className: "dhl-lvl", key: i, style: { position: "relative" } }, /* @__PURE__ */ React16.createElement("span", { style: { color: tr2.side === "B" ? "var(--dsw-alias-state-success-primary)" : "var(--dsw-alias-state-error-primary)" } }, fmtPrice(tr2.px)), /* @__PURE__ */ React16.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, fmt(tr2.sz, 4)), /* @__PURE__ */ React16.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, Number.isFinite(Number(tr2.time)) ? new Date(Number(tr2.time)).toLocaleTimeString(tr("zh-CN", "en-US"), { hour12: false }) : "\u2014"))));
19168
20107
  }
19169
20108
 
19170
20109
  // src/client/components/market-tape.tsx
19171
20110
  function MarketTape({ snapshot }) {
19172
20111
  const s = useStore();
19173
20112
  const tab = s.workspaceSessionId === s.activeSessionId ? s.workspace?.tapeTab ?? "book" : "book";
19174
- return /* @__PURE__ */ React16.createElement("aside", { className: "dhl-tape", "aria-label": tr("\u76D8\u53E3\u4E0E\u6210\u4EA4", "Order book and trades") }, /* @__PURE__ */ React16.createElement("div", { className: "dhl-tape-tabs", role: "tablist", "aria-label": tr("\u5E02\u573A\u6570\u636E", "Market data") }, /* @__PURE__ */ React16.createElement("button", { type: "button", role: "tab", "aria-selected": tab === "book", className: tab === "book" ? "on" : "", onClick: () => saveSessionWorkspace({ tapeTab: "book" }) }, tr("\u76D8\u53E3", "Order book")), /* @__PURE__ */ React16.createElement("button", { type: "button", role: "tab", "aria-selected": tab === "trades", className: tab === "trades" ? "on" : "", onClick: () => saveSessionWorkspace({ tapeTab: "trades" }) }, tr("\u6210\u4EA4", "Trades"))), /* @__PURE__ */ React16.createElement("div", { className: "dhl-tape-content", role: "tabpanel" }, tab === "book" ? /* @__PURE__ */ React16.createElement(BookCard, { snapshot }) : /* @__PURE__ */ React16.createElement(TradesCard, { snapshot })));
20113
+ return /* @__PURE__ */ React17.createElement("aside", { className: "dhl-tape", "aria-label": tr("\u76D8\u53E3\u4E0E\u6210\u4EA4", "Order book and trades") }, /* @__PURE__ */ React17.createElement("div", { className: "dhl-tape-tabs", role: "tablist", "aria-label": tr("\u5E02\u573A\u6570\u636E", "Market data") }, /* @__PURE__ */ React17.createElement("button", { type: "button", role: "tab", "aria-selected": tab === "book", className: tab === "book" ? "on" : "", onClick: () => saveSessionWorkspace({ tapeTab: "book" }) }, tr("\u76D8\u53E3", "Order book")), /* @__PURE__ */ React17.createElement("button", { type: "button", role: "tab", "aria-selected": tab === "trades", className: tab === "trades" ? "on" : "", onClick: () => saveSessionWorkspace({ tapeTab: "trades" }) }, tr("\u6210\u4EA4", "Trades"))), /* @__PURE__ */ React17.createElement("div", { className: "dhl-tape-content", role: "tabpanel" }, tab === "book" ? /* @__PURE__ */ React17.createElement(BookCard, { snapshot }) : /* @__PURE__ */ React17.createElement(TradesCard, { snapshot })));
19175
20114
  }
19176
20115
 
19177
20116
  // src/client/components/bottom-table.tsx
19178
- var React17 = __toESM(require("react"), 1);
20117
+ var React19 = __toESM(require("react"), 1);
20118
+
20119
+ // src/client/components/trade-review.tsx
20120
+ var React18 = __toESM(require("react"), 1);
20121
+
20122
+ // lib/review.js
20123
+ var numeric = (value) => value == null || value === "" || !Number.isFinite(Number(value)) ? null : Number(value);
20124
+ var key2 = (row) => row?.oid == null || !row.coin ? null : `${row.coin}:${String(row.oid)}`;
20125
+ var core = (coin) => typeof coin === "string" && coin.trim().length > 0 && !coin.startsWith("#") && !coin.includes(":") && !coin.startsWith("@") && !coin.includes("/");
20126
+ function reviewLedger(payload, coin = "") {
20127
+ const orders = (payload.orders ?? []).filter((row) => !coin || row.coin === coin);
20128
+ const allFills = (payload.fills ?? []).filter((row) => !coin || row.coin === coin);
20129
+ const byOrder = new Map(orders.filter((row) => key2(row)).map((row) => [key2(row), row]));
20130
+ const seen = /* @__PURE__ */ new Set(), records = [];
20131
+ let duplicates = 0;
20132
+ for (const [index, fill] of allFills.entries()) {
20133
+ const id = fill.tid != null ? `${fill.coin}:${fill.tid}` : payload.mode === "paper" && fill.oid ? `${fill.coin}:${fill.oid}` : `unidentified:${index}`;
20134
+ if (seen.has(id)) {
20135
+ duplicates++;
20136
+ continue;
20137
+ }
20138
+ seen.add(id);
20139
+ const order = byOrder.get(key2(fill)), pnl = numeric(fill.closedPnl ?? fill.realized), fee = numeric(fill.fee);
20140
+ const feeToken = String(fill.feeToken ?? "").trim();
20141
+ const px = numeric(fill.px), size3 = numeric(fill.sz), buy = typeof fill.isBuy === "boolean" ? fill.isBuy : fill.side === "B" ? true : fill.side === "A" ? false : null;
20142
+ const reference = numeric(fill.referencePx ?? order?.referencePx);
20143
+ const reason = fill.reason || order?.reason || "";
20144
+ const closed = numeric(fill.closedSize) ?? (buy != null && numeric(fill.startPosition) != null && Math.sign(Number(fill.startPosition)) !== (buy ? 1 : -1) ? Math.min(Math.abs(Number(fill.startPosition)), size3 ?? 0) : /^Close/.test(fill.dir ?? "") ? size3 : null);
20145
+ const eligible = core(fill.coin) && feeToken === "USDC";
20146
+ const net = eligible && pnl != null && fee != null ? pnl - fee : null;
20147
+ const flags2 = [];
20148
+ if (!order) flags2.push("unmatched-order");
20149
+ if (px == null || px <= 0) flags2.push("missing-price");
20150
+ if (size3 == null || size3 <= 0) flags2.push("missing-size");
20151
+ if (numeric(fill.time) == null) flags2.push("missing-time");
20152
+ if (buy == null) flags2.push("missing-side");
20153
+ if (!reason || /^(session:|tool$|chat$|order$)/.test(reason)) flags2.push("missing-reason");
20154
+ if (fee == null || !feeToken) flags2.push("missing-fee");
20155
+ if (pnl == null) flags2.push("missing-pnl");
20156
+ if (fill.tid == null && payload.mode !== "paper") flags2.push("missing-trade-id");
20157
+ records.push({
20158
+ id,
20159
+ coin: fill.coin,
20160
+ oid: fill.oid ?? null,
20161
+ time: numeric(fill.time),
20162
+ px,
20163
+ size: size3,
20164
+ isBuy: buy,
20165
+ gross: pnl,
20166
+ fee,
20167
+ feeToken,
20168
+ net,
20169
+ reference,
20170
+ slippageBps: reference && reference > 0 && px && buy != null ? (buy ? px - reference : reference - px) / reference * 1e4 : null,
20171
+ closedSize: closed,
20172
+ reason: flags2.includes("missing-reason") ? "" : reason,
20173
+ sessionId: fill.sessionId ?? order?.sessionId ?? null,
20174
+ flags: flags2,
20175
+ source: payload.mode
20176
+ });
20177
+ }
20178
+ const reconciled = orders.map((order) => {
20179
+ const fills = records.filter((row) => key2(row) && key2(row) === key2(order));
20180
+ const filled = fills.reduce((sum, row) => sum + (row.size ?? 0), 0);
20181
+ const expected = numeric(order.origSz ?? order.sz), tolerance = Math.max(1e-8, (expected ?? 0) * 1e-6);
20182
+ const status = fills.some((row) => row.flags.includes("missing-size")) ? "missing-size" : /filled/i.test(order.status ?? "") && expected != null && Math.abs(filled - expected) > tolerance ? "size-gap" : expected != null && filled > expected + tolerance ? "excess-fill" : fills.length ? "matched" : "no-fill-in-window";
20183
+ return { coin: order.coin, oid: order.oid ?? null, status, filled, expected, exchangeStatus: order.status ?? "unknown", reason: order.reason ?? "" };
20184
+ });
20185
+ const supported = records.filter((row) => core(row.coin));
20186
+ const fees = {};
20187
+ for (const row of records) if (row.fee != null && row.feeToken) fees[row.feeToken] = (fees[row.feeToken] ?? 0) + row.fee;
20188
+ const supportedScope = !coin || core(coin);
20189
+ const gross = supportedScope ? supported.reduce((sum, row) => sum + (row.gross ?? 0), 0) : null;
20190
+ const tradingNet = !supportedScope || supported.some((row) => row.net == null) ? null : supported.reduce((sum, row) => sum + row.net, 0);
20191
+ const fundingRows = (payload.funding ?? []).filter((row) => core(row.delta?.coin) && (!coin || row.delta.coin === coin));
20192
+ const funding = supportedScope && payload.coverage?.fundingStatus === "complete" && fundingRows.every((row) => numeric(row.delta.usdc) != null) ? fundingRows.reduce((sum, row) => sum + Number(row.delta.usdc), 0) : null;
20193
+ const closes = supported.filter((row) => row.closedSize > 0);
20194
+ return {
20195
+ records,
20196
+ orders: reconciled,
20197
+ duplicates,
20198
+ fees,
20199
+ gross,
20200
+ tradingNet,
20201
+ funding,
20202
+ net: tradingNet != null && funding != null ? tradingNet + funding : null,
20203
+ closeCount: closes.length,
20204
+ missingReasons: records.filter((row) => row.flags.includes("missing-reason")).length,
20205
+ unmatched: records.filter((row) => row.flags.includes("unmatched-order")).length,
20206
+ gaps: reconciled.filter((row) => ["size-gap", "excess-fill"].includes(row.status)).length,
20207
+ unsupported: records.length - supported.length
20208
+ };
20209
+ }
20210
+
20211
+ // src/client/components/trade-review.tsx
20212
+ var flags = {
20213
+ "unmatched-order": ["\u672A\u5339\u914D\u59D4\u6258", "Unmatched order"],
20214
+ "missing-reason": ["\u672A\u8BB0\u5F55\u539F\u56E0", "Missing rationale"],
20215
+ "missing-fee": ["\u8D39\u7528\u672A\u77E5", "Unknown fee"],
20216
+ "missing-pnl": ["\u76C8\u4E8F\u672A\u77E5", "Unknown P&L"],
20217
+ "missing-trade-id": ["\u7F3A\u5C11\u6210\u4EA4 ID", "Missing trade ID"],
20218
+ "size-gap": ["\u6570\u91CF\u6709\u5DEE\u5F02", "Size gap"],
20219
+ "excess-fill": ["\u6210\u4EA4\u8D85\u51FA\u59D4\u6258\u91CF", "Excess fill"],
20220
+ "missing-price": ["\u4EF7\u683C\u672A\u77E5", "Unknown price"],
20221
+ "missing-size": ["\u6570\u91CF\u672A\u77E5", "Unknown size"],
20222
+ "missing-time": ["\u65F6\u95F4\u672A\u77E5", "Unknown time"],
20223
+ "missing-side": ["\u65B9\u5411\u672A\u77E5", "Unknown side"],
20224
+ "matched": ["\u6570\u91CF\u5339\u914D", "Quantity matched"],
20225
+ "no-fill-in-window": ["\u7A97\u53E3\u5185\u65E0\u6210\u4EA4", "No fill in window"]
20226
+ };
20227
+ function TradeReview() {
20228
+ const s = useStore(), [days, setDays] = React18.useState(30), [scope, setScope] = React18.useState("coin");
20229
+ const [reload, setReload] = React18.useState(0), [data, setData] = React18.useState(null), [error, setError] = React18.useState(""), [limit, setLimit] = React18.useState(50);
20230
+ const identity = `${s.status?.network}:${s.status?.tradingMode}:${s.account?.address ?? ""}:${days}`;
20231
+ const [loadedFor, setLoadedFor] = React18.useState("");
20232
+ React18.useEffect(() => {
20233
+ let live = true;
20234
+ setData(null);
20235
+ setError("");
20236
+ setLimit(50);
20237
+ setLoadedFor("");
20238
+ void hyperCall("tradeHistory", { includeFunding: true, days }).then((value) => {
20239
+ if (live) {
20240
+ setData(value);
20241
+ setLoadedFor(identity);
20242
+ }
20243
+ }).catch((cause) => {
20244
+ if (live) setError(String(cause?.message ?? cause));
20245
+ });
20246
+ return () => {
20247
+ live = false;
20248
+ };
20249
+ }, [identity, reload]);
20250
+ const payload = loadedFor === identity ? data : null;
20251
+ const coin = scope === "coin" ? s.activeCoin : "";
20252
+ const ledger = React18.useMemo(() => payload ? reviewLedger(payload, coin) : null, [payload, coin]);
20253
+ const showFlag = (flag) => flags[flag] ? tr(...flags[flag]) : flag;
20254
+ const money = (value) => value == null ? tr("\u672A\u77E5", "Unknown") : `${fmt(value)} USDC`;
20255
+ const coverage = payload?.coverage;
20256
+ return /* @__PURE__ */ React18.createElement("section", { className: "dhl-review", "aria-label": tr("\u4EA4\u6613\u590D\u76D8", "Trade review") }, /* @__PURE__ */ React18.createElement("div", { className: "dhl-history-controls" }, /* @__PURE__ */ React18.createElement(Select, { "aria-label": tr("\u590D\u76D8\u65F6\u95F4\u8303\u56F4", "Review window"), value: days, onChange: (e) => setDays(Number(e.target.value)) }, [7, 30, 90].map((d) => /* @__PURE__ */ React18.createElement("option", { value: d, key: d }, tr(`\u6700\u8FD1 ${d} \u5929`, `Last ${d} days`)))), /* @__PURE__ */ React18.createElement(Select, { "aria-label": tr("\u590D\u76D8\u6807\u7684\u8303\u56F4", "Review markets"), value: scope, onChange: (e) => setScope(e.target.value) }, /* @__PURE__ */ React18.createElement("option", { value: "coin" }, s.activeCoin), /* @__PURE__ */ React18.createElement("option", { value: "all" }, tr("\u5168\u90E8\u6807\u7684", "All markets"))), /* @__PURE__ */ React18.createElement("button", { type: "button", onClick: () => setReload((v) => v + 1) }, tr("\u5237\u65B0\u8BC1\u636E", "Refresh evidence")), /* @__PURE__ */ React18.createElement("button", { type: "button", disabled: !ledger, onClick: () => downloadBlob(new Blob([JSON.stringify({ exportedAt: (/* @__PURE__ */ new Date()).toISOString(), scope: coin || "all", evidence: payload, reconciliation: ledger }, null, 2)], { type: "application/json" }), `trade-review-${Date.now()}.json`) }, tr("\u5BFC\u51FA\u8BC1\u636E JSON", "Export evidence JSON")), /* @__PURE__ */ React18.createElement("button", { type: "button", disabled: !ledger, onClick: () => queueToAgent(tr(`\u8BF7\u7528 hyper_account action=review days=${days}${coin ? ` coin=${coin}` : ""} \u83B7\u53D6\u590D\u76D8\u8BC1\u636E\uFF0C\u6838\u5BF9\u8BA2\u5355\u3001\u6210\u4EA4\u3001\u6210\u672C\u548C\u539F\u56E0\u3002\u5148\u8BF4\u660E\u8986\u76D6\u7F3A\u53E3\uFF0C\u518D\u590D\u76D8\u51B3\u7B56\u4E0E\u6267\u884C\uFF1B\u4E0D\u8981\u628A\u7A97\u53E3\u7EDF\u8BA1\u5F53\u6210\u8D26\u6237\u6536\u76CA\u7387\u3002`, `Use hyper_account action=review days=${days}${coin ? ` coin=${coin}` : ""} to review orders, fills, costs and rationale. Explain coverage gaps before reviewing decisions and execution. Window totals are not account returns.`)) }, tr("\u4EA4\u7ED9 AI \u590D\u76D8", "Review with AI"))), error ? /* @__PURE__ */ React18.createElement("p", { className: "dhl-inline-error", role: "alert" }, error) : !ledger ? /* @__PURE__ */ React18.createElement("p", { role: "status" }, tr("\u6B63\u5728\u6838\u5BF9\u4EA4\u6613\u8BC1\u636E\u2026", "Checking trading evidence\u2026")) : /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement("p", { className: "dhl-evidence-note" }, payload.mode === "paper" ? tr("\u6A21\u62DF\u8D26\u672C", "Paper ledger") : tr("\u4EA4\u6613\u6240\u5386\u53F2", "Exchange history"), " \xB7 ", payload.network, " \xB7 ", new Date(coverage.startTime).toLocaleString(), " \u2014 ", new Date(coverage.endTime).toLocaleString(), /* @__PURE__ */ React18.createElement("br", null), tr("\u7A97\u53E3\u7EDF\u8BA1\uFF0C\u4E0D\u4EE3\u8868\u8D26\u6237\u6536\u76CA\u6216\u5B8C\u6574\u5F80\u8FD4\u4EA4\u6613\u3002\u4EC5\u6838\u5FC3 USDC \u6C38\u7EED\u5408\u5E76\u76C8\u4E8F\uFF1B\u5176\u4ED6\u62B5\u62BC\u5E01\u79CD\u5355\u72EC\u770B\u539F\u59CB\u8BB0\u5F55\u3002\u624B\u7EED\u8D39\u5DF2\u542B builder fee\uFF0C\u4E0D\u91CD\u590D\u6263\u9664\u3002", "Window statistics, not account returns or completed round trips. Combined P&L covers core USDC perpetuals only. Fees include builder fees once.")), coverage.fillsLimited || coverage.ordersLimited ? /* @__PURE__ */ React18.createElement("p", { className: "dhl-inline-error" }, tr("\u5386\u53F2\u6570\u91CF\u8FBE\u5230\u4FDD\u7559\u4E0A\u9650\uFF0C\u65F6\u95F4\u8303\u56F4\u53EF\u80FD\u4E0D\u5B8C\u6574\uFF1B\u4E0D\u80FD\u636E\u6B64\u786E\u8BA4\u5168\u90E8\u4EA4\u6613\u5DF2\u6838\u5BF9\u3002", "History reaches its retention limit; this window may be incomplete.")) : null, coverage.fundingStatus !== "complete" ? /* @__PURE__ */ React18.createElement("p", { className: "dhl-evidence-note" }, tr("\u8D44\u91D1\u8D39", "Funding"), "\uFF1A", coverage.fundingStatus === "not-simulated" ? tr("\u672A\u6A21\u62DF", "Not simulated") : tr("\u672A\u5B8C\u6574\u53D6\u5F97", "Incomplete"), " ", coverage.fundingError ?? "", " \xB7 ", tr("\u4E0D\u663E\u793A\u542B\u8D44\u91D1\u8D39\u51C0\u989D", "Net including funding is unknown")) : null, /* @__PURE__ */ React18.createElement("div", { className: "dhl-review-summary" }, [[tr("\u5DF2\u77E5\u6BDB\u76C8\u4E8F", "Known gross P&L"), money(ledger.gross)], [tr("\u6263\u624B\u7EED\u8D39\u51C0\u989D", "Net after fees"), money(ledger.tradingNet)], [tr("\u8D44\u91D1\u8D39\u6536\u5165 / \u652F\u51FA", "Funding credits / debits"), money(ledger.funding)], [tr("\u542B\u8D44\u91D1\u8D39\u51C0\u989D", "Net including funding"), money(ledger.net)], [tr("\u6210\u4EA4 / \u5E73\u4ED3\u6210\u4EA4\u7B14\u6570", "Fills / closing fills"), `${ledger.records.length} / ${ledger.closeCount}`], [tr("\u7F3A\u5C11\u539F\u56E0 / \u672A\u5339\u914D / \u6570\u91CF\u5DEE\u5F02", "Missing rationale / unmatched / size gaps"), `${ledger.missingReasons} / ${ledger.unmatched} / ${ledger.gaps}`]].map(([label, value]) => /* @__PURE__ */ React18.createElement("div", { key: label }, /* @__PURE__ */ React18.createElement("span", null, label), /* @__PURE__ */ React18.createElement("strong", null, value)))), /* @__PURE__ */ React18.createElement("p", { className: "dhl-evidence-note" }, tr("\u5DF2\u77E5\u624B\u7EED\u8D39", "Known fees"), "\uFF1A", Object.entries(ledger.fees).map(([token, value]) => `${fmt(value, 6)} ${token}`).join(" \xB7 ") || "\u2014", " \xB7 ", tr("\u53BB\u91CD\u6210\u4EA4", "Duplicate fills removed"), " ", ledger.duplicates, " \xB7 ", tr("\u672A\u5408\u5E76\u76C8\u4E8F\u7684\u6210\u4EA4", "Fills excluded from combined P&L"), " ", ledger.unsupported), payload.paperCosts ? /* @__PURE__ */ React18.createElement("p", { className: "dhl-evidence-note" }, tr("\u5F53\u524D\u6A21\u62DF\u5047\u8BBE", "Current simulation assumptions"), "\uFF1A", payload.paperCosts.feeBps, " bps ", tr("\u624B\u7EED\u8D39", "fee"), " / ", payload.paperCosts.slippageBps, " bps ", tr("\u6ED1\u70B9", "slippage"), " \xB7 ", tr("\u5386\u53F2\u9010\u7B14\u4FDD\u7559\u5F53\u65F6\u914D\u7F6E\uFF1B\u65E7\u8BB0\u5F55\u8D39\u7528\u672A\u77E5\u3002\u672A\u6A21\u62DF\u76D8\u53E3\u6DF1\u5EA6\u3001\u8D44\u91D1\u8D39\u3001\u5F3A\u5E73\u3002", "Each fill preserves its assumptions; legacy fees may be unknown. No depth, funding or liquidation simulation.")) : null, payload.localOrders?.some((row) => ["unknown", "submitting", "protection-unknown"].includes(row.status)) ? /* @__PURE__ */ React18.createElement("details", null, /* @__PURE__ */ React18.createElement("summary", { className: "dhl-inline-error" }, tr("\u672C\u5730\u4E0B\u5355\u72B6\u6001\u5F85\u6838\u5B9E", "Local order status unconfirmed")), payload.localOrders.filter((row) => ["unknown", "submitting", "protection-unknown"].includes(row.status)).map((row) => /* @__PURE__ */ React18.createElement("div", { className: "dhl-evidence-detail", key: row.cloid }, /* @__PURE__ */ React18.createElement("span", null, row.coin, " \xB7 ", row.status, " \xB7 ", row.cloid), /* @__PURE__ */ React18.createElement("button", { type: "button", className: "dhl-ghost", onClick: () => queueToAgent(tr(`\u8BF7\u7528 hyper_account action=orderStatus oid=${row.cloid} \u6838\u5B9E\u8BA2\u5355\u4E0E\u4FDD\u62A4\u5355\u72B6\u6001\uFF0C\u4E0D\u8981\u91CD\u590D\u4E0B\u5355\u3002`, `Use hyper_account action=orderStatus oid=${row.cloid} to check the order and protection state. Do not submit another order.`)) }, tr("\u6838\u5B9E\u8BA2\u5355\u72B6\u6001", "Check order status"))))) : null, /* @__PURE__ */ React18.createElement("details", null, /* @__PURE__ */ React18.createElement("summary", null, tr("\u59D4\u6258\u6570\u91CF\u6838\u5BF9", "Order quantity reconciliation"), " \xB7 ", ledger.orders.length), /* @__PURE__ */ React18.createElement("p", { className: "dhl-evidence-note" }, tr("\u6309\u6807\u7684 + oid \u5339\u914D\uFF1B\u90E8\u5206\u6210\u4EA4\u3001\u7A97\u53E3\u5916\u6210\u4EA4\u6216\u4FDD\u7559\u622A\u65AD\u90FD\u4F1A\u4EA7\u751F\u5DEE\u5F02\uFF0C\u65E0\u6210\u4EA4\u4E0D\u7B49\u4E8E\u672A\u6267\u884C\u3002", "Matched by market and oid. Partial fills, window boundaries and retention can cause gaps. No fill is not proof of no execution.")), ledger.orders.slice(0, limit).map((row, i) => /* @__PURE__ */ React18.createElement("div", { className: "dhl-review-order", key: `${row.coin}:${row.oid}:${i}` }, /* @__PURE__ */ React18.createElement("span", null, row.coin, " \xB7 oid ", row.oid ?? "\u2014"), /* @__PURE__ */ React18.createElement("span", null, fmt(row.filled, 6), " / ", row.expected == null ? "\u2014" : fmt(row.expected, 6)), /* @__PURE__ */ React18.createElement("span", null, row.exchangeStatus, " \xB7 ", showFlag(row.status))))), !ledger.records.length ? /* @__PURE__ */ React18.createElement("p", { className: "dhl-empty" }, tr("\u7A97\u53E3\u5185\u65E0\u6210\u4EA4\u8BB0\u5F55", "No fills in this window")) : null, [...ledger.records].sort((a, b) => (b.time ?? 0) - (a.time ?? 0)).slice(0, limit).map((row) => /* @__PURE__ */ React18.createElement("details", { key: row.id, className: "dhl-review-fill" }, /* @__PURE__ */ React18.createElement("summary", null, /* @__PURE__ */ React18.createElement("time", null, row.time ? new Date(row.time).toLocaleString() : "\u2014"), " ", /* @__PURE__ */ React18.createElement("b", null, row.coin), " ", row.isBuy == null ? tr("\u65B9\u5411\u672A\u77E5", "Side unknown") : row.isBuy ? tr("\u4E70", "Buy") : tr("\u5356", "Sell"), " ", fmt(row.size, 6), " @ ", fmtPrice(row.px), " ", /* @__PURE__ */ React18.createElement("span", null, tr("\u6BDB\u76C8\u4E8F", "Gross"), " ", row.gross == null ? "\u2014" : fmt(row.gross))), /* @__PURE__ */ React18.createElement("div", { className: "dhl-evidence-detail" }, /* @__PURE__ */ React18.createElement("span", null, "oid ", row.oid ?? "\u2014", " \xB7 ", row.id), /* @__PURE__ */ React18.createElement("span", null, tr("\u624B\u7EED\u8D39", "Fee"), " ", row.fee == null ? tr("\u672A\u77E5", "Unknown") : `${fmt(row.fee, 6)} ${row.feeToken}`, " \xB7 ", tr("USDC \u51C0\u989D", "USDC net"), " ", money(row.net)), /* @__PURE__ */ React18.createElement("span", null, tr("\u76F8\u5BF9\u51B3\u7B56\u4E2D\u95F4\u4EF7\u7684\u6ED1\u70B9\uFF08\u6B63\u6570\u4E3A\u4E0D\u5229\uFF09", "Slippage vs decision mid (positive is adverse)"), " ", row.slippageBps == null ? tr("\u672A\u77E5", "Unknown") : `${fmt(row.slippageBps, 2)} bps`), /* @__PURE__ */ React18.createElement("span", null, tr("\u5F53\u65F6\u539F\u56E0", "Rationale"), "\uFF1A", row.reason || tr("\u672A\u8BB0\u5F55\uFF0C\u4E0D\u80FD\u4E8B\u540E\u8865\u5199\u6210\u539F\u59CB\u4F9D\u636E", "Not recorded; cannot reconstruct original rationale")), /* @__PURE__ */ React18.createElement("span", null, row.flags.map(showFlag).join(" \xB7 ")), row.coin === s.activeCoin && row.time ? /* @__PURE__ */ React18.createElement("button", { type: "button", className: "dhl-ghost", onClick: () => {
20257
+ store.patch({ chartFocus: { coin: row.coin, interval: s.interval, t: row.time, at: Date.now() } });
20258
+ saveChartTab();
20259
+ } }, tr("\u5B9A\u4F4D\u56FE\u8868", "Locate on chart")) : null))), Math.max(ledger.records.length, ledger.orders.length) > limit ? /* @__PURE__ */ React18.createElement("button", { type: "button", className: "dhl-ghost", onClick: () => setLimit((v) => v + 100) }, tr("\u663E\u793A\u66F4\u591A", "Show more")) : null));
20260
+ }
20261
+ var saveChartTab = () => saveSessionWorkspace({ chartTab: "chart" });
20262
+
20263
+ // src/client/components/bottom-table.tsx
19179
20264
  function BottomTable({ account, accountError }) {
19180
20265
  const s = useStore();
19181
20266
  const tab = s.workspaceSessionId === s.activeSessionId ? s.workspace?.bottomTab ?? "positions" : "positions";
19182
- const [orderView, setOrderView] = React17.useState("open");
19183
- const [historyLimit, setHistoryLimit] = React17.useState(100);
20267
+ const [orderView, setOrderView] = React19.useState("open");
20268
+ const [historyLimit, setHistoryLimit] = React19.useState(100);
19184
20269
  const positions = account?.positions ?? [];
19185
20270
  const orders = account?.openOrders ?? [];
19186
20271
  const history = s.tradeHistory;
19187
- React17.useEffect(() => {
20272
+ React19.useEffect(() => {
19188
20273
  if (tab === "orders") void loadTradeHistory();
19189
20274
  }, [tab, account?.address, s.status?.network]);
19190
20275
  const requestCancel = (coin, oid) => {
@@ -19196,58 +20281,61 @@ function BottomTable({ account, accountError }) {
19196
20281
  const tabs = [
19197
20282
  ["positions", t("tab.positions", "\u6301\u4ED3")],
19198
20283
  ["orders", t("tab.orders", "\u59D4\u6258\u4E0E\u6210\u4EA4")],
19199
- ["account", t("tab.account", "\u8D26\u6237")]
20284
+ ["account", t("tab.account", "\u8D26\u6237")],
20285
+ ["review", tr("\u4EA4\u6613\u590D\u76D8", "Trade review")]
19200
20286
  ];
19201
- const head = (...cells) => /* @__PURE__ */ React17.createElement("div", { className: "dhl-posrow dhl-poshead" }, cells.map((c) => /* @__PURE__ */ React17.createElement("span", { key: c }, c)));
20287
+ const head = (...cells) => /* @__PURE__ */ React19.createElement("div", { className: "dhl-posrow dhl-poshead" }, cells.map((c) => /* @__PURE__ */ React19.createElement("span", { key: c }, c)));
19202
20288
  let body = null;
19203
20289
  if (tab === "positions") {
19204
- body = /* @__PURE__ */ React17.createElement("div", { className: "dhl-positions-table" }, /* @__PURE__ */ React17.createElement("div", { className: "dhl-posrow dhl-posrow-position dhl-poshead" }, [t("positions.coin", "\u5408\u7EA6"), t("positions.size", "\u6570\u91CF"), t("positions.entry", "\u5F00\u4ED3\u4EF7"), t("positions.mark", "\u6807\u8BB0"), t("positions.upnl", "\u672A\u5B9E\u73B0"), "ROE", tr("\u6E05\u7B97\u4EF7", "Liquidation")].map((label) => /* @__PURE__ */ React17.createElement("span", { key: label }, label))), positions.length === 0 ? /* @__PURE__ */ React17.createElement("div", { className: "dhl-posrow" }, /* @__PURE__ */ React17.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, t("positions.none", "\uFF08\u65E0\u6301\u4ED3\uFF09"))) : positions.map((p, i) => {
20290
+ body = /* @__PURE__ */ React19.createElement("div", { className: "dhl-positions-table" }, /* @__PURE__ */ React19.createElement("div", { className: "dhl-posrow dhl-posrow-position dhl-poshead" }, [t("positions.coin", "\u5408\u7EA6"), t("positions.size", "\u6570\u91CF"), t("positions.entry", "\u5F00\u4ED3\u4EF7"), t("positions.mark", "\u6807\u8BB0"), t("positions.upnl", "\u672A\u5B9E\u73B0"), "ROE", tr("\u6E05\u7B97\u4EF7", "Liquidation")].map((label) => /* @__PURE__ */ React19.createElement("span", { key: label }, label))), positions.length === 0 ? /* @__PURE__ */ React19.createElement("div", { className: "dhl-posrow" }, /* @__PURE__ */ React19.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, t("positions.none", "\uFF08\u65E0\u6301\u4ED3\uFF09"))) : positions.map((p, i) => {
19205
20291
  const roe = p.returnOnEquity == null ? null : Number(p.returnOnEquity) * 100;
19206
20292
  const leverage = typeof p.leverage === "number" ? p.leverage : Number(p.leverage?.value);
19207
- return /* @__PURE__ */ React17.createElement("div", { key: `${p.coin}:${i}` }, /* @__PURE__ */ React17.createElement("div", { className: "dhl-posrow dhl-posrow-position" }, /* @__PURE__ */ React17.createElement("span", null, p.coin), /* @__PURE__ */ React17.createElement("span", null, `${p.size > 0 ? "+" : ""}${fmt(p.size, 4)}`), /* @__PURE__ */ React17.createElement("span", null, fmtPrice(p.entryPx)), /* @__PURE__ */ React17.createElement("span", null, p.markPx == null ? "\u2014" : fmtPrice(p.markPx)), /* @__PURE__ */ React17.createElement("span", { className: p.uPnl >= 0 ? "dhl-up" : "dhl-down" }, fmt(p.uPnl)), /* @__PURE__ */ React17.createElement("span", { className: roe == null ? "" : roe >= 0 ? "dhl-up" : "dhl-down" }, roe == null ? "\u2014" : `${fmt(roe)}%`), /* @__PURE__ */ React17.createElement("span", null, p.liquidationPx == null ? "\u2014" : fmtPrice(p.liquidationPx))), account?.mode === "live" ? /* @__PURE__ */ React17.createElement("div", { className: "dhl-position-extra" }, /* @__PURE__ */ React17.createElement("span", null, tr("\u4FDD\u8BC1\u91D1", "Margin"), " ", p.marginUsed == null ? "\u2014" : `${fmt(p.marginUsed)} USDC`), /* @__PURE__ */ React17.createElement("span", null, tr("\u6760\u6746", "Leverage"), " ", Number.isFinite(leverage) && leverage > 0 ? `${leverage}x` : "\u2014"), /* @__PURE__ */ React17.createElement("span", null, tr("\u5F00\u4ED3\u4EE5\u6765\u8D44\u91D1\u8D39", "Funding since open"), " ", p.cumFunding?.sinceOpen == null ? "\u2014" : `${fmt(p.cumFunding.sinceOpen)} USDC`)) : null);
20293
+ return /* @__PURE__ */ React19.createElement("div", { key: `${p.coin}:${i}` }, /* @__PURE__ */ React19.createElement("div", { className: "dhl-posrow dhl-posrow-position" }, /* @__PURE__ */ React19.createElement("span", null, p.coin), /* @__PURE__ */ React19.createElement("span", null, `${p.size > 0 ? "+" : ""}${fmt(p.size, 4)}`), /* @__PURE__ */ React19.createElement("span", null, fmtPrice(p.entryPx)), /* @__PURE__ */ React19.createElement("span", null, p.markPx == null ? "\u2014" : fmtPrice(p.markPx)), /* @__PURE__ */ React19.createElement("span", { className: p.uPnl >= 0 ? "dhl-up" : "dhl-down" }, fmt(p.uPnl)), /* @__PURE__ */ React19.createElement("span", { className: roe == null ? "" : roe >= 0 ? "dhl-up" : "dhl-down" }, roe == null ? "\u2014" : `${fmt(roe)}%`), /* @__PURE__ */ React19.createElement("span", null, p.liquidationPx == null ? "\u2014" : fmtPrice(p.liquidationPx))), account?.mode === "live" ? /* @__PURE__ */ React19.createElement("div", { className: "dhl-position-extra" }, /* @__PURE__ */ React19.createElement("span", null, tr("\u4FDD\u8BC1\u91D1", "Margin"), " ", p.marginUsed == null ? "\u2014" : `${fmt(p.marginUsed)} USDC`), /* @__PURE__ */ React19.createElement("span", null, tr("\u6760\u6746", "Leverage"), " ", Number.isFinite(leverage) && leverage > 0 ? `${leverage}x` : "\u2014"), /* @__PURE__ */ React19.createElement("span", null, tr("\u5F00\u4ED3\u4EE5\u6765\u8D44\u91D1\u8D39", "Funding since open"), " ", p.cumFunding?.sinceOpen == null ? "\u2014" : `${fmt(p.cumFunding.sinceOpen)} USDC`)) : null);
19208
20294
  }));
19209
20295
  } else if (tab === "orders") {
19210
20296
  const rows = orderView === "open" ? orders : orderView === "history" ? history?.orders ?? [] : history?.fills ?? [];
19211
- body = /* @__PURE__ */ React17.createElement("div", null, /* @__PURE__ */ React17.createElement("div", { className: "dhl-history-controls" }, [["open", tr("\u5F53\u524D\u6302\u5355", "Open")], ["history", tr("\u5386\u53F2\u59D4\u6258", "Order history")], ["fills", tr("\u6210\u4EA4\u8BB0\u5F55", "Fills")]].map(([id, label]) => /* @__PURE__ */ React17.createElement("button", { type: "button", key: id, className: orderView === id ? "on" : "", onClick: () => {
20297
+ body = /* @__PURE__ */ React19.createElement("div", null, /* @__PURE__ */ React19.createElement("div", { className: "dhl-history-controls" }, [["open", tr("\u5F53\u524D\u6302\u5355", "Open")], ["history", tr("\u5386\u53F2\u59D4\u6258", "Order history")], ["fills", tr("\u6210\u4EA4\u8BB0\u5F55", "Fills")]].map(([id, label]) => /* @__PURE__ */ React19.createElement("button", { type: "button", key: id, className: orderView === id ? "on" : "", onClick: () => {
19212
20298
  setOrderView(id);
19213
20299
  setHistoryLimit(100);
19214
- } }, label)), /* @__PURE__ */ React17.createElement("button", { type: "button", className: "dhl-history-refresh", onClick: () => void loadTradeHistory(true) }, tr("\u5237\u65B0", "Refresh"))), orderView !== "open" && s.tradeHistoryError ? /* @__PURE__ */ React17.createElement("div", { className: "dhl-inline-error", role: "status" }, s.tradeHistoryError) : null, head(
20300
+ } }, label)), /* @__PURE__ */ React19.createElement("button", { type: "button", className: "dhl-history-refresh", onClick: () => void loadTradeHistory(true) }, tr("\u5237\u65B0", "Refresh"))), orderView !== "open" && s.tradeHistoryError ? /* @__PURE__ */ React19.createElement("div", { className: "dhl-inline-error", role: "status" }, s.tradeHistoryError) : null, head(
19215
20301
  t("orders.coin", "\u5408\u7EA6"),
19216
20302
  t("orders.side", "\u65B9\u5411"),
19217
20303
  t("orders.px", "\u4EF7\u683C"),
19218
20304
  t("orders.sz", "\u6570\u91CF"),
19219
20305
  t("orders.status", "\u72B6\u6001")
19220
- ), rows.length === 0 ? /* @__PURE__ */ React17.createElement("div", { className: "dhl-posrow" }, /* @__PURE__ */ React17.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, orderView !== "open" && !s.tradeHistoryAt && !s.tradeHistoryError ? tr("\u6B63\u5728\u8BFB\u53D6\u5386\u53F2\u2026", "Loading history\u2026") : accountError && orderView === "open" ? tr("\u6302\u5355\u72B6\u6001\u672A\u786E\u8BA4", "Order status unconfirmed") : tr("\u6682\u65E0\u8BB0\u5F55", "No records"))) : null, rows.slice(0, orderView === "open" ? 30 : historyLimit).map((row, index) => {
20306
+ ), rows.length === 0 ? /* @__PURE__ */ React19.createElement("div", { className: "dhl-posrow" }, /* @__PURE__ */ React19.createElement("span", { style: { color: "var(--dsw-alias-label-secondary)" } }, orderView !== "open" && !s.tradeHistoryAt && !s.tradeHistoryError ? tr("\u6B63\u5728\u8BFB\u53D6\u5386\u53F2\u2026", "Loading history\u2026") : accountError && orderView === "open" ? tr("\u6302\u5355\u72B6\u6001\u672A\u786E\u8BA4", "Order status unconfirmed") : tr("\u6682\u65E0\u8BB0\u5F55", "No records"))) : null, rows.slice(0, orderView === "open" ? 30 : historyLimit).map((row, index) => {
19221
20307
  const buy = row.isBuy ?? row.side === "B";
19222
20308
  const time = Number(row.timestamp ?? row.time ?? row.statusTimestamp);
19223
20309
  const reason = orderView === "fills" ? row.reason || history?.orders.find((order) => order.oid != null && order.oid === row.oid)?.reason : row.reason;
19224
20310
  const displayReason = typeof reason === "string" && !reason.startsWith("session:") && !["tool", "chat", "order"].includes(reason) ? reason : "";
19225
- return /* @__PURE__ */ React17.createElement("div", { key: `${orderView}:${row.oid ?? row.tid ?? index}:${time}` }, /* @__PURE__ */ React17.createElement("div", { className: "dhl-posrow" }, /* @__PURE__ */ React17.createElement("span", null, row.coin ?? "\u2014"), /* @__PURE__ */ React17.createElement("span", { className: buy ? "dhl-up" : "dhl-down" }, buy ? t("side.buy", "\u4E70") : t("side.sell", "\u5356")), /* @__PURE__ */ React17.createElement("span", null, fmtPrice(row.limitPx ?? row.px)), /* @__PURE__ */ React17.createElement("span", null, fmt(row.origSz ?? row.sz, 4)), /* @__PURE__ */ React17.createElement("span", { className: "dhl-order-action" }, /* @__PURE__ */ React17.createElement("span", null, orderView === "open" ? t("orders.open", "\u6302\u5355\u4E2D") : orderView === "fills" ? tr("\u5DF2\u6210\u4EA4", "Filled") : String(row.status ?? "\u2014")), orderView === "open" && Number.isInteger(Number(row.oid)) && Number(row.oid) > 0 ? /* @__PURE__ */ React17.createElement("button", { type: "button", className: "dhl-ghost", onClick: () => requestCancel(String(row.coin), Number(row.oid)) }, tr("\u64A4\u5355", "Cancel")) : null)), orderView !== "open" ? /* @__PURE__ */ React17.createElement("div", { className: "dhl-history-detail" }, /* @__PURE__ */ React17.createElement("span", null, Number.isFinite(time) && time > 0 ? new Date(time).toLocaleString() : "\u2014"), orderView === "fills" && row.closedPnl != null ? /* @__PURE__ */ React17.createElement("span", null, "P&L ", fmt(row.closedPnl), " \xB7 ", tr("\u624B\u7EED\u8D39", "Fee"), " ", fmt(row.fee)) : null, displayReason ? /* @__PURE__ */ React17.createElement("span", { title: displayReason }, tr("\u4E0B\u5355\u539F\u56E0", "Order reason"), "\uFF1A", displayReason) : null) : null);
19226
- }), orderView !== "open" && rows.length > historyLimit ? /* @__PURE__ */ React17.createElement("button", { type: "button", className: "dhl-history-more", onClick: () => setHistoryLimit((limit) => limit + 100) }, tr("\u663E\u793A\u66F4\u591A", "Show more"), " \xB7 ", Math.min(historyLimit, rows.length), "/", rows.length) : null);
20311
+ return /* @__PURE__ */ React19.createElement("div", { key: `${orderView}:${row.oid ?? row.tid ?? index}:${time}` }, /* @__PURE__ */ React19.createElement("div", { className: "dhl-posrow" }, /* @__PURE__ */ React19.createElement("span", null, row.coin ?? "\u2014"), /* @__PURE__ */ React19.createElement("span", { className: buy ? "dhl-up" : "dhl-down" }, buy ? t("side.buy", "\u4E70") : t("side.sell", "\u5356")), /* @__PURE__ */ React19.createElement("span", null, fmtPrice(row.limitPx ?? row.px)), /* @__PURE__ */ React19.createElement("span", null, fmt(row.origSz ?? row.sz, 4)), /* @__PURE__ */ React19.createElement("span", { className: "dhl-order-action" }, /* @__PURE__ */ React19.createElement("span", null, orderView === "open" ? t("orders.open", "\u6302\u5355\u4E2D") : orderView === "fills" ? tr("\u5DF2\u6210\u4EA4", "Filled") : String(row.status ?? "\u2014")), orderView === "open" && Number.isInteger(Number(row.oid)) && Number(row.oid) > 0 ? /* @__PURE__ */ React19.createElement("button", { type: "button", className: "dhl-ghost", onClick: () => requestCancel(String(row.coin), Number(row.oid)) }, tr("\u64A4\u5355", "Cancel")) : null)), orderView !== "open" ? /* @__PURE__ */ React19.createElement("div", { className: "dhl-history-detail" }, /* @__PURE__ */ React19.createElement("span", null, Number.isFinite(time) && time > 0 ? new Date(time).toLocaleString() : "\u2014"), orderView === "fills" && row.closedPnl != null ? /* @__PURE__ */ React19.createElement("span", null, "P&L ", fmt(row.closedPnl), " \xB7 ", tr("\u624B\u7EED\u8D39", "Fee"), " ", fmt(row.fee)) : null, displayReason ? /* @__PURE__ */ React19.createElement("span", { title: displayReason }, tr("\u4E0B\u5355\u539F\u56E0", "Order reason"), "\uFF1A", displayReason) : null) : null);
20312
+ }), orderView !== "open" && rows.length > historyLimit ? /* @__PURE__ */ React19.createElement("button", { type: "button", className: "dhl-history-more", onClick: () => setHistoryLimit((limit) => limit + 100) }, tr("\u663E\u793A\u66F4\u591A", "Show more"), " \xB7 ", Math.min(historyLimit, rows.length), "/", rows.length) : null);
20313
+ } else if (tab === "review") {
20314
+ body = /* @__PURE__ */ React19.createElement(TradeReview, null);
19227
20315
  } else {
19228
- body = /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 14, flexWrap: "wrap", fontSize: 11.5, fontVariantNumeric: "tabular-nums", padding: "4px 0" } }, account?.mode === "paper" ? [
19229
- /* @__PURE__ */ React17.createElement("span", { key: "c" }, `${t("account.cash", "\u73B0\u91D1")}: ${fmt(account.usd)}`),
19230
- /* @__PURE__ */ React17.createElement("span", { key: "e" }, `${t("account.equity", "\u6743\u76CA")}: ${fmt(account.equity)}`),
19231
- /* @__PURE__ */ React17.createElement("span", { key: "m" }, `${tr("\u5DF2\u7528\u4FDD\u8BC1\u91D1", "Margin used")}: ${fmt(account.marginUsed)}`),
19232
- /* @__PURE__ */ React17.createElement("span", { key: "a" }, `${tr("\u53EF\u7528\u4FDD\u8BC1\u91D1", "Available margin")}: ${fmt(account.availableMargin)}`),
19233
- /* @__PURE__ */ React17.createElement("span", { key: "u", style: { color: account.uPnl >= 0 ? "var(--dsw-alias-state-success-primary)" : "var(--dsw-alias-state-error-primary)" } }, `uPnL: ${fmt(account.uPnl)}`)
20316
+ body = /* @__PURE__ */ React19.createElement("div", { style: { display: "flex", gap: 14, flexWrap: "wrap", fontSize: 11.5, fontVariantNumeric: "tabular-nums", padding: "4px 0" } }, account?.mode === "paper" ? [
20317
+ /* @__PURE__ */ React19.createElement("span", { key: "c" }, `${t("account.cash", "\u73B0\u91D1")}: ${fmt(account.usd)}`),
20318
+ /* @__PURE__ */ React19.createElement("span", { key: "e" }, `${t("account.equity", "\u6743\u76CA")}: ${fmt(account.equity)}`),
20319
+ /* @__PURE__ */ React19.createElement("span", { key: "m" }, `${tr("\u5DF2\u7528\u4FDD\u8BC1\u91D1", "Margin used")}: ${fmt(account.marginUsed)}`),
20320
+ /* @__PURE__ */ React19.createElement("span", { key: "a" }, `${tr("\u53EF\u7528\u4FDD\u8BC1\u91D1", "Available margin")}: ${fmt(account.availableMargin)}`),
20321
+ /* @__PURE__ */ React19.createElement("span", { key: "u", style: { color: account.uPnl >= 0 ? "var(--dsw-alias-state-success-primary)" : "var(--dsw-alias-state-error-primary)" } }, `uPnL: ${fmt(account.uPnl)}`)
19234
20322
  ] : [
19235
- account?.accountMode ? /* @__PURE__ */ React17.createElement("span", { key: "mode" }, `${tr("\u8D26\u6237\u6A21\u5F0F", "Account mode")}: ${{ unifiedAccount: "Unified", portfolioMargin: "Portfolio", disabled: "Standard", default: "Standard", dexAbstraction: "DEX abstraction" }[account.accountMode] ?? account.accountMode}`) : null,
19236
- account?.accountDex ? /* @__PURE__ */ React17.createElement("span", { key: "dex" }, `${tr("\u6C38\u7EED DEX", "Perp DEX")}: ${account.accountDex}`) : null,
19237
- account?.marginSummary ? /* @__PURE__ */ React17.createElement("span", { key: "v" }, `${account.accountDex ? tr("DEX \u62B5\u62BC\u8D26\u6237\u4EF7\u503C", "DEX collateral value") : t("account.value", "\u8D26\u6237\u4EF7\u503C")}: ${fmt(account.marginSummary.accountValue)}`) : null,
19238
- account?.balanceSource === "spot" ? /* @__PURE__ */ React17.createElement("span", { key: "usdc" }, `${tr("USDC \u4F59\u989D", "USDC balance")}: ${fmt(account.usdcBalance)} \xB7 ${tr("\u672A\u5360\u7528", "Unheld")}: ${fmt(account.unheldUsdc)}`) : account ? /* @__PURE__ */ React17.createElement("span", { key: "a" }, `${account.accountDex ? tr("DEX \u53EF\u7528", "DEX available") : t("account.available", "\u53EF\u7528")}: ${fmt(account.withdrawable)}`) : null,
19239
- account?.address ? /* @__PURE__ */ React17.createElement("span", { key: "addr", style: { color: "var(--dsw-alias-label-secondary)" } }, account.address.slice(0, 6) + "\u2026" + account.address.slice(-4)) : null
20323
+ account?.accountMode ? /* @__PURE__ */ React19.createElement("span", { key: "mode" }, `${tr("\u8D26\u6237\u6A21\u5F0F", "Account mode")}: ${{ unifiedAccount: "Unified", portfolioMargin: "Portfolio", disabled: "Standard", default: "Standard", dexAbstraction: "DEX abstraction" }[account.accountMode] ?? account.accountMode}`) : null,
20324
+ account?.accountDex ? /* @__PURE__ */ React19.createElement("span", { key: "dex" }, `${tr("\u6C38\u7EED DEX", "Perp DEX")}: ${account.accountDex}`) : null,
20325
+ account?.marginSummary ? /* @__PURE__ */ React19.createElement("span", { key: "v" }, `${account.accountDex ? tr("DEX \u62B5\u62BC\u8D26\u6237\u4EF7\u503C", "DEX collateral value") : t("account.value", "\u8D26\u6237\u4EF7\u503C")}: ${fmt(account.marginSummary.accountValue)}`) : null,
20326
+ account?.balanceSource === "spot" ? /* @__PURE__ */ React19.createElement("span", { key: "usdc" }, `${tr("USDC \u4F59\u989D", "USDC balance")}: ${fmt(account.usdcBalance)} \xB7 ${tr("\u672A\u5360\u7528", "Unheld")}: ${fmt(account.unheldUsdc)}`) : account ? /* @__PURE__ */ React19.createElement("span", { key: "a" }, `${account.accountDex ? tr("DEX \u53EF\u7528", "DEX available") : t("account.available", "\u53EF\u7528")}: ${fmt(account.withdrawable)}`) : null,
20327
+ account?.address ? /* @__PURE__ */ React19.createElement("span", { key: "addr", style: { color: "var(--dsw-alias-label-secondary)" } }, account.address.slice(0, 6) + "\u2026" + account.address.slice(-4)) : null
19240
20328
  ]);
19241
20329
  }
19242
- return /* @__PURE__ */ React17.createElement("div", { className: "dhl-card", style: { margin: "0 14px 12px", flex: "none" } }, accountError ? /* @__PURE__ */ React17.createElement("div", { className: "dhl-account-warning", role: "status" }, tr("\u8D26\u6237\u4E0E\u6302\u5355\u72B6\u6001\u672A\u786E\u8BA4", "Account and order status unconfirmed"), "\uFF1A", accountError) : null, /* @__PURE__ */ React17.createElement("div", { style: { display: "flex", gap: 2, marginBottom: 6 } }, tabs.map(([id, label]) => /* @__PURE__ */ React17.createElement("button", { key: id, className: "dhl-tab" + (tab === id ? " on" : ""), onClick: () => saveSessionWorkspace({ bottomTab: id }) }, label))), body, account?.mode === "paper" ? /* @__PURE__ */ React17.createElement("p", { className: "dhl-ticket-note" }, tr(account.simulation, "Fixed slippage; order book depth, fees, funding and liquidation are not simulated.")) : null);
20330
+ return /* @__PURE__ */ React19.createElement("div", { className: "dhl-card", style: { margin: "0 14px 12px", flex: "none" } }, accountError ? /* @__PURE__ */ React19.createElement("div", { className: "dhl-account-warning", role: "status" }, tr("\u8D26\u6237\u4E0E\u6302\u5355\u72B6\u6001\u672A\u786E\u8BA4", "Account and order status unconfirmed"), "\uFF1A", accountError) : null, /* @__PURE__ */ React19.createElement("div", { style: { display: "flex", gap: 2, marginBottom: 6 } }, tabs.map(([id, label]) => /* @__PURE__ */ React19.createElement("button", { key: id, className: "dhl-tab" + (tab === id ? " on" : ""), onClick: () => saveSessionWorkspace({ bottomTab: id }) }, label))), body, account?.mode === "paper" ? /* @__PURE__ */ React19.createElement("p", { className: "dhl-ticket-note" }, tr(account.simulation, "Configured slippage and fees; depth, funding and liquidation are not simulated.")) : null);
19243
20331
  }
19244
20332
 
19245
20333
  // src/client/components/market-picker.tsx
19246
- var React19 = __toESM(require("react"), 1);
19247
- var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
20334
+ var React21 = __toESM(require("react"), 1);
20335
+ var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
19248
20336
 
19249
20337
  // src/client/components/market-icon.tsx
19250
- var React18 = __toESM(require("react"), 1);
20338
+ var React20 = __toESM(require("react"), 1);
19251
20339
  var ICON_ROOT = "https://app.hyperliquid.xyz/coins/";
19252
20340
  function iconNames(market) {
19253
20341
  if (market.kind === "outcome") return [];
@@ -19259,10 +20347,10 @@ function iconNames(market) {
19259
20347
  function MarketIcon({ market, small = false }) {
19260
20348
  const names = iconNames(market);
19261
20349
  const identity = `${market.kind}:${market.name}:${names.join(",")}`;
19262
- const [state, setState] = React18.useState({ identity, index: 0, loaded: false });
20350
+ const [state, setState] = React20.useState({ identity, index: 0, loaded: false });
19263
20351
  const current2 = state.identity === identity ? state : { identity, index: 0, loaded: false };
19264
20352
  const name = names[current2.index];
19265
- return /* @__PURE__ */ React18.createElement("span", { className: "dhl-market-icon" + (small ? " dhl-market-icon-small" : ""), "aria-hidden": "true" }, name ? /* @__PURE__ */ React18.createElement(
20353
+ return /* @__PURE__ */ React20.createElement("span", { className: "dhl-market-icon" + (small ? " dhl-market-icon-small" : ""), "aria-hidden": "true" }, name ? /* @__PURE__ */ React20.createElement(
19266
20354
  "img",
19267
20355
  {
19268
20356
  key: `${identity}:${current2.index}`,
@@ -19313,19 +20401,19 @@ function interestOf(row) {
19313
20401
  }
19314
20402
  function MarketPicker({ activeCoin, onClose }) {
19315
20403
  const s = useStore();
19316
- const [query, setQuery] = React19.useState("");
19317
- const [category, setCategory] = React19.useState("all");
19318
- const [strict, setStrict] = React19.useState(true);
19319
- const [sort, setSort] = React19.useState("volume");
19320
- const [descending, setDescending] = React19.useState(true);
19321
- const [favorites, setFavorites] = React19.useState(readFavorites);
19322
- const [cursor, setCursor] = React19.useState(0);
19323
- const [loading, setLoading] = React19.useState(false);
19324
- const [renderCount, setRenderCount] = React19.useState(150);
19325
- const searchRef = React19.useRef(null);
19326
- const listRef = React19.useRef(null);
19327
- const keyboardScroll = React19.useRef(false);
19328
- React19.useEffect(() => {
20404
+ const [query, setQuery] = React21.useState("");
20405
+ const [category, setCategory] = React21.useState("all");
20406
+ const [strict, setStrict] = React21.useState(true);
20407
+ const [sort, setSort] = React21.useState("volume");
20408
+ const [descending, setDescending] = React21.useState(true);
20409
+ const [favorites, setFavorites] = React21.useState(readFavorites);
20410
+ const [cursor, setCursor] = React21.useState(0);
20411
+ const [loading, setLoading] = React21.useState(false);
20412
+ const [renderCount, setRenderCount] = React21.useState(150);
20413
+ const searchRef = React21.useRef(null);
20414
+ const listRef = React21.useRef(null);
20415
+ const keyboardScroll = React21.useRef(false);
20416
+ React21.useEffect(() => {
19329
20417
  searchRef.current?.focus();
19330
20418
  setLoading(true);
19331
20419
  void loadMarketCatalog().catch((error) => notify(tr("\u5E02\u573A\u76EE\u5F55\u52A0\u8F7D\u5931\u8D25", "Market catalog load failed") + "\uFF1A" + String(error))).finally(() => setLoading(false));
@@ -19335,7 +20423,7 @@ function MarketPicker({ activeCoin, onClose }) {
19335
20423
  }, 5e3);
19336
20424
  return () => window.clearInterval(timer);
19337
20425
  }, []);
19338
- const marketRows = React19.useMemo(() => {
20426
+ const marketRows = React21.useMemo(() => {
19339
20427
  const livePerps = new Map(s.markets.map((market) => [market.name, market]));
19340
20428
  const catalog = s.marketCatalog.length ? s.marketCatalog.map((row) => {
19341
20429
  const latest = row.tradable ? livePerps.get(row.name) : null;
@@ -19390,20 +20478,20 @@ function MarketPicker({ activeCoin, onClose }) {
19390
20478
  if (category === "trending" && !needle) rows.sort((a, b) => n(b.ctx?.dayNtlVlm) - n(a.ctx?.dayNtlVlm));
19391
20479
  return rows;
19392
20480
  }, [s.marketCatalog, s.markets, query, category, strict, sort, descending, favorites]);
19393
- React19.useEffect(() => {
20481
+ React21.useEffect(() => {
19394
20482
  setCursor(0);
19395
20483
  setRenderCount(150);
19396
20484
  listRef.current?.scrollTo({ top: 0 });
19397
20485
  }, [query, category, strict, sort, descending]);
19398
- React19.useEffect(() => {
20486
+ React21.useEffect(() => {
19399
20487
  if (cursor >= renderCount) setRenderCount(Math.ceil((cursor + 1) / 150) * 150);
19400
20488
  }, [cursor, renderCount]);
19401
- React19.useLayoutEffect(() => {
20489
+ React21.useLayoutEffect(() => {
19402
20490
  if (!keyboardScroll.current || cursor >= renderCount) return;
19403
20491
  listRef.current?.querySelector(`[data-market-index="${cursor}"]`)?.scrollIntoView({ block: "nearest" });
19404
20492
  keyboardScroll.current = false;
19405
20493
  }, [cursor, renderCount]);
19406
- const choose = React19.useCallback((row) => {
20494
+ const choose = React21.useCallback((row) => {
19407
20495
  onClose();
19408
20496
  void setDeskCoin(row.name).catch(() => {
19409
20497
  });
@@ -19418,11 +20506,11 @@ function MarketPicker({ activeCoin, onClose }) {
19418
20506
  return next;
19419
20507
  });
19420
20508
  };
19421
- const sortBy = (key2) => {
19422
- if (key2 === sort) setDescending((value) => !value);
20509
+ const sortBy = (key3) => {
20510
+ if (key3 === sort) setDescending((value) => !value);
19423
20511
  else {
19424
- setSort(key2);
19425
- setDescending(key2 !== "name");
20512
+ setSort(key3);
20513
+ setDescending(key3 !== "name");
19426
20514
  }
19427
20515
  };
19428
20516
  const onKeyDown = (event) => {
@@ -19451,21 +20539,21 @@ function MarketPicker({ activeCoin, onClose }) {
19451
20539
  choose(marketRows[cursor]);
19452
20540
  }
19453
20541
  };
19454
- const header = (label, key2) => /* @__PURE__ */ React19.createElement("button", { type: "button", onClick: () => sortBy(key2), "aria-label": tr(`\u6309${label}\u6392\u5E8F`, `Sort by ${label}`) }, label, sort === key2 ? /* @__PURE__ */ React19.createElement(UiIcon, { name: descending ? "arrowDown" : "arrowUp", size: 12 }) : null);
19455
- return /* @__PURE__ */ React19.createElement(import_dsh_client_ui_primitives2.Modal, { open: true, onClose, title: t("market.choose", "\u9009\u62E9\u5E02\u573A"), headless: true, className: "dhl-picker-modal", backdropBlur: true, onKeyDownCapture: onKeyDown }, /* @__PURE__ */ React19.createElement("section", { className: "dhl-picker" }, /* @__PURE__ */ React19.createElement("div", { className: "dhl-picker-top" }, /* @__PURE__ */ React19.createElement("div", { className: "dhl-picker-search" }, /* @__PURE__ */ React19.createElement(SearchIcon, null), /* @__PURE__ */ React19.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__ */ React19.createElement("kbd", null, "/")), /* @__PURE__ */ React19.createElement("div", { className: "dhl-picker-mode", role: "group", "aria-label": tr("\u5E02\u573A\u8303\u56F4", "Market scope") }, /* @__PURE__ */ React19.createElement("button", { type: "button", className: strict ? "on" : "", "aria-pressed": strict, onClick: () => setStrict(true) }, t("market.tradable", "\u53EF\u4EA4\u6613")), /* @__PURE__ */ React19.createElement("button", { type: "button", className: !strict ? "on" : "", "aria-pressed": !strict, onClick: () => setStrict(false) }, t("market.all", "\u5168\u90E8"))), /* @__PURE__ */ React19.createElement("button", { type: "button", className: "dhl-picker-close", onClick: onClose, "aria-label": t("modal.close", "\u5173\u95ED") }, /* @__PURE__ */ React19.createElement(UiIcon, { name: "close", size: 18 }))), !strict ? /* @__PURE__ */ React19.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__ */ React19.createElement("nav", { className: "dhl-picker-tabs", "aria-label": tr("\u5E02\u573A\u5206\u7C7B", "Market categories") }, CATEGORIES.map(([key2, label]) => /* @__PURE__ */ React19.createElement("button", { type: "button", key: key2, className: category === key2 ? "on" : "", "aria-current": category === key2 ? "page" : void 0, onClick: () => {
19456
- setCategory(key2);
19457
- if (["spot", "hip3", "outcome", "crypto", "tradfi", "preipo"].includes(key2)) setStrict(false);
19458
- } }, t(`market.category.${key2}`, label)))), /* @__PURE__ */ React19.createElement("div", { className: "dhl-picker-tablehead" }, /* @__PURE__ */ React19.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__ */ React19.createElement("div", { className: "dhl-picker-list", role: "listbox", "aria-label": tr("\u5E02\u573A\u5217\u8868", "Market list"), ref: listRef, onScroll: (event) => {
20542
+ 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: () => {
20544
+ setCategory(key3);
20545
+ if (["spot", "hip3", "outcome", "crypto", "tradfi", "preipo"].includes(key3)) setStrict(false);
20546
+ } }, 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) => {
19459
20547
  const element = event.currentTarget;
19460
20548
  if (element.scrollTop + element.clientHeight >= element.scrollHeight - 200) setRenderCount((count) => Math.min(marketRows.length, count + 150));
19461
20549
  } }, marketRows.slice(0, renderCount).map((row, index) => {
19462
20550
  const change = changeOf(row);
19463
- return /* @__PURE__ */ React19.createElement("div", { className: "dhl-picker-row" + (row.name === activeCoin ? " active" : "") + (cursor === index ? " cursor" : ""), role: "option", "aria-selected": row.name === activeCoin, key: `${row.kind}:${row.name}`, "data-market-index": index, onMouseEnter: () => setCursor(index) }, /* @__PURE__ */ React19.createElement("button", { type: "button", className: "dhl-picker-star" + (favorites.includes(row.name) ? " on" : ""), "aria-label": `${favorites.includes(row.name) ? tr("\u53D6\u6D88\u81EA\u9009", "Remove favorite") : tr("\u52A0\u5165\u81EA\u9009", "Add favorite")} ${row.label}`, onClick: () => toggleFavorite(row.name) }, /* @__PURE__ */ React19.createElement(UiIcon, { name: "star", size: 17, filled: favorites.includes(row.name) })), /* @__PURE__ */ React19.createElement("button", { type: "button", className: "dhl-picker-market", onClick: () => choose(row), title: `${row.label} \xB7 ${row.name} \xB7 ${row.venue}${row.category ? ` \xB7 ${row.category}` : ""}` }, /* @__PURE__ */ React19.createElement(MarketIcon, { market: row, small: true }), /* @__PURE__ */ React19.createElement("strong", null, row.label), /* @__PURE__ */ React19.createElement("small", null, row.kind === "perp" ? "PERP" : row.kind === "hip3" ? "HIP-3" : row.kind === "outcome" ? "OUTCOME" : "SPOT", row.name.startsWith("@") ? ` ${row.name}` : "", row.category ? ` \xB7 ${row.category}` : "", row.tradable ? "" : ` \xB7 ${tr("\u53EA\u8BFB", "View only")}`)), /* @__PURE__ */ React19.createElement("span", null, n(row.ctx?.markPx) > 0 ? fmtPrice(row.ctx.markPx) : "\u2014"), /* @__PURE__ */ React19.createElement("span", { className: change > 0 ? "dhl-up" : change < 0 ? "dhl-down" : "" }, n(row.ctx?.prevDayPx) > 0 ? `${change >= 0 ? "+" : ""}${change.toFixed(2)}%` : "\u2014"), /* @__PURE__ */ React19.createElement("span", null, row.ctx?.funding != null ? `${(n(row.ctx.funding) * 100).toFixed(4)}%` : "\u2014"), /* @__PURE__ */ React19.createElement("span", null, row.ctx?.dayNtlVlm != null ? "$" + fmt(row.ctx.dayNtlVlm, 0) : "\u2014"), /* @__PURE__ */ React19.createElement("span", null, row.ctx?.openInterest != null ? "$" + fmt(interestOf(row), 0) : "\u2014"));
19464
- }), !loading && marketRows.length === 0 ? /* @__PURE__ */ React19.createElement("div", { className: "dhl-picker-empty" }, category === "outcome" && s.status?.network === "mainnet" ? t("market.outcomeTestnet", "Outcome \u76EE\u5F55\u76EE\u524D\u4EC5\u5728 testnet \u63D0\u4F9B\u3002") : t("market.empty", "\u6CA1\u6709\u5339\u914D\u7684\u5E02\u573A")) : null, loading && !s.marketCatalog.length ? /* @__PURE__ */ React19.createElement("div", { className: "dhl-picker-empty" }, t("market.loading", "\u6B63\u5728\u8BFB\u53D6\u5E02\u573A\u76EE\u5F55\u2026")) : null), /* @__PURE__ */ React19.createElement("footer", { className: "dhl-picker-foot" }, /* @__PURE__ */ React19.createElement("span", null, t("market.shortcuts", "\u2191\u2193 \u9009\u62E9\u3000Enter \u6253\u5F00\u3000\u2318/Ctrl+S \u81EA\u9009\u3000Esc \u5173\u95ED")), /* @__PURE__ */ React19.createElement("span", null, marketRows.length, " ", t("market.count", "\u4E2A\u5E02\u573A"), " \xB7 Hyperliquid \xB7 ", s.status?.network ?? "Hyperliquid"))));
20551
+ return /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-row" + (row.name === activeCoin ? " active" : "") + (cursor === index ? " cursor" : ""), role: "option", "aria-selected": row.name === activeCoin, key: `${row.kind}:${row.name}`, "data-market-index": index, onMouseEnter: () => setCursor(index) }, /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-picker-star" + (favorites.includes(row.name) ? " on" : ""), "aria-label": `${favorites.includes(row.name) ? tr("\u53D6\u6D88\u81EA\u9009", "Remove favorite") : tr("\u52A0\u5165\u81EA\u9009", "Add favorite")} ${row.label}`, onClick: () => toggleFavorite(row.name) }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "star", size: 17, filled: favorites.includes(row.name) })), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-picker-market", onClick: () => choose(row), title: `${row.label} \xB7 ${row.name} \xB7 ${row.venue}${row.category ? ` \xB7 ${row.category}` : ""}` }, /* @__PURE__ */ React21.createElement(MarketIcon, { market: row, small: true }), /* @__PURE__ */ React21.createElement("strong", null, row.label), /* @__PURE__ */ React21.createElement("small", null, row.kind === "perp" ? "PERP" : row.kind === "hip3" ? "HIP-3" : row.kind === "outcome" ? "OUTCOME" : "SPOT", row.name.startsWith("@") ? ` ${row.name}` : "", row.category ? ` \xB7 ${row.category}` : "", row.tradable ? "" : ` \xB7 ${tr("\u53EA\u8BFB", "View only")}`)), /* @__PURE__ */ React21.createElement("span", null, n(row.ctx?.markPx) > 0 ? fmtPrice(row.ctx.markPx) : "\u2014"), /* @__PURE__ */ React21.createElement("span", { className: change > 0 ? "dhl-up" : change < 0 ? "dhl-down" : "" }, n(row.ctx?.prevDayPx) > 0 ? `${change >= 0 ? "+" : ""}${change.toFixed(2)}%` : "\u2014"), /* @__PURE__ */ React21.createElement("span", null, row.ctx?.funding != null ? `${(n(row.ctx.funding) * 100).toFixed(4)}%` : "\u2014"), /* @__PURE__ */ React21.createElement("span", null, row.ctx?.dayNtlVlm != null ? "$" + fmt(row.ctx.dayNtlVlm, 0) : "\u2014"), /* @__PURE__ */ React21.createElement("span", null, row.ctx?.openInterest != null ? "$" + fmt(interestOf(row), 0) : "\u2014"));
20552
+ }), !loading && marketRows.length === 0 ? /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-empty" }, category === "outcome" && s.status?.network === "mainnet" ? t("market.outcomeTestnet", "Outcome \u76EE\u5F55\u76EE\u524D\u4EC5\u5728 testnet \u63D0\u4F9B\u3002") : t("market.empty", "\u6CA1\u6709\u5339\u914D\u7684\u5E02\u573A")) : null, loading && !s.marketCatalog.length ? /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-empty" }, t("market.loading", "\u6B63\u5728\u8BFB\u53D6\u5E02\u573A\u76EE\u5F55\u2026")) : null), /* @__PURE__ */ React21.createElement("footer", { className: "dhl-picker-foot" }, /* @__PURE__ */ React21.createElement("span", null, t("market.shortcuts", "\u2191\u2193 \u9009\u62E9\u3000Enter \u6253\u5F00\u3000\u2318/Ctrl+S \u81EA\u9009\u3000Esc \u5173\u95ED")), /* @__PURE__ */ React21.createElement("span", null, marketRows.length, " ", t("market.count", "\u4E2A\u5E02\u573A"), " \xB7 Hyperliquid \xB7 ", s.status?.network ?? "Hyperliquid"))));
19465
20553
  }
19466
20554
 
19467
20555
  // src/client/components/strategy-menu.tsx
19468
- var React20 = __toESM(require("react"), 1);
20556
+ var React22 = __toESM(require("react"), 1);
19469
20557
 
19470
20558
  // src/client/strategy-theories.ts
19471
20559
  var MINUTES = {
@@ -19623,13 +20711,13 @@ var STATE_LABELS = {
19623
20711
  insufficient: ["\u6570\u636E\u4E0D\u8DB3", "Insufficient data"]
19624
20712
  };
19625
20713
  function StrategyMenu({ candles, coin, interval }) {
19626
- const menu = React20.useRef(null);
20714
+ const menu = React22.useRef(null);
19627
20715
  const english = tr("zh", "en") === "en";
19628
- const studies = React20.useMemo(() => evaluateStrategyStudies(candles, coin, interval, Date.now(), english), [candles, coin, interval, english]);
19629
- return /* @__PURE__ */ React20.createElement("details", { className: "dhl-strategy-menu", ref: menu }, /* @__PURE__ */ React20.createElement("summary", null, tr("\u7B56\u7565\u6846\u67B6", "Strategies"), /* @__PURE__ */ React20.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React20.createElement("div", { className: "dhl-strategy-popover", role: "group", "aria-label": tr("\u4EA4\u6613\u7B56\u7565\u7814\u7A76", "Trading strategy studies") }, /* @__PURE__ */ React20.createElement("header", null, /* @__PURE__ */ React20.createElement("strong", null, tr("\u7B56\u7565\u7814\u7A76 \xB7 \u5DF2\u6536\u76D8 K \u7EBF", "Strategy studies \xB7 closed candles")), /* @__PURE__ */ React20.createElement("small", null, coin, " \xB7 ", interval)), /* @__PURE__ */ React20.createElement("p", { className: "dhl-strategy-caveat" }, tr(
20716
+ const studies = React22.useMemo(() => evaluateStrategyStudies(candles, coin, interval, Date.now(), english), [candles, coin, interval, english]);
20717
+ 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(
19630
20718
  "\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",
19631
20719
  "These conditions structure analysis; returns are unverified for this market and interval. Check higher timeframe, book, funding and costs."
19632
- )), studies.map((study) => /* @__PURE__ */ React20.createElement("section", { className: "dhl-strategy-card", key: study.id }, /* @__PURE__ */ React20.createElement("div", { className: "dhl-strategy-card-head" }, /* @__PURE__ */ React20.createElement("b", null, study.title), /* @__PURE__ */ React20.createElement("span", { className: `state-${study.state}` }, tr(...STATE_LABELS[study.state]))), /* @__PURE__ */ React20.createElement("p", null, study.summary), study.evidence.map((item) => /* @__PURE__ */ React20.createElement("small", { key: item }, item)), study.evidence.length ? /* @__PURE__ */ React20.createElement("small", null, tr("\u5931\u6548\uFF1A", "Invalidation: "), study.invalidation) : null, /* @__PURE__ */ React20.createElement("div", { className: "dhl-strategy-card-actions" }, /* @__PURE__ */ React20.createElement("a", { href: study.source, target: "_blank", rel: "noopener noreferrer" }, tr("\u7406\u8BBA\u6765\u6E90", "Source")), /* @__PURE__ */ React20.createElement("button", { type: "button", disabled: study.state === "stale" || study.state === "insufficient", onClick: () => {
20720
+ )), studies.map((study) => /* @__PURE__ */ React22.createElement("section", { className: "dhl-strategy-card", key: study.id }, /* @__PURE__ */ React22.createElement("div", { className: "dhl-strategy-card-head" }, /* @__PURE__ */ React22.createElement("b", null, study.title), /* @__PURE__ */ React22.createElement("span", { className: `state-${study.state}` }, tr(...STATE_LABELS[study.state]))), /* @__PURE__ */ React22.createElement("p", null, study.summary), study.evidence.map((item) => /* @__PURE__ */ React22.createElement("small", { key: item }, item)), study.evidence.length ? /* @__PURE__ */ React22.createElement("small", null, tr("\u5931\u6548\uFF1A", "Invalidation: "), study.invalidation) : null, /* @__PURE__ */ React22.createElement("div", { className: "dhl-strategy-card-actions" }, /* @__PURE__ */ React22.createElement("a", { href: study.source, target: "_blank", rel: "noopener noreferrer" }, tr("\u7406\u8BBA\u6765\u6E90", "Source")), /* @__PURE__ */ React22.createElement("button", { type: "button", disabled: study.state === "stale" || study.state === "insufficient", onClick: () => {
19633
20721
  if (queueToAgent(study.prompt)) menu.current?.removeAttribute("open");
19634
20722
  } }, tr("\u8BF7 Agent \u6838\u5BF9", "Ask agent to verify")))))));
19635
20723
  }
@@ -19695,6 +20783,226 @@ function chartTradeEvents(candles, coin, orders, fills) {
19695
20783
  return out.sort((a, b) => a.candleTime - b.candleTime || a.time - b.time).slice(-120);
19696
20784
  }
19697
20785
 
20786
+ // src/client/components/indicator-editor.tsx
20787
+ var React23 = __toESM(require("react"), 1);
20788
+ var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
20789
+ var SECTIONS = [
20790
+ ["rsi", "RSI", [["period", "\u5468\u671F", "Length"], ["lower", "\u4E0B\u9608\u503C", "Lower"], ["upper", "\u4E0A\u9608\u503C", "Upper"]]],
20791
+ ["macd", "MACD", [["fast", "\u5FEB\u7EBF", "Fast"], ["slow", "\u6162\u7EBF", "Slow"], ["signal", "\u4FE1\u53F7\u7EBF", "Signal"]]],
20792
+ ["bollinger", "BOLL", [["period", "\u5468\u671F", "Length"], ["multiplier", "\u6807\u51C6\u5DEE\u500D\u6570", "Std. deviations"]]],
20793
+ ["stochastic", "Stochastic", [["period", "%K \u5468\u671F", "%K length"], ["smoothK", "%K \u5E73\u6ED1", "%K smoothing"], ["smoothD", "%D \u5468\u671F", "%D length"], ["lower", "\u4E0B\u9608\u503C", "Lower"], ["upper", "\u4E0A\u9608\u503C", "Upper"]]],
20794
+ ["adx", "ADX / DMI", [["period", "DI \u5468\u671F", "DI length"], ["smoothing", "ADX \u5E73\u6ED1", "ADX smoothing"]]]
20795
+ ];
20796
+ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
20797
+ const [open, setOpen] = React23.useState(false);
20798
+ const [draft, setDraft] = React23.useState({});
20799
+ const [active, setActive] = React23.useState(indicators);
20800
+ const [lines, setLines] = React23.useState(maLines);
20801
+ const [templates, setTemplates] = React23.useState([]);
20802
+ const [selected, setSelected] = React23.useState("");
20803
+ const [name, setName] = React23.useState("");
20804
+ const [error, setError] = React23.useState("");
20805
+ 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)]))])));
20807
+ React23.useEffect(() => {
20808
+ if (!open) return;
20809
+ populate(settings);
20810
+ setActive(indicators);
20811
+ setLines(maLines);
20812
+ setSelected("");
20813
+ setName("");
20814
+ setError("");
20815
+ let cancelled = false;
20816
+ void hyperCall("indicatorTemplates", { action: "list" }).then((result) => {
20817
+ if (!cancelled) setTemplates(result.templates);
20818
+ }).catch((cause) => {
20819
+ if (!cancelled) setError(String(cause.message ?? cause));
20820
+ });
20821
+ return () => {
20822
+ cancelled = true;
20823
+ };
20824
+ }, [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]))])));
20826
+ const apply2 = () => {
20827
+ try {
20828
+ onApply({ indicatorSettings: values(), indicators: active, maLines: lines });
20829
+ setOpen(false);
20830
+ } catch (cause) {
20831
+ setError(String(cause.message));
20832
+ }
20833
+ };
20834
+ const save = async (overwrite = false) => {
20835
+ setError("");
20836
+ setBusy(true);
20837
+ try {
20838
+ const result = await hyperCall("indicatorTemplates", { action: "save", name, ...overwrite ? { id: selected } : {}, indicators: active, maLines: lines, indicatorSettings: values() });
20839
+ setTemplates(result.templates);
20840
+ setSelected(result.template.id);
20841
+ } catch (cause) {
20842
+ setError(String(cause.message));
20843
+ } finally {
20844
+ setBusy(false);
20845
+ }
20846
+ };
20847
+ return /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement("button", { className: "dhl-indicator-settings-trigger", type: "button", onClick: () => setOpen(true) }, /* @__PURE__ */ React23.createElement(UiIcon, { name: "settings", size: 14 }), tr("\u53C2\u6570\u4E0E\u6A21\u677F", "Settings & templates")), /* @__PURE__ */ React23.createElement(
20848
+ import_dsh_client_ui_primitives4.Modal,
20849
+ {
20850
+ open,
20851
+ onClose: () => setOpen(false),
20852
+ title: tr("\u6307\u6807\u53C2\u6570\u4E0E\u6A21\u677F", "Indicator settings & templates"),
20853
+ closeLabel: tr("\u5173\u95ED", "Close"),
20854
+ className: "dhl-indicator-settings-modal",
20855
+ footer: /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement(import_dsh_client_ui_primitives4.Button, { variant: "outline", onClick: () => populate() }, tr("\u9ED8\u8BA4\u53C2\u6570", "Default parameters")), /* @__PURE__ */ React23.createElement(import_dsh_client_ui_primitives4.Button, { variant: "primary", onClick: apply2 }, tr("\u5E94\u7528\u6307\u6807\u8BBE\u7F6E", "Apply indicator settings")))
20856
+ },
20857
+ /* @__PURE__ */ React23.createElement("div", { className: "dhl-study-settings" }, /* @__PURE__ */ React23.createElement("section", { className: "dhl-study-template" }, /* @__PURE__ */ React23.createElement("strong", null, tr("\u6307\u6807\u6A21\u677F", "Indicator templates")), /* @__PURE__ */ React23.createElement("small", null, tr("\u6A21\u677F\u5305\u542B\u5DF2\u542F\u7528\u6307\u6807\u3001\u5168\u90E8\u53C2\u6570\u4E0E\u81EA\u5B9A\u4E49\u5747\u7EBF\uFF1B\u4FDD\u5B58\u5230\u5BBF\u4E3B\uFF0C\u53EF\u8DE8\u4F1A\u8BDD\u4F7F\u7528\u3002", "Templates include enabled studies, parameters and custom MAs, saved on the host for reuse across sessions.")), /* @__PURE__ */ React23.createElement(Select, { "aria-label": tr("\u5DF2\u4FDD\u5B58\u6307\u6807\u6A21\u677F", "Saved indicator templates"), value: selected, onChange: (event) => {
20858
+ const id = event.target.value;
20859
+ setSelected(id);
20860
+ setError("");
20861
+ const template = templates.find((row) => row.id === id);
20862
+ if (template) {
20863
+ populate(template.indicatorSettings);
20864
+ setActive(template.indicators);
20865
+ setLines(template.maLines);
20866
+ setName(template.name);
20867
+ }
20868
+ } }, /* @__PURE__ */ React23.createElement("option", { value: "" }, tr("\u9009\u62E9\u6A21\u677F", "Select template")), templates.map((row) => /* @__PURE__ */ React23.createElement("option", { key: row.id, value: row.id }, row.name))), /* @__PURE__ */ React23.createElement(import_dsh_client_ui_primitives4.Input, { "aria-label": tr("\u6307\u6807\u6A21\u677F\u540D\u79F0", "Indicator template name"), value: name, maxLength: 60, placeholder: tr("\u6A21\u677F\u540D\u79F0", "Template name"), onChange: (event) => setName(event.target.value) }), /* @__PURE__ */ React23.createElement("div", { className: "dhl-study-template-actions" }, /* @__PURE__ */ React23.createElement(import_dsh_client_ui_primitives4.Button, { variant: "outline", disabled: busy || !name.trim(), onClick: () => void save() }, tr("\u53E6\u5B58\u6A21\u677F", "Save new")), /* @__PURE__ */ React23.createElement(import_dsh_client_ui_primitives4.Button, { variant: "outline", disabled: busy || !selected || !name.trim(), onClick: () => void save(true) }, tr("\u66F4\u65B0\u6A21\u677F", "Update")), /* @__PURE__ */ React23.createElement(import_dsh_client_ui_primitives4.Button, { variant: "outline", disabled: busy || !selected, onClick: () => {
20869
+ setBusy(true);
20870
+ void hyperCall("indicatorTemplates", { action: "remove", id: selected }).then((result) => {
20871
+ setTemplates(result.templates);
20872
+ setSelected("");
20873
+ }).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)
20875
+ ));
20876
+ }
20877
+
20878
+ // src/client/components/research-pane.tsx
20879
+ var React24 = __toESM(require("react"), 1);
20880
+ var RESEARCH_INTERVALS = ["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "8h", "12h", "1d", "3d", "1w", "1M"];
20881
+ var durations = [6e4, 18e4, 3e5, 9e5, 18e5, 36e5, 72e5, 144e5, 288e5, 432e5, 864e5, 2592e5, 6048e5, 26784e5];
20882
+ var merge2 = (a, b) => [...new Map([...a, ...b].filter((c) => Number.isFinite(c.t) && [c.o, c.h, c.l, c.c, c.v].every(Number.isFinite)).map((c) => [c.t, c])).values()].sort((a2, b2) => a2.t - b2.t).slice(-5e3);
20883
+ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, chartType, onAnalyzeFill }) {
20884
+ const s = useStore(), scope = `${s.activeSessionId}:${s.status?.network}:${coin}:${interval}`;
20885
+ const [state, setState] = React24.useState({ scope: "", candles: [], error: "", older: "idle", at: 0 });
20886
+ const currentScope = React24.useRef(scope);
20887
+ currentScope.current = scope;
20888
+ const feed = state.scope === scope ? state : { scope, candles: [], error: "", older: "idle", at: 0 };
20889
+ React24.useEffect(() => {
20890
+ let alive = true, busy = false;
20891
+ setState({ scope, candles: [], error: "", older: "idle", at: 0 });
20892
+ const load = async () => {
20893
+ if (busy || document.hidden) return;
20894
+ busy = true;
20895
+ try {
20896
+ const result = await hyperCall("candles", { coin, interval });
20897
+ if (alive) setState((old) => ({ ...old, scope, candles: merge2(old.scope === scope ? old.candles : [], result.candles), error: "", at: Date.now() }));
20898
+ } catch (cause) {
20899
+ if (alive) setState((old) => ({ ...old, scope, error: String(cause?.message ?? cause) }));
20900
+ } finally {
20901
+ busy = false;
20902
+ }
20903
+ };
20904
+ void load();
20905
+ const timer = setInterval(() => void load(), 12e3);
20906
+ return () => {
20907
+ alive = false;
20908
+ clearInterval(timer);
20909
+ };
20910
+ }, [scope]);
20911
+ const olderBusy = React24.useRef(false);
20912
+ const needOlder = async () => {
20913
+ if (olderBusy.current || feed.older !== "idle" || !feed.candles.length) return;
20914
+ olderBusy.current = true;
20915
+ setState((old) => ({ ...old, older: "loading" }));
20916
+ try {
20917
+ const first = feed.candles[0].t;
20918
+ const result = await hyperCall("candles", { coin, interval, endMs: first - 1, startMs: first - durations[RESEARCH_INTERVALS.indexOf(interval)] * 500 });
20919
+ if (currentScope.current === scope) setState((old) => {
20920
+ const rows = merge2(result.candles, old.candles);
20921
+ return { ...old, candles: rows, older: !rows.length || rows[0].t >= first || rows.length >= 5e3 ? "done" : "idle" };
20922
+ });
20923
+ } catch (cause) {
20924
+ if (currentScope.current === scope) setState((old) => ({ ...old, older: "error", error: String(cause?.message ?? cause) }));
20925
+ } finally {
20926
+ olderBusy.current = false;
20927
+ }
20928
+ };
20929
+ 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(
20932
+ CandleChart,
20933
+ {
20934
+ key: scope,
20935
+ candles: feed.candles,
20936
+ marks,
20937
+ indicators,
20938
+ expanded: false,
20939
+ compact: true,
20940
+ readOnly: true,
20941
+ resetToken: 0,
20942
+ chartType,
20943
+ caption: `${coin} ${interval}`,
20944
+ link,
20945
+ indicatorSettings: s.workspace?.indicatorSettings,
20946
+ showDataWindow: s.workspace?.showDataWindow,
20947
+ maLines: s.workspace?.maLines ?? [],
20948
+ priceScale: s.workspace?.priceScale ?? "normal",
20949
+ visibleRange: s.workspace?.chartRanges?.[`${coin}:${interval}`] ?? null,
20950
+ onVisibleRange: (range) => saveSessionWorkspace({ chartRanges: { [`${coin}:${interval}`]: range } }),
20951
+ drawMode: null,
20952
+ onDraw: () => {
20953
+ },
20954
+ onSelect: (selection) => insertChartReference(selection, coin, interval),
20955
+ onNeedOlder: () => void needOlder(),
20956
+ olderState: feed.older,
20957
+ tradeEvents: events,
20958
+ showReasons: layers.fills && layers.reasons,
20959
+ onAnalyzeFill,
20960
+ magnet: "off",
20961
+ drawingsLocked: true,
20962
+ onEditMark: () => {
20963
+ },
20964
+ onRemoveMark: () => {
20965
+ },
20966
+ onCloneMark: () => {
20967
+ },
20968
+ selectRequest: null,
20969
+ focus: null
20970
+ }
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);
20972
+ }
20973
+
20974
+ // lib/workspace-settings.js
20975
+ var INTERVALS2 = ["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "8h", "12h", "1d", "3d", "1w", "1M"];
20976
+ function normalizeResearch(input = {}) {
20977
+ const next = { layout: "single", intervals: ["1h", "4h", "1d"], syncTime: true, syncCursor: true, ...input };
20978
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !["layout", "intervals", "syncTime", "syncCursor"].includes(key3)) || !["single", "dual", "quad"].includes(next.layout) || !Array.isArray(next.intervals) || next.intervals.length !== 3 || next.intervals.some((iv) => !INTERVALS2.includes(iv)) || typeof next.syncTime !== "boolean" || typeof next.syncCursor !== "boolean") throw Error("\u591A\u5468\u671F\u7814\u7A76\u914D\u7F6E\u65E0\u6548");
20979
+ return next;
20980
+ }
20981
+
20982
+ // src/client/components/chart-navigation.tsx
20983
+ var React25 = __toESM(require("react"), 1);
20984
+ var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
20985
+ function ChartNavigation({ onNavigate, onCsv, disabled }) {
20986
+ const [date, setDate] = React25.useState("");
20987
+ const dateMenu = React25.useRef(null);
20988
+ const exportMenu = React25.useRef(null);
20989
+ const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
20990
+ const go = (event) => {
20991
+ event.preventDefault();
20992
+ const time = new Date(date).getTime();
20993
+ if (!Number.isFinite(time)) return;
20994
+ onNavigate({ kind: "date", time });
20995
+ dateMenu.current?.removeAttribute("open");
20996
+ };
20997
+ return /* @__PURE__ */ React25.createElement("div", { className: "dhl-chart-navigation", "aria-label": tr("\u56FE\u8868\u5BFC\u822A", "Chart navigation") }, /* @__PURE__ */ React25.createElement("div", { className: "dhl-chart-ranges", role: "group", "aria-label": tr("\u663E\u793A\u65F6\u95F4\u8303\u56F4", "Visible time range") }, [["1D", 1], ["5D", 5], ["1M", 30]].map(([name, days]) => /* @__PURE__ */ React25.createElement("button", { key: name, type: "button", disabled, title: tr(`\u67E5\u770B\u6700\u8FD1 ${days} \u5929`, `Show the last ${days} days`), onClick: () => onNavigate({ kind: "range", duration: days * 864e5 }) }, name)), /* @__PURE__ */ React25.createElement("button", { type: "button", disabled, title: tr("\u9002\u5E94\u5DF2\u52A0\u8F7D\u7684\u5168\u90E8 K \u7EBF", "Fit all loaded candles"), onClick: () => onNavigate({ kind: "fit" }) }, tr("\u5168\u90E8", "All"))), /* @__PURE__ */ React25.createElement("details", { ref: dateMenu, className: "dhl-chart-footer-menu" }, /* @__PURE__ */ React25.createElement("summary", { "aria-label": tr("\u8DF3\u8F6C\u5230\u65E5\u671F", "Go to date"), title: tr("\u8DF3\u8F6C\u5230\u65E5\u671F", "Go to date") }, /* @__PURE__ */ React25.createElement(UiIcon, { name: "calendar", size: 15 })), /* @__PURE__ */ React25.createElement("form", { className: "dhl-chart-date-form", onSubmit: go }, /* @__PURE__ */ React25.createElement("label", { htmlFor: "dhl-chart-date" }, tr("\u8DF3\u8F6C\u5230\u65E5\u671F", "Go to date")), /* @__PURE__ */ React25.createElement("small", null, tr("\u672C\u5730\u65F6\u95F4", "Local time"), " \xB7 ", zone), /* @__PURE__ */ React25.createElement(import_dsh_client_ui_primitives5.Input, { id: "dhl-chart-date", type: "datetime-local", step: "60", value: date, onChange: (event) => setDate(event.target.value), required: true }), /* @__PURE__ */ React25.createElement("button", { type: "submit", disabled: disabled || !date }, tr("\u5B9A\u4F4D K \u7EBF", "Find candle")), /* @__PURE__ */ React25.createElement("small", null, tr("\u8D85\u51FA\u5DF2\u52A0\u8F7D\u8303\u56F4\u65F6\u4F1A\u8865\u53D6\u5386\u53F2\u6570\u636E\uFF0C\u53D7\u4EA4\u6613\u6240\u5386\u53F2\u4E0A\u9650\u9650\u5236\u3002", "Earlier data is loaded as needed, up to the exchange history limit.")))), /* @__PURE__ */ React25.createElement("button", { type: "button", className: "dhl-chart-live", disabled, onClick: () => onNavigate({ kind: "live" }) }, /* @__PURE__ */ React25.createElement(UiIcon, { name: "live", size: 14 }), tr("\u56DE\u5230\u6700\u65B0", "Latest")), /* @__PURE__ */ React25.createElement("span", { className: "dhl-chart-footer-spacer" }), /* @__PURE__ */ React25.createElement("details", { ref: exportMenu, className: "dhl-chart-footer-menu" }, /* @__PURE__ */ React25.createElement("summary", { "aria-label": tr("\u5BFC\u51FA\u56FE\u8868", "Export chart"), title: tr("\u5BFC\u51FA\u56FE\u8868", "Export chart") }, /* @__PURE__ */ React25.createElement(UiIcon, { name: "download", size: 15 }), tr("\u5BFC\u51FA", "Export")), /* @__PURE__ */ React25.createElement("div", { className: "dhl-chart-export-menu" }, /* @__PURE__ */ React25.createElement("button", { type: "button", disabled, onClick: () => {
20998
+ onNavigate({ kind: "snapshot" });
20999
+ exportMenu.current?.removeAttribute("open");
21000
+ } }, /* @__PURE__ */ React25.createElement(UiIcon, { name: "camera", size: 15 }), tr("\u56FE\u7247 PNG\uFF08\u542B\u753B\u7EBF\uFF09", "PNG image with drawings")), /* @__PURE__ */ React25.createElement("button", { type: "button", disabled, onClick: () => {
21001
+ onCsv();
21002
+ exportMenu.current?.removeAttribute("open");
21003
+ } }, /* @__PURE__ */ React25.createElement(UiIcon, { name: "download", size: 15 }), tr("\u5DF2\u52A0\u8F7D K \u7EBF CSV", "Loaded candles as CSV")))), /* @__PURE__ */ React25.createElement("details", { className: "dhl-chart-footer-menu" }, /* @__PURE__ */ React25.createElement("summary", { "aria-label": tr("\u56FE\u8868\u64CD\u4F5C\u5E2E\u52A9", "Chart shortcuts"), title: tr("\u56FE\u8868\u64CD\u4F5C\u5E2E\u52A9", "Chart shortcuts") }, /* @__PURE__ */ React25.createElement(UiIcon, { name: "help", size: 15 })), /* @__PURE__ */ React25.createElement("div", { className: "dhl-chart-shortcuts" }, /* @__PURE__ */ React25.createElement("strong", null, tr("\u56FE\u8868\u64CD\u4F5C", "Chart controls")), [[tr("\u79FB\u52A8 / \u7F29\u653E", "Pan / Zoom"), tr("\u62D6\u52A8 / \u6EDA\u8F6E", "Drag / Wheel")], [tr("\u64A4\u9500 / \u91CD\u505A", "Undo / Redo"), "Ctrl/\u2318 Z \xB7 Shift Ctrl/\u2318 Z"], [tr("\u590D\u5236 / \u7C98\u8D34\u753B\u7EBF", "Copy / Paste drawing"), "Ctrl/\u2318 C \xB7 Ctrl/\u2318 V"], [tr("\u5220\u9664\u9009\u4E2D\u753B\u7EBF", "Delete drawing"), "Delete / Backspace"], [tr("\u6C34\u5E73\u7EA6\u675F", "Horizontal constraint"), "Shift"], [tr("\u53D6\u6D88\u7ED8\u5236 / \u9009\u62E9", "Cancel drawing / selection"), "Esc"], [tr("\u641C\u7D22\u6807\u7684", "Search symbols"), "/"]].map(([label, key3]) => /* @__PURE__ */ React25.createElement("div", { key: label }, /* @__PURE__ */ React25.createElement("span", null, label), /* @__PURE__ */ React25.createElement("kbd", null, key3))))));
21004
+ }
21005
+
19698
21006
  // src/client/components/desk-column.tsx
19699
21007
  var INDICATOR_CHOICES = [
19700
21008
  ["volume", "\u6210\u4EA4\u91CF", "K \u7EBF\u4E0B\u65B9\u7684\u6210\u4EA4\u91CF\u67F1"],
@@ -19704,7 +21012,12 @@ var INDICATOR_CHOICES = [
19704
21012
  ["ema50", "EMA 50", "50 \u6839\u6536\u76D8\u4EF7\u7684\u6307\u6570\u5747\u7EBF"],
19705
21013
  ["bollinger20", "\u5E03\u6797\u5E26 20", "\u5747\u7EBF\u53CA\u4E0A\u4E0B\u4E24\u500D\u6807\u51C6\u5DEE"],
19706
21014
  ["rsi14", "RSI 14", "\u76F8\u5BF9\u5F3A\u5F31\u6307\u6570\u526F\u56FE"],
19707
- ["macd", "MACD", "12 / 26 / 9 \u52A8\u91CF\u526F\u56FE"]
21015
+ ["macd", "MACD", "12 / 26 / 9 \u52A8\u91CF\u526F\u56FE"],
21016
+ ["stochastic", "Stochastic", "%K \u4E0E %D \u968F\u673A\u6307\u6807"],
21017
+ ["adx", "ADX / DMI", "\u8D8B\u52BF\u5F3A\u5EA6\u4E0E\u6B63\u8D1F\u65B9\u5411\u6307\u6807"],
21018
+ ["obv", "OBV", "\u4EE5\u52A0\u8F7D\u7A97\u53E3\u9996\u6839\u4E3A\u96F6\u70B9\u7684\u7D2F\u79EF\u80FD\u91CF\u6F6E"],
21019
+ ["atr14", "ATR 14", "Wilder \u5E73\u6ED1\u7684\u771F\u5B9E\u6CE2\u5E45\uFF0C\u8861\u91CF\u4EF7\u683C\u6CE2\u52A8"],
21020
+ ["vwap", "VWAP \xB7 UTC", "UTC \u6BCF\u65E5\u91CD\u7F6E\uFF0C\u4EE5 HLC3 \u548C\u6210\u4EA4\u91CF\u8BA1\u7B97\uFF1B\u81EA\u52A8\u8865\u53D6\u5F53\u65E5 K \u7EBF"]
19708
21021
  ];
19709
21022
  var INDICATOR_EN = {
19710
21023
  volume: ["Volume", "Volume bars below the candles"],
@@ -19714,25 +21027,30 @@ var INDICATOR_EN = {
19714
21027
  ema50: ["EMA 50", "50-close exponential moving average"],
19715
21028
  bollinger20: ["Bollinger 20", "Mean and two standard deviation bands"],
19716
21029
  rsi14: ["RSI 14", "Relative strength index"],
19717
- macd: ["MACD", "12 / 26 / 9 momentum panel"]
21030
+ macd: ["MACD", "12 / 26 / 9 momentum panel"],
21031
+ stochastic: ["Stochastic", "%K and %D oscillator"],
21032
+ adx: ["ADX / DMI", "Trend strength and directional movement"],
21033
+ obv: ["OBV", "Cumulative signed volume; zero starts at the first loaded candle"],
21034
+ atr14: ["ATR 14", "Wilder average true range, a measure of volatility"],
21035
+ vwap: ["VWAP \xB7 UTC", "Daily UTC session, HLC3 weighted by volume; loads today\u2019s bars as needed"]
19718
21036
  };
19719
21037
  function usePreference(name, fallback, parse) {
19720
- const key2 = `dsh-hyperliquid.draw.${name}`;
19721
- const [value, setValue] = React21.useState(() => {
21038
+ const key3 = `dsh-hyperliquid.draw.${name}`;
21039
+ const [value, setValue] = React26.useState(() => {
19722
21040
  try {
19723
- const raw = localStorage.getItem(key2);
21041
+ const raw = localStorage.getItem(key3);
19724
21042
  return raw == null ? fallback : parse(JSON.parse(raw));
19725
21043
  } catch {
19726
21044
  return fallback;
19727
21045
  }
19728
21046
  });
19729
- const update = React21.useCallback((next) => {
21047
+ const update = React26.useCallback((next) => {
19730
21048
  setValue(next);
19731
21049
  try {
19732
- localStorage.setItem(key2, JSON.stringify(next));
21050
+ localStorage.setItem(key3, JSON.stringify(next));
19733
21051
  } catch {
19734
21052
  }
19735
- }, [key2]);
21053
+ }, [key3]);
19736
21054
  return [value, update];
19737
21055
  }
19738
21056
  function finite(value) {
@@ -19757,34 +21075,55 @@ function DeskColumn({ onCollapse }) {
19757
21075
  const funding = finite(ctx?.funding);
19758
21076
  const change = mark != null && mark > 0 && prev != null && prev > 0 ? (mark / prev - 1) * 100 : null;
19759
21077
  const chartTab = s.workspaceSessionId === s.activeSessionId ? s.workspace?.chartTab ?? "chart" : "chart";
19760
- const indicators = React21.useMemo(() => new Set(s.workspaceSessionId === s.activeSessionId ? s.workspace?.indicators ?? ["volume"] : ["volume"]), [s.workspace, s.workspaceSessionId, s.activeSessionId]);
19761
- const [drawMode, setDrawMode] = React21.useState(null);
19762
- const drawMenu = React21.useRef(null);
21078
+ const indicators = React26.useMemo(() => new Set(s.workspaceSessionId === s.activeSessionId ? s.workspace?.indicators ?? ["volume"] : ["volume"]), [s.workspace, s.workspaceSessionId, s.activeSessionId]);
21079
+ const [drawMode, setDrawMode] = React26.useState(null);
21080
+ const [favorites, setFavorites] = usePreference("favorites", ["trend", "level", "fib", "rect", "measure", "long", "short"], (value) => Array.isArray(value) ? [...new Set(value.filter((kind) => DRAWING_TOOLS.some((tool) => tool.kind === kind)))].slice(0, 12) : []);
21081
+ const chartScope = `${s.activeSessionId}:${coin}:${s.interval}`;
21082
+ const [chartCommand, setChartCommand] = React26.useState(null);
21083
+ const chartType = s.workspace?.chartType ?? "candles";
21084
+ const studySettings = React26.useMemo(() => normalizeIndicatorSettings(s.workspace?.indicatorSettings), [s.workspace?.indicatorSettings]);
21085
+ const research = React26.useMemo(() => normalizeResearch(s.workspace?.research), [s.workspace?.research]);
21086
+ const researchScope = `${s.activeSessionId}:${s.workspaceSessionId}:${s.status?.network}:${coin}`;
21087
+ const [cursorEvent, setCursorEvent] = React26.useState(null);
21088
+ const [rangeEvent, setRangeEvent] = React26.useState(null);
21089
+ const makeLink = (id) => ({
21090
+ id,
21091
+ cursor: research.syncCursor && cursorEvent?.scope === researchScope ? cursorEvent.value : null,
21092
+ range: research.syncTime && rangeEvent?.scope === researchScope ? rangeEvent.value : null,
21093
+ onCursor: (time) => {
21094
+ if (research.syncCursor) setCursorEvent((old) => old?.scope === researchScope && old.value.source === id && old.value.time === time ? old : { scope: researchScope, value: { source: id, time, at: performance.now() } });
21095
+ },
21096
+ onRange: (range) => {
21097
+ if (research.syncTime) setRangeEvent({ scope: researchScope, value: { source: id, at: performance.now(), ...range } });
21098
+ }
21099
+ });
21100
+ const researchCount = research.layout === "quad" ? 3 : research.layout === "dual" ? 1 : 0;
21101
+ const drawMenu = React26.useRef(null);
19763
21102
  const [magnet, setMagnet] = usePreference("magnet", "off", (value) => value === "weak" || value === "strong" ? value : "off");
19764
21103
  const [stayDrawing, setStayDrawing] = usePreference("stayDrawing", false, (value) => value === true);
19765
21104
  const [drawingsLocked, setDrawingsLocked] = usePreference("drawingsLocked", false, (value) => value === true);
19766
- const [textAt, setTextAt] = React21.useState(null);
19767
- const [selectRequest, setSelectRequest] = React21.useState(null);
19768
- const [textValue, setTextValue] = React21.useState("");
19769
- const [confirmClear, setConfirmClear] = React21.useState(false);
19770
- const [pickerOpen, setPickerOpen] = React21.useState(false);
19771
- const [chartExpanded, setChartExpanded] = React21.useState(false);
19772
- const [chartResetToken, setChartResetToken] = React21.useState(0);
21105
+ const [textAt, setTextAt] = React26.useState(null);
21106
+ const [selectRequest, setSelectRequest] = React26.useState(null);
21107
+ const [textValue, setTextValue] = React26.useState("");
21108
+ const [confirmClear, setConfirmClear] = React26.useState(false);
21109
+ const [pickerOpen, setPickerOpen] = React26.useState(false);
21110
+ const [chartExpanded, setChartExpanded] = React26.useState(false);
21111
+ const [chartResetToken, setChartResetToken] = React26.useState(0);
19773
21112
  const sessionMaLines = s.workspaceSessionId === s.activeSessionId ? s.workspace?.maLines : void 0;
19774
- const maLines = React21.useMemo(() => sessionMaLines ?? [], [sessionMaLines]);
21113
+ const maLines = React26.useMemo(() => sessionMaLines ?? [], [sessionMaLines]);
19775
21114
  const toggleIndicator = (id) => {
19776
21115
  const next = new Set(indicators);
19777
21116
  if (next.has(id)) next.delete(id);
19778
21117
  else next.add(id);
19779
21118
  saveSessionWorkspace({ indicators: [...next] });
19780
21119
  };
19781
- React21.useEffect(() => {
21120
+ React26.useEffect(() => {
19782
21121
  setDrawMode(null);
19783
21122
  setChartExpanded(false);
19784
21123
  }, [s.activeSessionId]);
19785
21124
  const marks = s.chartMarksKey === `${s.activeSessionId ?? ""}:${coin}` ? s.chartMarks : [];
19786
21125
  const layers = { orders: true, fills: true, drawings: true, reasons: false, ...s.workspace?.chartLayers };
19787
- const tradeEvents = React21.useMemo(
21126
+ const tradeEvents = React26.useMemo(
19788
21127
  () => chartTradeEvents(
19789
21128
  s.candles,
19790
21129
  coin,
@@ -19793,15 +21132,15 @@ function DeskColumn({ onCollapse }) {
19793
21132
  ),
19794
21133
  [s.candles, coin, s.tradeHistory, layers.orders, layers.fills]
19795
21134
  );
19796
- const stayRef = React21.useRef(stayDrawing);
21135
+ const stayRef = React26.useRef(stayDrawing);
19797
21136
  stayRef.current = stayDrawing;
19798
21137
  const history = useDrawingHistory(`${s.activeSessionId ?? ""}:${coin}`);
19799
- const historyRef = React21.useRef(history);
21138
+ const historyRef = React26.useRef(history);
19800
21139
  historyRef.current = history;
19801
- const save = React21.useCallback((mark2, label) => {
21140
+ const save = React26.useCallback((mark2, label) => {
19802
21141
  void historyRef.current.create({ ...loadStyleTemplate(mark2.kind), ...mark2, label });
19803
21142
  }, []);
19804
- const draw = React21.useCallback((mark2) => {
21143
+ const draw = React26.useCallback((mark2) => {
19805
21144
  if (!stayRef.current) setDrawMode(null);
19806
21145
  if ((isTextKind(mark2.kind) || mark2.kind === "icon") && "points" in mark2) {
19807
21146
  setTextValue("");
@@ -19810,7 +21149,7 @@ function DeskColumn({ onCollapse }) {
19810
21149
  }
19811
21150
  void save(mark2, mark2.kind === "level" ? drawingLabel("level", [], mark2.price) : mark2.kind === "trend" ? drawingLabel("trend", [{ time: mark2.fromTime, price: mark2.fromPrice }]) : drawingLabel(mark2.kind, mark2.points));
19812
21151
  }, [save]);
19813
- const analyzeFill = React21.useCallback((event) => {
21152
+ const analyzeFill = React26.useCallback((event) => {
19814
21153
  const state = store.get();
19815
21154
  const known = (id) => Boolean(id && state.sessions.some((row2) => row2.sessionId === id) && !state.archivedSessionIds.includes(id));
19816
21155
  const target = known(event.sessionId) ? event.sessionId : state.activeSessionId;
@@ -19839,21 +21178,21 @@ function DeskColumn({ onCollapse }) {
19839
21178
  notify(tr("\u5DF2\u5207\u6362\u5230\u505A\u51FA\u8FD9\u7B14\u51B3\u7B56\u7684\u4F1A\u8BDD\uFF0C\u590D\u76D8\u5F15\u7528\u5DF2\u653E\u5165\u8F93\u5165\u6846", "Switched to the session that made this decision; the review is in the composer"));
19840
21179
  }
19841
21180
  }, []);
19842
- const editMark = React21.useCallback((id, patch) => historyRef.current.edit(id, patch), []);
19843
- const erase = React21.useCallback((id) => historyRef.current.remove(id), []);
19844
- const cloneMark = React21.useCallback((copy) => {
21181
+ const editMark = React26.useCallback((id, patch) => historyRef.current.edit(id, patch), []);
21182
+ const erase = React26.useCallback((id) => historyRef.current.remove(id), []);
21183
+ const cloneMark = React26.useCallback((copy) => {
19845
21184
  void historyRef.current.create({ kind: copy.kind, label: copy.label, ...copy.geometry, ...copy.style });
19846
21185
  }, []);
19847
- React21.useEffect(() => {
21186
+ React26.useEffect(() => {
19848
21187
  const onKey = (event) => {
19849
21188
  if (!(event.metaKey || event.ctrlKey) || event.altKey) return;
19850
21189
  const element = event.target;
19851
21190
  if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
19852
- const key2 = event.key.toLowerCase();
19853
- if (key2 === "z" && !event.shiftKey) {
21191
+ const key3 = event.key.toLowerCase();
21192
+ if (key3 === "z" && !event.shiftKey) {
19854
21193
  event.preventDefault();
19855
21194
  void historyRef.current.undo();
19856
- } else if (key2 === "z" && event.shiftKey || key2 === "y") {
21195
+ } else if (key3 === "z" && event.shiftKey || key3 === "y") {
19857
21196
  event.preventDefault();
19858
21197
  void historyRef.current.redo();
19859
21198
  }
@@ -19861,7 +21200,7 @@ function DeskColumn({ onCollapse }) {
19861
21200
  window.addEventListener("keydown", onKey);
19862
21201
  return () => window.removeEventListener("keydown", onKey);
19863
21202
  }, []);
19864
- React21.useEffect(() => {
21203
+ React26.useEffect(() => {
19865
21204
  const onKey = (event) => {
19866
21205
  if (event.key !== "/" || event.metaKey || event.ctrlKey || event.altKey || pickerOpen) return;
19867
21206
  const element = event.target;
@@ -19872,10 +21211,10 @@ function DeskColumn({ onCollapse }) {
19872
21211
  window.addEventListener("keydown", onKey);
19873
21212
  return () => window.removeEventListener("keydown", onKey);
19874
21213
  }, [pickerOpen]);
19875
- React21.useEffect(() => {
21214
+ React26.useEffect(() => {
19876
21215
  if (!chartExpanded) return void 0;
19877
21216
  const onEscape = (event) => {
19878
- if (event.key === "Escape" && !pickerOpen) {
21217
+ if (event.key === "Escape" && !pickerOpen && !event.defaultPrevented) {
19879
21218
  event.preventDefault();
19880
21219
  setChartExpanded(false);
19881
21220
  }
@@ -19883,25 +21222,52 @@ function DeskColumn({ onCollapse }) {
19883
21222
  window.addEventListener("keydown", onEscape);
19884
21223
  return () => window.removeEventListener("keydown", onEscape);
19885
21224
  }, [chartExpanded, pickerOpen]);
19886
- React21.useEffect(() => {
21225
+ React26.useEffect(() => {
21226
+ const keys = { KeyT: "trend", KeyH: "level", KeyJ: "hray", KeyV: "vline", KeyC: "crossline", KeyF: "fib" };
21227
+ const onKey = (event) => {
21228
+ if (!event.altKey || event.ctrlKey || event.metaKey || chartTab !== "chart") return;
21229
+ const element = event.target;
21230
+ if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
21231
+ const kind = event.shiftKey ? event.code === "KeyR" ? "rect" : void 0 : keys[event.code];
21232
+ if (!kind) return;
21233
+ event.preventDefault();
21234
+ setDrawMode(kind);
21235
+ };
21236
+ window.addEventListener("keydown", onKey);
21237
+ return () => window.removeEventListener("keydown", onKey);
21238
+ }, [chartTab]);
21239
+ React26.useEffect(() => {
19887
21240
  if (!drawMode) return void 0;
19888
21241
  const cancel = (event) => {
19889
- if (event.key === "Escape") setDrawMode(null);
21242
+ if (event.key === "Escape") {
21243
+ event.preventDefault();
21244
+ setDrawMode(null);
21245
+ }
19890
21246
  };
19891
- window.addEventListener("keydown", cancel);
19892
- return () => window.removeEventListener("keydown", cancel);
21247
+ window.addEventListener("keydown", cancel, true);
21248
+ return () => window.removeEventListener("keydown", cancel, true);
19893
21249
  }, [drawMode]);
19894
- React21.useEffect(() => {
21250
+ React26.useEffect(() => {
19895
21251
  if (chartTab === "funding" && row?.kind !== "spot" && row?.kind !== "outcome" && store.get().fundingCoin !== coin) void loadFunding();
19896
21252
  if (chartTab === "funding" && (row?.kind === "spot" || row?.kind === "outcome")) saveSessionWorkspace({ chartTab: "chart" });
19897
21253
  }, [chartTab, coin, row?.kind]);
19898
- React21.useEffect(() => {
21254
+ React26.useEffect(() => {
21255
+ if (!indicators.has("vwap") || s.candlesOlder !== "idle" || s.candles.length < 2) return;
21256
+ const start = Math.floor(s.candles.at(-1).t / 864e5) * 864e5;
21257
+ if (s.candles[0].t > start) void loadOlderCandles();
21258
+ }, [indicators, s.candles, s.candlesOlder]);
21259
+ React26.useEffect(() => {
21260
+ if (s.candlesOlder !== "idle" || s.candles.length < 2) return;
21261
+ const earliest = Math.min(...s.chartMarks.filter((mark2) => !mark2.hidden && (mark2.kind === "vprofile" || mark2.kind === "regression")).map((mark2) => Math.min(...(mark2.points ?? []).map((point) => point.time))));
21262
+ if (Number.isFinite(earliest) && s.candles[0].t > earliest) void loadOlderCandles();
21263
+ }, [s.chartMarks, s.candles, s.candlesOlder]);
21264
+ React26.useEffect(() => {
19899
21265
  void loadChartMarks();
19900
21266
  }, [s.activeSessionId, coin, s.interval]);
19901
- React21.useEffect(() => {
21267
+ React26.useEffect(() => {
19902
21268
  void loadTradeHistory();
19903
21269
  }, [s.status?.network, s.status?.tradingMode]);
19904
- const changeInterval = React21.useCallback((next) => {
21270
+ const changeInterval = React26.useCallback((next) => {
19905
21271
  store.patch({ interval: next });
19906
21272
  pushDeskInterval(next);
19907
21273
  void loadCandles();
@@ -19914,24 +21280,24 @@ function DeskColumn({ onCollapse }) {
19914
21280
  [tr("\u672A\u5E73\u4ED3\u91CF", "Open interest"), openInterest == null || mark == null ? "\u2014" : `$${fmt(openInterest * mark, 0)}`],
19915
21281
  [tr("\u8D44\u91D1\u8D39 / h", "Funding / h"), funding == null ? "\u2014" : `${(funding * 100).toFixed(4)}%`]
19916
21282
  ];
19917
- return /* @__PURE__ */ React21.createElement("main", { id: "dhl-panel-desk", className: "dhl-col-desk" }, /* @__PURE__ */ React21.createElement("header", { className: "dhl-market-strip" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-market-name" }, /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-market-trigger", "aria-haspopup": "dialog", "aria-expanded": pickerOpen, onClick: () => setPickerOpen(true) }, /* @__PURE__ */ React21.createElement(MarketIcon, { market: row ?? { name: coin, label: `${coin}-USDC`, kind: "perp" } }), /* @__PURE__ */ React21.createElement("strong", null, row?.label ?? `${coin}-USDC`), /* @__PURE__ */ React21.createElement("span", { className: "dhl-market-chevron" }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "chevronDown", size: 16 }))), /* @__PURE__ */ React21.createElement("span", { className: "dhl-market-kind" }, row?.kind === "spot" ? "SPOT" : row?.kind === "hip3" ? "HIP-3" : row?.kind === "outcome" ? "OUTCOME" : "PERP"), row?.maxLeverage ? /* @__PURE__ */ React21.createElement("span", { className: "dhl-market-tag" }, row.maxLeverage, "x") : null, row?.tradable === false ? /* @__PURE__ */ React21.createElement("span", { className: "dhl-market-readonly" }, tr("\u53EA\u8BFB", "View only")) : null), /* @__PURE__ */ React21.createElement("div", { className: "dhl-market-stats" }, stats.map(([label, value, tone]) => /* @__PURE__ */ React21.createElement("div", { className: "dhl-market-stat", key: label }, /* @__PURE__ */ React21.createElement("span", null, label), /* @__PURE__ */ React21.createElement("strong", { className: tone ?? "" }, value)))), /* @__PURE__ */ React21.createElement("button", { className: "dhl-panel-iconbtn", type: "button", onClick: onCollapse, "aria-label": tr("\u6536\u8D77\u884C\u60C5\u533A", "Collapse market panel"), title: tr("\u6536\u8D77\u884C\u60C5\u533A", "Collapse market panel") }, /* @__PURE__ */ React21.createElement(FoldIcon, { direction: "right" }))), /* @__PURE__ */ React21.createElement(
19918
- import_dsh_client_ui_primitives3.Modal,
21283
+ return /* @__PURE__ */ React26.createElement("main", { id: "dhl-panel-desk", className: "dhl-col-desk" }, /* @__PURE__ */ React26.createElement("header", { className: "dhl-market-strip" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-market-name" }, /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-market-trigger", "aria-haspopup": "dialog", "aria-expanded": pickerOpen, onClick: () => setPickerOpen(true) }, /* @__PURE__ */ React26.createElement(MarketIcon, { market: row ?? { name: coin, label: `${coin}-USDC`, kind: "perp" } }), /* @__PURE__ */ React26.createElement("strong", null, row?.label ?? `${coin}-USDC`), /* @__PURE__ */ React26.createElement("span", { className: "dhl-market-chevron" }, /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 16 }))), /* @__PURE__ */ React26.createElement("span", { className: "dhl-market-kind" }, row?.kind === "spot" ? "SPOT" : row?.kind === "hip3" ? "HIP-3" : row?.kind === "outcome" ? "OUTCOME" : "PERP"), row?.maxLeverage ? /* @__PURE__ */ React26.createElement("span", { className: "dhl-market-tag" }, row.maxLeverage, "x") : null, row?.tradable === false ? /* @__PURE__ */ React26.createElement("span", { className: "dhl-market-readonly" }, tr("\u53EA\u8BFB", "View only")) : null), /* @__PURE__ */ React26.createElement("div", { className: "dhl-market-stats" }, stats.map(([label, value, tone]) => /* @__PURE__ */ React26.createElement("div", { className: "dhl-market-stat", key: label }, /* @__PURE__ */ React26.createElement("span", null, label), /* @__PURE__ */ React26.createElement("strong", { className: tone ?? "" }, value)))), /* @__PURE__ */ React26.createElement("button", { className: "dhl-panel-iconbtn", type: "button", onClick: onCollapse, "aria-label": tr("\u6536\u8D77\u884C\u60C5\u533A", "Collapse market panel"), title: tr("\u6536\u8D77\u884C\u60C5\u533A", "Collapse market panel") }, /* @__PURE__ */ React26.createElement(FoldIcon, { direction: "right" }))), /* @__PURE__ */ React26.createElement(
21284
+ import_dsh_client_ui_primitives6.Modal,
19919
21285
  {
19920
21286
  open: Boolean(textAt),
19921
21287
  onClose: () => setTextAt(null),
19922
21288
  title: textAt?.kind === "icon" ? tr("\u9009\u62E9\u56FE\u6807", "Pick an icon") : textAt?.kind === "callout" ? tr("\u6DFB\u52A0\u6807\u6CE8", "Add callout") : tr("\u6DFB\u52A0\u6587\u672C", "Add text"),
19923
21289
  closeLabel: tr("\u5173\u95ED", "Close"),
19924
21290
  className: "dhl-rename-modal",
19925
- footer: /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(import_dsh_client_ui_primitives3.Button, { variant: "outline", onClick: () => setTextAt(null) }, tr("\u53D6\u6D88", "Cancel")), /* @__PURE__ */ React21.createElement(import_dsh_client_ui_primitives3.Button, { variant: "primary", disabled: !textValue.trim(), onClick: () => {
21291
+ footer: /* @__PURE__ */ React26.createElement(React26.Fragment, null, /* @__PURE__ */ React26.createElement(import_dsh_client_ui_primitives6.Button, { variant: "outline", onClick: () => setTextAt(null) }, tr("\u53D6\u6D88", "Cancel")), /* @__PURE__ */ React26.createElement(import_dsh_client_ui_primitives6.Button, { variant: "primary", disabled: !textValue.trim(), onClick: () => {
19926
21292
  if (textAt && textValue.trim()) void save({ kind: textAt.kind, points: textAt.points }, textValue.trim());
19927
21293
  setTextAt(null);
19928
21294
  } }, tr("\u6DFB\u52A0", "Add")))
19929
21295
  },
19930
- textAt?.kind === "icon" ? /* @__PURE__ */ React21.createElement(StickerPicker, { onPick: (name) => {
21296
+ textAt?.kind === "icon" ? /* @__PURE__ */ React26.createElement(StickerPicker, { onPick: (name) => {
19931
21297
  if (textAt) void save({ kind: "icon", points: textAt.points }, name);
19932
21298
  setTextAt(null);
19933
- } }) : /* @__PURE__ */ React21.createElement(
19934
- import_dsh_client_ui_primitives3.Input,
21299
+ } }) : /* @__PURE__ */ React26.createElement(
21300
+ import_dsh_client_ui_primitives6.Input,
19935
21301
  {
19936
21302
  "data-modal-autofocus": true,
19937
21303
  className: "dhl-rename-input",
@@ -19949,12 +21315,11 @@ function DeskColumn({ onCollapse }) {
19949
21315
  }
19950
21316
  }
19951
21317
  )
19952
- ), pickerOpen ? /* @__PURE__ */ React21.createElement(MarketPicker, { activeCoin: coin, onClose: () => setPickerOpen(false) }) : null, /* @__PURE__ */ React21.createElement("div", { className: "dhl-market-workspace" + (s.tapePlacement === "below" ? " dhl-tape-below" : "") }, /* @__PURE__ */ React21.createElement("section", { className: "dhl-chart-panel" + (chartExpanded ? " dhl-chart-panel-expanded" : ""), "aria-label": tr("\u884C\u60C5\u56FE\u8868", "Market chart") }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-chart-tabs", role: "tablist", "aria-label": tr("\u56FE\u8868\u5185\u5BB9", "Chart content") }, /* @__PURE__ */ React21.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "chart", className: chartTab === "chart" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "chart" }) }, tr("K \u7EBF", "Chart")), row?.kind !== "spot" && row?.kind !== "outcome" ? /* @__PURE__ */ React21.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "funding", className: chartTab === "funding" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "funding" }) }, tr("\u8D44\u91D1\u8D39\u5386\u53F2", "Funding history")) : null), chartTab === "chart" ? /* @__PURE__ */ React21.createElement("div", { role: "tabpanel" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-chart-tools" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-intervals", "aria-label": tr("K \u7EBF\u5468\u671F", "Chart interval") }, INTERVALS.map((iv) => /* @__PURE__ */ React21.createElement("button", { key: iv, type: "button", className: s.interval === iv ? "on" : "", onClick: () => changeInterval(iv) }, iv)), /* @__PURE__ */ React21.createElement(Select, { "aria-label": tr("\u66F4\u591A K \u7EBF\u5468\u671F", "More chart intervals"), value: MORE_INTERVALS.includes(s.interval) ? s.interval : "", onChange: (event) => changeInterval(event.target.value) }, /* @__PURE__ */ React21.createElement("option", { value: "", disabled: true }, tr("\u66F4\u591A", "More")), MORE_INTERVALS.map((iv) => /* @__PURE__ */ React21.createElement("option", { key: iv, value: iv }, iv)))), /* @__PURE__ */ React21.createElement("div", { className: "dhl-chart-actions" }, /* @__PURE__ */ React21.createElement("details", { className: "dhl-layer-menu" }, /* @__PURE__ */ React21.createElement("summary", null, tr("\u663E\u793A", "Show"), /* @__PURE__ */ React21.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React21.createElement("div", { className: "dhl-layer-popover" }, [["orders", tr("\u59D4\u6258", "Orders")], ["fills", tr("\u6210\u4EA4", "Fills")], ["reasons", tr("\u6210\u4EA4\u539F\u56E0", "Fill reasons")], ["drawings", tr("\u753B\u7EBF", "Drawings")]].map(([id, label]) => /* @__PURE__ */ React21.createElement("label", { key: id }, /* @__PURE__ */ React21.createElement("input", { type: "checkbox", checked: layers[id], onChange: (event) => saveSessionWorkspace({ chartLayers: { ...layers, [id]: event.target.checked } }) }), label)))), /* @__PURE__ */ React21.createElement("details", { className: "dhl-indicator-menu" }, /* @__PURE__ */ React21.createElement("summary", { "aria-label": tr("\u9009\u62E9\u56FE\u8868\u6307\u6807", "Choose chart indicators") }, tr("\u6307\u6807", "Indicators"), " ", /* @__PURE__ */ React21.createElement("span", { className: "dhl-indicator-count" }, indicators.size + maLines.length)), /* @__PURE__ */ React21.createElement("div", { className: "dhl-indicator-popover", role: "group", "aria-label": tr("\u56FE\u8868\u6307\u6807", "Chart indicators") }, /* @__PURE__ */ React21.createElement("strong", null, tr("\u56FE\u8868\u6307\u6807", "Chart indicators")), INDICATOR_CHOICES.map(([id, label, description]) => /* @__PURE__ */ React21.createElement("button", { type: "button", key: id, className: indicators.has(id) ? "on" : "", "aria-pressed": indicators.has(id), onClick: () => toggleIndicator(id) }, /* @__PURE__ */ React21.createElement("span", { className: "dhl-indicator-check", "aria-hidden": "true" }, indicators.has(id) ? /* @__PURE__ */ React21.createElement(UiIcon, { name: "check", size: 13 }) : null), /* @__PURE__ */ React21.createElement("span", null, /* @__PURE__ */ React21.createElement("b", null, tr(label, INDICATOR_EN[id]?.[0] ?? label)), /* @__PURE__ */ React21.createElement("small", null, tr(description, INDICATOR_EN[id]?.[1] ?? description))))), /* @__PURE__ */ React21.createElement(MaEditor, { lines: maLines, onChange: (next) => saveSessionWorkspace({ maLines: next }) }))), /* @__PURE__ */ React21.createElement(StrategyMenu, { candles: s.candles, coin, interval: s.interval }), /* @__PURE__ */ React21.createElement("details", { className: "dhl-draw-menu" + (drawMode ? " active" : ""), ref: drawMenu }, /* @__PURE__ */ React21.createElement("summary", { "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, drawMode ? /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(ToolIcon, { kind: drawMode, size: 14 }), tr(toolOf(drawMode).zh, toolOf(drawMode).en)) : tr("\u753B\u7EBF", "Draw"), /* @__PURE__ */ React21.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React21.createElement("div", { className: "dhl-draw-popover", role: "menu", "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, DRAWING_TOOLS.map((tool, index) => /* @__PURE__ */ React21.createElement(React21.Fragment, { key: tool.kind }, index > 0 && DRAWING_TOOLS[index - 1].group !== tool.group ? /* @__PURE__ */ React21.createElement("hr", null) : null, /* @__PURE__ */ React21.createElement(
21318
+ ), pickerOpen ? /* @__PURE__ */ React26.createElement(MarketPicker, { activeCoin: coin, onClose: () => setPickerOpen(false) }) : null, /* @__PURE__ */ React26.createElement("div", { className: "dhl-market-workspace" + (s.tapePlacement === "below" ? " dhl-tape-below" : "") }, /* @__PURE__ */ React26.createElement("section", { className: "dhl-chart-panel" + (chartExpanded ? " dhl-chart-panel-expanded" : ""), "aria-label": tr("\u884C\u60C5\u56FE\u8868", "Market chart") }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tabs", role: "tablist", "aria-label": tr("\u56FE\u8868\u5185\u5BB9", "Chart content") }, /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "chart", className: chartTab === "chart" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "chart" }) }, tr("K \u7EBF", "Chart")), row?.kind !== "spot" && row?.kind !== "outcome" ? /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "funding", className: chartTab === "funding" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "funding" }) }, tr("\u8D44\u91D1\u8D39\u5386\u53F2", "Funding history")) : null), chartTab === "chart" ? /* @__PURE__ */ React26.createElement("div", { role: "tabpanel" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tools" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-intervals", "aria-label": tr("K \u7EBF\u5468\u671F", "Chart interval") }, INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("button", { key: iv, type: "button", className: s.interval === iv ? "on" : "", onClick: () => changeInterval(iv) }, iv)), /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u66F4\u591A K \u7EBF\u5468\u671F", "More chart intervals"), value: MORE_INTERVALS.includes(s.interval) ? s.interval : "", onChange: (event) => changeInterval(event.target.value) }, /* @__PURE__ */ React26.createElement("option", { value: "", disabled: true }, tr("\u66F4\u591A", "More")), MORE_INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("option", { key: iv, value: iv }, iv)))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-actions" }, /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u56FE\u8868\u7C7B\u578B", "Chart type"), value: chartType, onChange: (event) => saveSessionWorkspace({ chartType: event.target.value }) }, [["candles", "\u8721\u70DB\u56FE", "Candles"], ["bars", "\u7F8E\u56FD\u7EBF", "OHLC bars"], ["line", "\u6298\u7EBF\u56FE", "Line"], ["area", "\u9762\u79EF\u56FE", "Area"], ["heikinAshi", "\u5E73\u5747K\u7EBF", "Heikin Ashi"]].map(([value, zh, en]) => /* @__PURE__ */ React26.createElement("option", { key: value, value }, tr(zh, en)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-layer-menu" }, /* @__PURE__ */ React26.createElement("summary", null, tr("\u663E\u793A", "Show"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-layer-popover" }, [["orders", tr("\u59D4\u6258", "Orders")], ["fills", tr("\u6210\u4EA4", "Fills")], ["reasons", tr("\u6210\u4EA4\u539F\u56E0", "Fill reasons")], ["drawings", tr("\u753B\u7EBF", "Drawings")]].map(([id, label]) => /* @__PURE__ */ React26.createElement("label", { key: id }, /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: layers[id], onChange: (event) => saveSessionWorkspace({ chartLayers: { ...layers, [id]: event.target.checked } }) }), label)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-indicator-menu" }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u9009\u62E9\u56FE\u8868\u6307\u6807", "Choose chart indicators") }, tr("\u6307\u6807", "Indicators"), " ", /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-count" }, indicators.size + maLines.length)), /* @__PURE__ */ React26.createElement("div", { className: "dhl-indicator-popover", role: "group", "aria-label": tr("\u56FE\u8868\u6307\u6807", "Chart indicators") }, /* @__PURE__ */ React26.createElement("strong", null, tr("\u56FE\u8868\u6307\u6807", "Chart indicators")), INDICATOR_CHOICES.map(([id, label, description]) => /* @__PURE__ */ React26.createElement("button", { type: "button", key: id, className: indicators.has(id) ? "on" : "", "aria-pressed": indicators.has(id), onClick: () => toggleIndicator(id) }, /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-check", "aria-hidden": "true" }, indicators.has(id) ? /* @__PURE__ */ React26.createElement(UiIcon, { name: "check", size: 13 }) : null), /* @__PURE__ */ React26.createElement("span", null, /* @__PURE__ */ React26.createElement("b", null, id === "rsi14" ? `RSI ${studySettings.rsi.period}` : id === "bollinger20" ? `BOLL ${studySettings.bollinger.period} \xD7 ${studySettings.bollinger.multiplier}` : tr(label, INDICATOR_EN[id]?.[0] ?? label)), /* @__PURE__ */ React26.createElement("small", null, id === "macd" ? `${studySettings.macd.fast} / ${studySettings.macd.slow} / ${studySettings.macd.signal}` : id === "bollinger20" ? tr("\u5747\u7EBF\u4E0E\u6807\u51C6\u5DEE\u901A\u9053", "Mean and standard deviation bands") : tr(description, INDICATOR_EN[id]?.[1] ?? description))))), /* @__PURE__ */ React26.createElement(IndicatorEditor, { key: s.activeSessionId, settings: s.workspace?.indicatorSettings, indicators: [...indicators], maLines, onApply: saveSessionWorkspace }), /* @__PURE__ */ React26.createElement(MaEditor, { lines: maLines, onChange: (next) => saveSessionWorkspace({ maLines: next }) }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: s.workspace?.showDataWindow ? "on" : "", "aria-pressed": s.workspace?.showDataWindow ?? false, onClick: () => saveSessionWorkspace({ showDataWindow: !s.workspace?.showDataWindow }) }, tr("\u6570\u636E\u7A97\u53E3", "Data window")), /* @__PURE__ */ React26.createElement(StrategyMenu, { candles: s.candles, coin, interval: s.interval }), /* @__PURE__ */ React26.createElement("details", { className: "dhl-draw-menu" + (drawMode ? " active" : ""), ref: drawMenu }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, drawMode ? /* @__PURE__ */ React26.createElement(React26.Fragment, null, /* @__PURE__ */ React26.createElement(ToolIcon, { kind: drawMode, size: 14 }), tr(toolOf(drawMode).zh, toolOf(drawMode).en)) : tr("\u753B\u7EBF", "Draw"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-popover", role: "menu", "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, DRAWING_TOOLS.map((tool, index) => /* @__PURE__ */ React26.createElement(React26.Fragment, { key: tool.kind }, index > 0 && DRAWING_TOOLS[index - 1].group !== tool.group ? /* @__PURE__ */ React26.createElement("hr", null) : null, /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-tool-row" }, /* @__PURE__ */ React26.createElement(
19953
21319
  "button",
19954
21320
  {
19955
21321
  type: "button",
19956
21322
  role: "menuitemradio",
19957
- key: tool.kind,
19958
21323
  "aria-checked": drawMode === tool.kind,
19959
21324
  className: drawMode === tool.kind ? "on" : "",
19960
21325
  onClick: () => {
@@ -19962,9 +21327,20 @@ function DeskColumn({ onCollapse }) {
19962
21327
  drawMenu.current?.removeAttribute("open");
19963
21328
  }
19964
21329
  },
19965
- /* @__PURE__ */ React21.createElement(ToolIcon, { kind: tool.kind }),
19966
- /* @__PURE__ */ React21.createElement("span", null, /* @__PURE__ */ React21.createElement("b", null, tr(tool.zh, tool.en)), /* @__PURE__ */ React21.createElement("small", null, tr(tool.hintZh, tool.hintEn)))
19967
- ))), /* @__PURE__ */ React21.createElement("div", { className: "dhl-draw-settings" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-draw-setting" }, /* @__PURE__ */ React21.createElement("span", null, tr("\u78C1\u5438", "Magnet")), /* @__PURE__ */ React21.createElement("span", { className: "dhl-draw-seg", role: "radiogroup", "aria-label": tr("\u78C1\u5438", "Magnet") }, [["off", "\u5173", "Off"], ["weak", "\u5F31", "Weak"], ["strong", "\u5F3A", "Strong"]].map(([value, zh, en]) => /* @__PURE__ */ React21.createElement("button", { type: "button", role: "radio", key: value, "aria-checked": magnet === value, className: magnet === value ? "on" : "", onClick: () => setMagnet(value) }, tr(zh, en))))), /* @__PURE__ */ React21.createElement("label", { className: "dhl-draw-setting" }, /* @__PURE__ */ React21.createElement("span", null, tr("\u8FDE\u7EED\u7ED8\u5236", "Stay in drawing mode")), /* @__PURE__ */ React21.createElement("input", { type: "checkbox", checked: stayDrawing, onChange: (event) => setStayDrawing(event.target.checked) })), /* @__PURE__ */ React21.createElement("label", { className: "dhl-draw-setting" }, /* @__PURE__ */ React21.createElement("span", null, tr("\u9501\u5B9A\u6240\u6709\u753B\u7EBF", "Lock all drawings")), /* @__PURE__ */ React21.createElement("input", { type: "checkbox", checked: drawingsLocked, onChange: (event) => setDrawingsLocked(event.target.checked) })), /* @__PURE__ */ React21.createElement(
21330
+ /* @__PURE__ */ React26.createElement(ToolIcon, { kind: tool.kind }),
21331
+ /* @__PURE__ */ React26.createElement("span", null, /* @__PURE__ */ React26.createElement("b", null, tr(tool.zh, tool.en)), /* @__PURE__ */ React26.createElement("small", null, tr(tool.hintZh, tool.hintEn)))
21332
+ ), /* @__PURE__ */ React26.createElement(
21333
+ "button",
21334
+ {
21335
+ type: "button",
21336
+ className: "dhl-draw-favorite",
21337
+ "aria-label": `${favorites.includes(tool.kind) ? tr("\u53D6\u6D88\u6536\u85CF", "Unfavorite") : tr("\u6536\u85CF", "Favorite")} ${tr(tool.zh, tool.en)}`,
21338
+ "aria-pressed": favorites.includes(tool.kind),
21339
+ disabled: !favorites.includes(tool.kind) && favorites.length >= 12,
21340
+ onClick: () => setFavorites(favorites.includes(tool.kind) ? favorites.filter((kind) => kind !== tool.kind) : [...favorites, tool.kind])
21341
+ },
21342
+ /* @__PURE__ */ React26.createElement(UiIcon, { name: "star", filled: favorites.includes(tool.kind), size: 14 })
21343
+ )))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-settings" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-setting" }, /* @__PURE__ */ React26.createElement("span", null, tr("\u78C1\u5438", "Magnet")), /* @__PURE__ */ React26.createElement("span", { className: "dhl-draw-seg", role: "radiogroup", "aria-label": tr("\u78C1\u5438", "Magnet") }, [["off", "\u5173", "Off"], ["weak", "\u5F31", "Weak"], ["strong", "\u5F3A", "Strong"]].map(([value, zh, en]) => /* @__PURE__ */ React26.createElement("button", { type: "button", role: "radio", key: value, "aria-checked": magnet === value, className: magnet === value ? "on" : "", onClick: () => setMagnet(value) }, tr(zh, en))))), /* @__PURE__ */ React26.createElement("label", { className: "dhl-draw-setting" }, /* @__PURE__ */ React26.createElement("span", null, tr("\u8FDE\u7EED\u7ED8\u5236", "Stay in drawing mode")), /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: stayDrawing, onChange: (event) => setStayDrawing(event.target.checked) })), /* @__PURE__ */ React26.createElement("label", { className: "dhl-draw-setting" }, /* @__PURE__ */ React26.createElement("span", null, tr("\u9501\u5B9A\u6240\u6709\u753B\u7EBF", "Lock all drawings")), /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: drawingsLocked, onChange: (event) => setDrawingsLocked(event.target.checked) })), /* @__PURE__ */ React26.createElement(
19968
21344
  "button",
19969
21345
  {
19970
21346
  type: "button",
@@ -19982,9 +21358,22 @@ function DeskColumn({ onCollapse }) {
19982
21358
  onBlur: () => setConfirmClear(false)
19983
21359
  },
19984
21360
  confirmClear ? tr(`\u786E\u8BA4\u5220\u9664 ${marks.filter((item) => !item.locked).length} \u6761\u753B\u7EBF`, `Delete ${marks.filter((item) => !item.locked).length} drawings`) : tr("\u5220\u9664\u6240\u6709\u753B\u7EBF", "Remove all drawings")
19985
- )))), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-chart-iconbtn", disabled: !history.canUndo, "aria-label": tr("\u64A4\u9500", "Undo"), title: tr("\u64A4\u9500\u753B\u7EBF\u64CD\u4F5C\uFF08Ctrl/Cmd+Z\uFF09", "Undo drawing change (Ctrl/Cmd+Z)"), onClick: () => void history.undo() }, /* @__PURE__ */ React21.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React21.createElement("path", { d: "M9 14 4 9l5-5" }), /* @__PURE__ */ React21.createElement("path", { d: "M4 9h10a6 6 0 0 1 0 12h-3" }))), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-chart-iconbtn", disabled: !history.canRedo, "aria-label": tr("\u91CD\u505A", "Redo"), title: tr("\u91CD\u505A\uFF08Shift+Ctrl/Cmd+Z\uFF09", "Redo (Shift+Ctrl/Cmd+Z)"), onClick: () => void history.redo() }, /* @__PURE__ */ React21.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React21.createElement("path", { d: "m15 14 5-5-5-5" }), /* @__PURE__ */ React21.createElement("path", { d: "M20 9H10a6 6 0 0 0 0 12h3" }))), /* @__PURE__ */ React21.createElement(Select, { "aria-label": tr("\u4EF7\u683C\u523B\u5EA6", "Price scale"), value: s.workspace?.priceScale ?? "normal", onChange: (event) => saveSessionWorkspace({ priceScale: event.target.value }) }, /* @__PURE__ */ React21.createElement("option", { value: "normal" }, tr("\u7EBF\u6027", "Linear")), /* @__PURE__ */ React21.createElement("option", { value: "log" }, tr("\u5BF9\u6570", "Log")), /* @__PURE__ */ React21.createElement("option", { value: "percentage" }, "%")), /* @__PURE__ */ React21.createElement("span", { className: "dhl-chart-action-separator", "aria-hidden": "true" }), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-chart-iconbtn", "aria-label": tr("\u590D\u4F4D\u56FE\u8868\u89C6\u56FE", "Reset chart view"), title: tr("\u590D\u4F4D\u56FE\u8868\u89C6\u56FE", "Reset chart view"), onClick: () => setChartResetToken((value) => value + 1) }, /* @__PURE__ */ React21.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React21.createElement("path", { d: "M3 11a9 9 0 1 0 2.6-6.4M3 4v6h6" }), /* @__PURE__ */ React21.createElement("path", { d: "M12 7v5l3 2" }))), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-chart-iconbtn", "aria-label": chartExpanded ? tr("\u9000\u51FA\u653E\u5927\u56FE\u8868", "Exit expanded chart") : tr("\u653E\u5927\u56FE\u8868", "Expand chart"), "aria-pressed": chartExpanded, title: chartExpanded ? tr("\u9000\u51FA\u653E\u5927\u56FE\u8868 (Esc)", "Exit expanded chart (Esc)") : tr("\u653E\u5927\u56FE\u8868", "Expand chart"), onClick: () => setChartExpanded((value) => !value) }, chartExpanded ? /* @__PURE__ */ React21.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React21.createElement("path", { d: "M9 3v6H3m18 6h-6v6M3 9h6V3m12 12h-6v6" })) : /* @__PURE__ */ React21.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React21.createElement("path", { d: "M9 3H3v6m12-6h6v6M3 15v6h6m12-6v6h-6" }))))), /* @__PURE__ */ React21.createElement("div", { className: "dhl-chart" }, /* @__PURE__ */ React21.createElement(
21361
+ )))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", disabled: !history.canUndo, "aria-label": tr("\u64A4\u9500", "Undo"), title: tr("\u64A4\u9500\u753B\u7EBF\u64CD\u4F5C\uFF08Ctrl/Cmd+Z\uFF09", "Undo drawing change (Ctrl/Cmd+Z)"), onClick: () => void history.undo() }, /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M9 14 4 9l5-5" }), /* @__PURE__ */ React26.createElement("path", { d: "M4 9h10a6 6 0 0 1 0 12h-3" }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", disabled: !history.canRedo, "aria-label": tr("\u91CD\u505A", "Redo"), title: tr("\u91CD\u505A\uFF08Shift+Ctrl/Cmd+Z\uFF09", "Redo (Shift+Ctrl/Cmd+Z)"), onClick: () => void history.redo() }, /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "m15 14 5-5-5-5" }), /* @__PURE__ */ React26.createElement("path", { d: "M20 9H10a6 6 0 0 0 0 12h3" }))), /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u4EF7\u683C\u523B\u5EA6", "Price scale"), value: s.workspace?.priceScale ?? "normal", onChange: (event) => saveSessionWorkspace({ priceScale: event.target.value }) }, /* @__PURE__ */ React26.createElement("option", { value: "normal" }, tr("\u7EBF\u6027", "Linear")), /* @__PURE__ */ React26.createElement("option", { value: "log" }, tr("\u5BF9\u6570", "Log")), /* @__PURE__ */ React26.createElement("option", { value: "percentage" }, "%")), /* @__PURE__ */ React26.createElement("span", { className: "dhl-chart-action-separator", "aria-hidden": "true" }), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", "aria-label": tr("\u590D\u4F4D\u56FE\u8868\u89C6\u56FE", "Reset chart view"), title: tr("\u590D\u4F4D\u56FE\u8868\u89C6\u56FE", "Reset chart view"), onClick: () => setChartResetToken((value) => value + 1) }, /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M3 11a9 9 0 1 0 2.6-6.4M3 4v6h6" }), /* @__PURE__ */ React26.createElement("path", { d: "M12 7v5l3 2" }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", "aria-label": chartExpanded ? tr("\u9000\u51FA\u653E\u5927\u56FE\u8868", "Exit expanded chart") : tr("\u653E\u5927\u56FE\u8868", "Expand chart"), "aria-pressed": chartExpanded, title: chartExpanded ? tr("\u9000\u51FA\u653E\u5927\u56FE\u8868 (Esc)", "Exit expanded chart (Esc)") : tr("\u653E\u5927\u56FE\u8868", "Expand chart"), onClick: () => setChartExpanded((value) => !value) }, chartExpanded ? /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M9 3v6H3m18 6h-6v6M3 9h6V3m12 12h-6v6" })) : /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M9 3H3v6m12-6h6v6M3 15v6h6m12-6v6h-6" }))))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-favorite-tools", role: "toolbar", "aria-label": tr("\u5E38\u7528\u753B\u7EBF", "Favorite drawing tools") }, /* @__PURE__ */ React26.createElement("button", { type: "button", "aria-label": tr("\u5149\u6807\uFF08\u53D6\u6D88\u7ED8\u5236\uFF09", "Cursor (cancel drawing)"), title: tr("\u5149\u6807 \xB7 Esc \u53D6\u6D88\u7ED8\u5236", "Cursor \xB7 Esc cancels drawing"), "aria-pressed": !drawMode, className: !drawMode ? "on" : "", onClick: () => setDrawMode(null) }, /* @__PURE__ */ React26.createElement(UiIcon, { name: "cursor", size: 16 })), /* @__PURE__ */ React26.createElement("span", { className: "dhl-chart-action-separator", "aria-hidden": "true" }), favorites.map((kind) => /* @__PURE__ */ React26.createElement("button", { key: kind, type: "button", title: tr(toolOf(kind).zh, toolOf(kind).en), "aria-label": `${tr("\u5E38\u7528", "Favorite")} ${tr(toolOf(kind).zh, toolOf(kind).en)}`, "aria-pressed": drawMode === kind, className: drawMode === kind ? "on" : "", onClick: () => setDrawMode(drawMode === kind ? null : kind) }, /* @__PURE__ */ React26.createElement(ToolIcon, { kind, size: 16 }))), /* @__PURE__ */ React26.createElement("small", null, tr("\u5728\u753B\u7EBF\u83DC\u5355\u4E2D\u6536\u85CF\u5E38\u7528\u5DE5\u5177", "Star tools in the Draw menu"))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-research-controls" }, /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u591A\u5468\u671F\u5E03\u5C40", "Research layout"), value: research.layout, onChange: (e) => saveSessionWorkspace({ research: { ...research, layout: e.target.value } }) }, /* @__PURE__ */ React26.createElement("option", { value: "single" }, tr("\u5355\u56FE", "Single chart")), /* @__PURE__ */ React26.createElement("option", { value: "dual" }, tr("\u53CC\u56FE\u7814\u7A76", "Two charts")), /* @__PURE__ */ React26.createElement("option", { value: "quad" }, tr("\u56DB\u56FE\u7814\u7A76", "Four charts"))), /* @__PURE__ */ React26.createElement("label", null, /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: research.syncTime, onChange: (e) => {
21362
+ setRangeEvent(null);
21363
+ saveSessionWorkspace({ research: { ...research, syncTime: e.target.checked } });
21364
+ } }), tr("\u65F6\u95F4\u8054\u52A8", "Sync time")), /* @__PURE__ */ React26.createElement("label", null, /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: research.syncCursor, onChange: (e) => {
21365
+ setCursorEvent(null);
21366
+ saveSessionWorkspace({ research: { ...research, syncCursor: e.target.checked } });
21367
+ } }), tr("\u5149\u6807\u8054\u52A8", "Sync cursor")), researchCount > 0 ? /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-ghost", onClick: () => queueToAgent(tr(`\u8BF7\u7814\u7A76 ${coin} \u7684 ${[s.interval, ...research.intervals.slice(0, researchCount)].join("\u3001")} \u591A\u5468\u671F\u7ED3\u6784\u3002\u5148\u8BFB\u53D6 hyper_context \u7684\u6307\u6807\u53C2\u6570\uFF0C\u518D\u9010\u5468\u671F\u8C03\u7528 hyper_market action=indicators\uFF1B\u53EA\u5BF9\u5DF2\u6536\u76D8 K \u7EBF\u4F5C\u6BD4\u8F83\uFF0C\u8BF4\u660E\u5927\u5468\u671F\u8D8B\u52BF\u3001\u5C0F\u5468\u671F\u52A8\u91CF\u4E0E\u51B2\u7A81\uFF0C\u4EE5\u53CA\u6570\u636E\u65F6\u6548\u3002`, `Research ${coin} across ${[s.interval, ...research.intervals.slice(0, researchCount)].join(", ")}. Read hyper_context for configured indicators, then query hyper_market action=indicators for each interval. Compare closed bars, trend, momentum, conflicts and freshness.`)) }, tr("\u8BA8\u8BBA\u591A\u5468\u671F", "Discuss timeframes")) : null), /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart" }, /* @__PURE__ */ React26.createElement("div", { className: `dhl-research-grid ${research.layout}` }, /* @__PURE__ */ React26.createElement("section", { className: "dhl-research-pane dhl-primary-pane" }, researchCount > 0 ? /* @__PURE__ */ React26.createElement("header", null, /* @__PURE__ */ React26.createElement("b", null, coin, " \xB7 ", s.interval), /* @__PURE__ */ React26.createElement("small", null, tr("\u4E3B\u56FE \xB7 \u7F16\u8F91\u753B\u7EBF", "Main chart \xB7 edit drawings"))) : null, /* @__PURE__ */ React26.createElement(
19986
21368
  CandleChart,
19987
21369
  {
21370
+ link: researchCount > 0 ? makeLink("primary") : void 0,
21371
+ compact: researchCount > 0,
21372
+ indicatorSettings: s.workspace?.indicatorSettings,
21373
+ showDataWindow: s.workspace?.showDataWindow ?? false,
21374
+ chartType,
21375
+ command: chartCommand?.scope === chartScope ? chartCommand.command : null,
21376
+ caption: `${coin} ${s.interval} \xB7 ${chartType}`,
19988
21377
  candles: s.candles,
19989
21378
  marks: layers.drawings ? marks : [],
19990
21379
  tradeEvents,
@@ -20004,6 +21393,9 @@ function DeskColumn({ onCollapse }) {
20004
21393
  drawingsLocked,
20005
21394
  onEditMark: (id, patch) => void editMark(id, patch),
20006
21395
  onRemoveMark: (id) => void erase(id),
21396
+ onRemoveMarks: (ids) => void history.removeMany(ids),
21397
+ onEditMarks: (ids, patch) => void history.editMany(ids, patch),
21398
+ onEditEach: (patches) => void history.editEach(patches),
20007
21399
  focus: s.chartFocus && s.chartFocus.coin === coin && s.chartFocus.interval === s.interval ? s.chartFocus : null,
20008
21400
  maLines,
20009
21401
  priceScale: s.workspace?.priceScale ?? "normal",
@@ -20011,7 +21403,20 @@ function DeskColumn({ onCollapse }) {
20011
21403
  onVisibleRange: (range) => saveSessionWorkspace({ chartRanges: { [`${coin}:${s.interval}`]: range } }),
20012
21404
  key: `${s.activeSessionId ?? ""}:${s.workspaceSessionId === s.activeSessionId ? "ready" : "pending"}:${coin}:${s.interval}`
20013
21405
  }
20014
- ), /* @__PURE__ */ React21.createElement("div", { className: "dhl-chart-click-hint" }, drawMode ? `${tr(toolOf(drawMode).zh, toolOf(drawMode).en)}\uFF1A${tr(toolOf(drawMode).hintZh, toolOf(drawMode).hintEn)} \xB7 ${tr("Esc \u53D6\u6D88", "Esc to cancel")}` : tr("\u70B9\u51FB K \u7EBF\u3001\u6210\u4EA4\u91CF\u67F1\u6216\u6307\u6807\u7EBF\uFF0C\u5C06\u8BE5\u70B9\u6570\u636E\u52A0\u5165\u804A\u5929\u8F93\u5165\u6846", "Click a candle, volume bar or study line to add its data to the chat draft")), marks.length > 0 ? /* @__PURE__ */ React21.createElement(
21406
+ )), research.intervals.slice(0, researchCount).map((iv, index) => /* @__PURE__ */ React26.createElement(
21407
+ ResearchPane,
21408
+ {
21409
+ key: `${researchScope}:${index}`,
21410
+ coin,
21411
+ interval: iv,
21412
+ indicators,
21413
+ marks: layers.drawings ? marks : [],
21414
+ chartType,
21415
+ link: makeLink(`secondary-${index}`),
21416
+ onAnalyzeFill: analyzeFill,
21417
+ onInterval: (next) => saveSessionWorkspace({ research: { ...research, intervals: research.intervals.map((value, i) => i === index ? next : value) } })
21418
+ }
21419
+ ))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-click-hint" }, drawMode ? `${tr(toolOf(drawMode).zh, toolOf(drawMode).en)}\uFF1A${tr(toolOf(drawMode).hintZh, toolOf(drawMode).hintEn)} \xB7 ${tr("Esc \u53D6\u6D88", "Esc to cancel")}` : tr("\u70B9\u51FB K \u7EBF\u3001\u6210\u4EA4\u91CF\u67F1\u6216\u6307\u6807\u7EBF\uFF0C\u5C06\u8BE5\u70B9\u6570\u636E\u52A0\u5165\u804A\u5929\u8F93\u5165\u6846", "Click a candle, volume bar or study line to add its data to the chat draft")), marks.length > 0 ? /* @__PURE__ */ React26.createElement(
20015
21420
  DrawingList,
20016
21421
  {
20017
21422
  marks,
@@ -20020,18 +21425,25 @@ function DeskColumn({ onCollapse }) {
20020
21425
  onEdit: (id, patch) => void editMark(id, patch),
20021
21426
  onRemove: (id) => void erase(id)
20022
21427
  }
20023
- ) : null, s.candlesState === "error" ? /* @__PURE__ */ React21.createElement("div", { className: "dhl-inline-error" }, tr("K \u7EBF\u6682\u65F6\u4E0D\u53EF\u7528\u3002\u5207\u6362\u5468\u671F\u53EF\u91CD\u8BD5\u3002", "Chart data is unavailable. Change interval to retry.")) : null)) : /* @__PURE__ */ React21.createElement("div", { className: "dhl-funding-panel", role: "tabpanel" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-funding-summary" }, /* @__PURE__ */ React21.createElement("span", null, tr("\u5F53\u524D\u5C0F\u65F6\u8D44\u91D1\u8D39", "Current hourly funding")), /* @__PURE__ */ React21.createElement("strong", null, funding == null ? "\u2014" : `${(funding * 100).toFixed(4)}%`), /* @__PURE__ */ React21.createElement("span", null, tr("\u6700\u8FD1 7 \u5929 \xB7 \u6309\u5C0F\u65F6\u7ED3\u7B97", "Last 7 days \xB7 hourly settlement"))), s.fundingState === "loading" ? /* @__PURE__ */ React21.createElement("div", { className: "dhl-empty" }, tr("\u6B63\u5728\u8BFB\u53D6\u8D44\u91D1\u8D39\u5386\u53F2\u2026", "Loading funding history\u2026")) : null, s.fundingState === "error" ? /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-ghost", onClick: () => void loadFunding() }, tr("\u52A0\u8F7D\u5931\u8D25\uFF0C\u91CD\u8BD5", "Load failed, retry")) : null, s.fundingState === "ready" && s.fundingRows.length === 0 ? /* @__PURE__ */ React21.createElement("div", { className: "dhl-empty" }, tr("\u6682\u65E0\u8D44\u91D1\u8D39\u8BB0\u5F55", "No funding records")) : null, /* @__PURE__ */ React21.createElement("div", { className: "dhl-funding-list" }, s.fundingRows.slice(-30).reverse().map((item) => {
21428
+ ) : null, /* @__PURE__ */ React26.createElement(
21429
+ ChartNavigation,
21430
+ {
21431
+ disabled: s.candles.length < 2,
21432
+ onNavigate: (action) => setChartCommand({ scope: chartScope, command: { ...action, at: performance.now() } }),
21433
+ onCsv: () => downloadBlob(new Blob([candlesCsv(s.candles, coin, s.interval)], { type: "text/csv;charset=utf-8" }), `${coin.replace(/[^a-zA-Z0-9_-]/g, "_")}_${s.interval}.csv`)
21434
+ }
21435
+ ), s.candlesState === "error" ? /* @__PURE__ */ React26.createElement("div", { className: "dhl-inline-error" }, tr("K \u7EBF\u6682\u65F6\u4E0D\u53EF\u7528\u3002\u5207\u6362\u5468\u671F\u53EF\u91CD\u8BD5\u3002", "Chart data is unavailable. Change interval to retry.")) : null)) : /* @__PURE__ */ React26.createElement("div", { className: "dhl-funding-panel", role: "tabpanel" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-funding-summary" }, /* @__PURE__ */ React26.createElement("span", null, tr("\u5F53\u524D\u5C0F\u65F6\u8D44\u91D1\u8D39", "Current hourly funding")), /* @__PURE__ */ React26.createElement("strong", null, funding == null ? "\u2014" : `${(funding * 100).toFixed(4)}%`), /* @__PURE__ */ React26.createElement("span", null, tr("\u6700\u8FD1 7 \u5929 \xB7 \u6309\u5C0F\u65F6\u7ED3\u7B97", "Last 7 days \xB7 hourly settlement"))), s.fundingState === "loading" ? /* @__PURE__ */ React26.createElement("div", { className: "dhl-empty" }, tr("\u6B63\u5728\u8BFB\u53D6\u8D44\u91D1\u8D39\u5386\u53F2\u2026", "Loading funding history\u2026")) : null, s.fundingState === "error" ? /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-ghost", onClick: () => void loadFunding() }, tr("\u52A0\u8F7D\u5931\u8D25\uFF0C\u91CD\u8BD5", "Load failed, retry")) : null, s.fundingState === "ready" && s.fundingRows.length === 0 ? /* @__PURE__ */ React26.createElement("div", { className: "dhl-empty" }, tr("\u6682\u65E0\u8D44\u91D1\u8D39\u8BB0\u5F55", "No funding records")) : null, /* @__PURE__ */ React26.createElement("div", { className: "dhl-funding-list" }, s.fundingRows.slice(-30).reverse().map((item) => {
20024
21436
  const rate = Number(item.fundingRate) * 100;
20025
- return /* @__PURE__ */ React21.createElement("div", { className: "dhl-funding-row", key: item.time }, /* @__PURE__ */ React21.createElement("time", null, new Date(item.time).toLocaleString(tr("zh-CN", "en-US"), { hour12: false })), /* @__PURE__ */ React21.createElement("span", { className: rate >= 0 ? "dhl-up" : "dhl-down" }, `${rate >= 0 ? "+" : ""}${rate.toFixed(5)}%`));
20026
- })))), /* @__PURE__ */ React21.createElement(MarketTape, { snapshot: s.snapshot })), /* @__PURE__ */ React21.createElement("div", { className: "dhl-desk-foot" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-banner" }, /* @__PURE__ */ React21.createElement("span", { className: "dhl-banner-signal", "aria-hidden": "true" }), /* @__PURE__ */ React21.createElement("span", null, /* @__PURE__ */ React21.createElement("b", null, t("banner.title", "\u4F1A\u8BDD\u9A71\u52A8\u4EA4\u6613")), " \xB7 ", t("banner.body", "\u5728\u53F3\u4FA7\u4F1A\u8BDD\u4E2D\u8BA8\u8BBA\u884C\u60C5\u3001\u98CE\u9669\u4E0E\u4EA4\u6613\u51B3\u7B56\uFF1B\u76D8\u53E3\u4EF7\u683C\u53EF\u7528\u4E8E\u8F85\u52A9\u586B\u5199\u7968\u636E\u3002"))), /* @__PURE__ */ React21.createElement(BottomTable, { account: s.account, accountError: s.accountError })));
21437
+ return /* @__PURE__ */ React26.createElement("div", { className: "dhl-funding-row", key: item.time }, /* @__PURE__ */ React26.createElement("time", null, new Date(item.time).toLocaleString(tr("zh-CN", "en-US"), { hour12: false })), /* @__PURE__ */ React26.createElement("span", { className: rate >= 0 ? "dhl-up" : "dhl-down" }, `${rate >= 0 ? "+" : ""}${rate.toFixed(5)}%`));
21438
+ })))), /* @__PURE__ */ React26.createElement(MarketTape, { snapshot: s.snapshot })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-desk-foot" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-banner" }, /* @__PURE__ */ React26.createElement("span", { className: "dhl-banner-signal", "aria-hidden": "true" }), /* @__PURE__ */ React26.createElement("span", null, /* @__PURE__ */ React26.createElement("b", null, t("banner.title", "\u4F1A\u8BDD\u9A71\u52A8\u4EA4\u6613")), " \xB7 ", t("banner.body", "\u5728\u53F3\u4FA7\u4F1A\u8BDD\u4E2D\u8BA8\u8BBA\u884C\u60C5\u3001\u98CE\u9669\u4E0E\u4EA4\u6613\u51B3\u7B56\uFF1B\u76D8\u53E3\u4EF7\u683C\u53EF\u7528\u4E8E\u8F85\u52A9\u586B\u5199\u7968\u636E\u3002"))), /* @__PURE__ */ React26.createElement(BottomTable, { account: s.account, accountError: s.accountError })));
20027
21439
  }
20028
21440
 
20029
21441
  // src/client/components/chat-column.tsx
20030
- var React24 = __toESM(require("react"), 1);
21442
+ var React29 = __toESM(require("react"), 1);
20031
21443
 
20032
21444
  // src/client/dsh-extensions.tsx
20033
- var React22 = __toESM(require("react"), 1);
20034
- var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
21445
+ var React27 = __toESM(require("react"), 1);
21446
+ var import_dsh_client_ui_primitives7 = require("@deepseek-ai/dsh-client-ui-primitives");
20035
21447
  var HYPER_TOOLS = ["hyper_context", "hyper_market", "hyper_chart", "hyper_account", "hyper_decide", "hyper_order", "hyper_session"];
20036
21448
  var chartSourceReady = false;
20037
21449
  function chartReferenceChipsReady() {
@@ -20074,11 +21486,11 @@ function registerDshExtensions(ctx, slots) {
20074
21486
  });
20075
21487
  }
20076
21488
  if (!slots) return;
20077
- for (const key2 of HYPER_TOOLS) {
21489
+ for (const key3 of HYPER_TOOLS) {
20078
21490
  ctx.effect(() => slots.inject("tool.call.toolview", () => slots.register(
20079
- { name: "tool.call.toolview", key: key2, registrant: "dsh-hyperliquid" },
21491
+ { name: "tool.call.toolview", key: key3, registrant: "dsh-hyperliquid" },
20080
21492
  HyperToolRow
20081
- )), `dsh-hyperliquid: ${key2} row`);
21493
+ )), `dsh-hyperliquid: ${key3} row`);
20082
21494
  }
20083
21495
  ctx.effect(() => slots.inject("conversation.approval.detail", () => slots.register(
20084
21496
  { name: "conversation.approval.detail", priority: -1, registrant: "dsh-hyperliquid" },
@@ -20144,7 +21556,7 @@ function verdictOf(text) {
20144
21556
  return line.length > 72 ? line.slice(0, 71) + "\u2026" : line;
20145
21557
  }
20146
21558
  function Mark() {
20147
- return /* @__PURE__ */ React22.createElement("svg", { viewBox: "0 0 200 200", width: "14", height: "14", "aria-hidden": "true" }, /* @__PURE__ */ React22.createElement("path", { d: HL_MARK_PATH, fill: "currentColor" }));
21559
+ return /* @__PURE__ */ React27.createElement("svg", { viewBox: "0 0 200 200", width: "14", height: "14", "aria-hidden": "true" }, /* @__PURE__ */ React27.createElement("path", { d: HL_MARK_PATH, fill: "currentColor" }));
20148
21560
  }
20149
21561
  function HyperToolRow(props) {
20150
21562
  const { phase, block, toolName } = props;
@@ -20155,19 +21567,19 @@ function HyperToolRow(props) {
20155
21567
  const failed = phase === "result" && block?.isError === true;
20156
21568
  const verdict = phase === "result" ? verdictOf(text) : "";
20157
21569
  const heading = [title, summary].filter(Boolean).join(" \xB7 ");
20158
- return /* @__PURE__ */ React22.createElement(
20159
- import_dsh_client_ui_primitives4.DisclosureRow,
21570
+ return /* @__PURE__ */ React27.createElement(
21571
+ import_dsh_client_ui_primitives7.DisclosureRow,
20160
21572
  {
20161
- icon: /* @__PURE__ */ React22.createElement(Mark, null),
21573
+ icon: /* @__PURE__ */ React27.createElement(Mark, null),
20162
21574
  title: heading,
20163
21575
  open: disclosure.expanded,
20164
21576
  expandable: phase === "result" && text.length > 0,
20165
21577
  onToggle: disclosure.toggle,
20166
21578
  running: phase !== "result",
20167
21579
  expandOnRowClick: true,
20168
- collapsedContent: verdict ? /* @__PURE__ */ React22.createElement("span", { className: "dhl-tool-verdict" + (failed ? " err" : "") }, verdict) : null
21580
+ collapsedContent: verdict ? /* @__PURE__ */ React27.createElement("span", { className: "dhl-tool-verdict" + (failed ? " err" : "") }, verdict) : null
20169
21581
  },
20170
- phase === "result" ? /* @__PURE__ */ React22.createElement("pre", { className: "dhl-tool-output" }, text) : null
21582
+ phase === "result" ? /* @__PURE__ */ React27.createElement("pre", { className: "dhl-tool-output" }, text) : null
20171
21583
  );
20172
21584
  }
20173
21585
  function ApprovalDetail({ callId, useChat }) {
@@ -20180,12 +21592,12 @@ function ApprovalDetail({ callId, useChat }) {
20180
21592
  });
20181
21593
  if (!call) return null;
20182
21594
  const args = parseArgs(call.argsRaw);
20183
- if (typeof call.name === "string" && call.name.startsWith("hyper_")) return /* @__PURE__ */ React22.createElement(HyperApprovalCard, { name: call.name, args });
21595
+ if (typeof call.name === "string" && call.name.startsWith("hyper_")) return /* @__PURE__ */ React27.createElement(HyperApprovalCard, { name: call.name, args });
20184
21596
  return typeof args.command === "string" ? args.command : null;
20185
21597
  }
20186
21598
  function HyperApprovalCard({ name, args }) {
20187
- const [status, setStatus] = React22.useState(null);
20188
- React22.useEffect(() => {
21599
+ const [status, setStatus] = React27.useState(null);
21600
+ React27.useEffect(() => {
20189
21601
  let live = true;
20190
21602
  void hyperCall("status").then((value) => {
20191
21603
  if (live) setStatus(value);
@@ -20218,11 +21630,11 @@ function HyperApprovalCard({ name, args }) {
20218
21630
  if (args.reduceOnly) rows.push([tr("\u53EA\u51CF\u4ED3", "Reduce-only"), tr("\u662F", "Yes")]);
20219
21631
  }
20220
21632
  const limits = status?.riskLimits;
20221
- return /* @__PURE__ */ React22.createElement("div", { className: "dhl-approval-card" }, /* @__PURE__ */ React22.createElement("div", { className: "dhl-approval-head" }, /* @__PURE__ */ React22.createElement(Mark, null), /* @__PURE__ */ React22.createElement("strong", null, title), status ? /* @__PURE__ */ React22.createElement("span", { className: "dhl-approval-env" }, status.network === "mainnet" ? tr("\u4E3B\u7F51", "Mainnet") : tr("\u6D4B\u8BD5\u7F51", "Testnet"), " \xB7 ", status.tradingMode === "paper" ? tr("\u6A21\u62DF\u76D8", "Paper") : tr("\u5B9E\u76D8", "Live")) : null), /* @__PURE__ */ React22.createElement("dl", { className: "dhl-approval-rows" }, rows.map(([label, value, tone]) => /* @__PURE__ */ React22.createElement("div", { key: label }, /* @__PURE__ */ React22.createElement("dt", null, label), /* @__PURE__ */ React22.createElement("dd", { className: tone === "up" ? "dhl-up" : tone === "down" ? "dhl-down" : tone === "warn" ? "dhl-warn" : "" }, value)))), limits ? /* @__PURE__ */ React22.createElement("div", { className: "dhl-approval-limits" }, tr("\u4EA4\u6613\u9650\u989D", "Limits"), "\uFF1A", tr("\u5355\u7B14", "per order"), " \u2264 ", fmt(limits.maxOrderUsd), " USD \xB7 ", tr("\u4ECA\u65E5", "today"), " ", fmt(limits.usedTodayUsd ?? 0), " / ", fmt(limits.maxDailyUsd), " USD \xB7 ", tr("\u6760\u6746", "leverage"), " \u2264 ", fmt(limits.maxLeverage, 0), "x") : null, typeof args.reason === "string" && args.reason ? /* @__PURE__ */ React22.createElement("p", { className: "dhl-approval-reason" }, args.reason) : null);
21633
+ return /* @__PURE__ */ React27.createElement("div", { className: "dhl-approval-card" }, /* @__PURE__ */ React27.createElement("div", { className: "dhl-approval-head" }, /* @__PURE__ */ React27.createElement(Mark, null), /* @__PURE__ */ React27.createElement("strong", null, title), status ? /* @__PURE__ */ React27.createElement("span", { className: "dhl-approval-env" }, status.network === "mainnet" ? tr("\u4E3B\u7F51", "Mainnet") : tr("\u6D4B\u8BD5\u7F51", "Testnet"), " \xB7 ", status.tradingMode === "paper" ? tr("\u6A21\u62DF\u76D8", "Paper") : tr("\u5B9E\u76D8", "Live")) : null), /* @__PURE__ */ React27.createElement("dl", { className: "dhl-approval-rows" }, rows.map(([label, value, tone]) => /* @__PURE__ */ React27.createElement("div", { key: label }, /* @__PURE__ */ React27.createElement("dt", null, label), /* @__PURE__ */ React27.createElement("dd", { className: tone === "up" ? "dhl-up" : tone === "down" ? "dhl-down" : tone === "warn" ? "dhl-warn" : "" }, value)))), limits ? /* @__PURE__ */ React27.createElement("div", { className: "dhl-approval-limits" }, tr("\u4EA4\u6613\u9650\u989D", "Limits"), "\uFF1A", tr("\u5355\u7B14", "per order"), " \u2264 ", fmt(limits.maxOrderUsd), " USD \xB7 ", tr("\u4ECA\u65E5", "today"), " ", fmt(limits.usedTodayUsd ?? 0), " / ", fmt(limits.maxDailyUsd), " USD \xB7 ", tr("\u6760\u6746", "leverage"), " \u2264 ", fmt(limits.maxLeverage, 0), "x") : null, typeof args.reason === "string" && args.reason ? /* @__PURE__ */ React27.createElement("p", { className: "dhl-approval-reason" }, args.reason) : null);
20222
21634
  }
20223
21635
 
20224
21636
  // src/client/components/trade-ticket.tsx
20225
- var React23 = __toESM(require("react"), 1);
21637
+ var React28 = __toESM(require("react"), 1);
20226
21638
  function TradeTicket({ onSubmitted }) {
20227
21639
  const s = useStore();
20228
21640
  const coin = s.activeCoin;
@@ -20234,19 +21646,19 @@ function TradeTicket({ onSubmitted }) {
20234
21646
  const sharedBalance = s.account?.balanceSource === "spot";
20235
21647
  const capacity = s.account?.tradeCapacity?.coin === coin ? s.account.tradeCapacity : null;
20236
21648
  const available = Number(s.account?.mode === "paper" ? s.account?.availableMargin : sharedBalance ? capacity?.availableToTrade?.length === 2 ? Math.min(...capacity.availableToTrade.map(Number)) : NaN : s.account?.withdrawable);
20237
- const [isBuy, setIsBuy] = React23.useState(true);
20238
- const [kind, setKind] = React23.useState("market");
20239
- const [margin, setMargin] = React23.useState("cross");
20240
- const [leverage, setLeverage] = React23.useState(2);
20241
- const [size3, setSize] = React23.useState("");
20242
- const [price, setPrice] = React23.useState("");
20243
- const [pct, setPct] = React23.useState(0);
20244
- const [reduceOnly, setReduceOnly] = React23.useState(false);
20245
- const [withTargets, setWithTargets] = React23.useState(false);
20246
- const [tp, setTp] = React23.useState("");
20247
- const [sl, setSl] = React23.useState("");
20248
- const [reason, setReason] = React23.useState("");
20249
- const [enabled, setEnabled] = React23.useState(false);
21649
+ const [isBuy, setIsBuy] = React28.useState(true);
21650
+ const [kind, setKind] = React28.useState("market");
21651
+ const [margin, setMargin] = React28.useState("cross");
21652
+ const [leverage, setLeverage] = React28.useState(2);
21653
+ const [size3, setSize] = React28.useState("");
21654
+ const [price, setPrice] = React28.useState("");
21655
+ const [pct, setPct] = React28.useState(0);
21656
+ const [reduceOnly, setReduceOnly] = React28.useState(false);
21657
+ const [withTargets, setWithTargets] = React28.useState(false);
21658
+ const [tp, setTp] = React28.useState("");
21659
+ const [sl, setSl] = React28.useState("");
21660
+ const [reason, setReason] = React28.useState("");
21661
+ const [enabled, setEnabled] = React28.useState(false);
20250
21662
  const decimals = Number(market?.szDecimals ?? 5);
20251
21663
  const maxLeverage = Math.max(1, Math.min(50, Number(market?.maxLeverage ?? 2), Number(s.status?.riskLimits?.maxLeverage ?? 2)));
20252
21664
  const priceNum = kind === "limit" ? Number(price) : mark;
@@ -20258,20 +21670,20 @@ function TradeTicket({ onSubmitted }) {
20258
21670
  const bid = Number(s.snapshot?.book?.levels?.[0]?.[0]?.px);
20259
21671
  const ask = Number(s.snapshot?.book?.levels?.[1]?.[0]?.px);
20260
21672
  const spreadPct = bid > 0 && ask > bid ? (ask / bid - 1) * 100 : null;
20261
- React23.useEffect(() => {
21673
+ React28.useEffect(() => {
20262
21674
  setSize("");
20263
21675
  setPrice("");
20264
21676
  setPct(0);
20265
21677
  setEnabled(false);
20266
21678
  }, [coin]);
20267
- React23.useEffect(() => {
21679
+ React28.useEffect(() => {
20268
21680
  const draft = s.ticketDraft;
20269
21681
  if (!draft || draft.coin !== coin) return;
20270
21682
  setIsBuy(draft.isBuy);
20271
21683
  setKind("limit");
20272
21684
  setPrice(draft.px);
20273
21685
  }, [s.ticketDraft?.at, coin]);
20274
- React23.useEffect(() => {
21686
+ React28.useEffect(() => {
20275
21687
  if (leverage > maxLeverage) setLeverage(maxLeverage);
20276
21688
  }, [maxLeverage, leverage]);
20277
21689
  const setPercent = (next) => {
@@ -20344,31 +21756,36 @@ function TradeTicket({ onSubmitted }) {
20344
21756
  ];
20345
21757
  if (queueToAgent(parts.filter(Boolean).join("\n"))) onSubmitted?.();
20346
21758
  };
20347
- return /* @__PURE__ */ React23.createElement("section", { className: "dhl-ticket", "aria-label": tr("\u4EA4\u6613\u7968\u636E", "Trade ticket") }, /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-mode" }, /* @__PURE__ */ React23.createElement("label", null, tr("\u4FDD\u8BC1\u91D1\u6A21\u5F0F", "Margin mode"), /* @__PURE__ */ React23.createElement(Select, { value: margin, onChange: (e) => setMargin(e.target.value) }, /* @__PURE__ */ React23.createElement("option", { value: "cross" }, tr("\u5168\u4ED3", "Cross")), /* @__PURE__ */ React23.createElement("option", { value: "isolated" }, tr("\u9010\u4ED3", "Isolated")))), /* @__PURE__ */ React23.createElement("label", null, tr("\u6760\u6746", "Leverage"), /* @__PURE__ */ React23.createElement(Select, { value: leverage, onChange: (e) => setLeverage(Number(e.target.value)) }, Array.from({ length: maxLeverage }, (_, i) => i + 1).map((v) => /* @__PURE__ */ React23.createElement("option", { key: v, value: v }, v, "x"))))), /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-tabs", role: "tablist", "aria-label": tr("\u8BA2\u5355\u7C7B\u578B", "Order type") }, /* @__PURE__ */ React23.createElement("button", { type: "button", role: "tab", "aria-selected": kind === "market", className: kind === "market" ? "on" : "", onClick: () => setKind("market") }, tr("\u5E02\u4EF7", "Market")), /* @__PURE__ */ React23.createElement("button", { type: "button", role: "tab", "aria-selected": kind === "limit", className: kind === "limit" ? "on" : "", onClick: () => setKind("limit") }, tr("\u9650\u4EF7", "Limit"))), /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-sides", role: "group", "aria-label": tr("\u4EA4\u6613\u65B9\u5411", "Trade side") }, /* @__PURE__ */ React23.createElement("button", { type: "button", className: isBuy ? "buy on" : "buy", "aria-pressed": isBuy, onClick: () => setIsBuy(true) }, tr("\u4E70\u5165 / \u505A\u591A", "Buy / Long")), /* @__PURE__ */ React23.createElement("button", { type: "button", className: !isBuy ? "sell on" : "sell", "aria-pressed": !isBuy, onClick: () => setIsBuy(false) }, tr("\u5356\u51FA / \u505A\u7A7A", "Sell / Short"))), /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-balance" }, /* @__PURE__ */ React23.createElement("span", null, tr("\u53EF\u7528\u8D44\u91D1", "Available")), /* @__PURE__ */ React23.createElement("strong", null, Number.isFinite(available) ? fmt(available) : "\u2014", " USDC")), /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-balance" }, /* @__PURE__ */ React23.createElement("span", null, tr("\u5F53\u524D\u4ED3\u4F4D", "Current position")), /* @__PURE__ */ React23.createElement("strong", null, position ? fmt(position.size, decimals) : fmt(0, decimals), " ", coin)), kind === "limit" ? /* @__PURE__ */ React23.createElement("label", { className: "dhl-ticket-field" }, /* @__PURE__ */ React23.createElement("span", null, tr("\u9650\u4EF7", "Limit price")), /* @__PURE__ */ React23.createElement("div", null, /* @__PURE__ */ React23.createElement("input", { type: "number", min: "0", value: price, onChange: (e) => setPrice(e.target.value), placeholder: fmtPrice(mark) }), /* @__PURE__ */ React23.createElement("small", null, "USDC"))) : null, /* @__PURE__ */ React23.createElement("label", { className: "dhl-ticket-field" }, /* @__PURE__ */ React23.createElement("span", null, tr("\u6570\u91CF", "Size")), /* @__PURE__ */ React23.createElement("div", null, /* @__PURE__ */ React23.createElement("input", { type: "number", min: "0", step: 10 ** -decimals, value: size3, onChange: (e) => {
21759
+ return /* @__PURE__ */ React28.createElement("section", { className: "dhl-ticket", "aria-label": tr("\u4EA4\u6613\u7968\u636E", "Trade ticket") }, /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-mode" }, /* @__PURE__ */ React28.createElement("label", null, tr("\u4FDD\u8BC1\u91D1\u6A21\u5F0F", "Margin mode"), /* @__PURE__ */ React28.createElement(Select, { value: margin, onChange: (e) => setMargin(e.target.value) }, /* @__PURE__ */ React28.createElement("option", { value: "cross" }, tr("\u5168\u4ED3", "Cross")), /* @__PURE__ */ React28.createElement("option", { value: "isolated" }, tr("\u9010\u4ED3", "Isolated")))), /* @__PURE__ */ React28.createElement("label", null, tr("\u6760\u6746", "Leverage"), /* @__PURE__ */ React28.createElement(Select, { value: leverage, onChange: (e) => setLeverage(Number(e.target.value)) }, Array.from({ length: maxLeverage }, (_, i) => i + 1).map((v) => /* @__PURE__ */ React28.createElement("option", { key: v, value: v }, v, "x"))))), /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-tabs", role: "tablist", "aria-label": tr("\u8BA2\u5355\u7C7B\u578B", "Order type") }, /* @__PURE__ */ React28.createElement("button", { type: "button", role: "tab", "aria-selected": kind === "market", className: kind === "market" ? "on" : "", onClick: () => setKind("market") }, tr("\u5E02\u4EF7", "Market")), /* @__PURE__ */ React28.createElement("button", { type: "button", role: "tab", "aria-selected": kind === "limit", className: kind === "limit" ? "on" : "", onClick: () => setKind("limit") }, tr("\u9650\u4EF7", "Limit"))), /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-sides", role: "group", "aria-label": tr("\u4EA4\u6613\u65B9\u5411", "Trade side") }, /* @__PURE__ */ React28.createElement("button", { type: "button", className: isBuy ? "buy on" : "buy", "aria-pressed": isBuy, onClick: () => setIsBuy(true) }, tr("\u4E70\u5165 / \u505A\u591A", "Buy / Long")), /* @__PURE__ */ React28.createElement("button", { type: "button", className: !isBuy ? "sell on" : "sell", "aria-pressed": !isBuy, onClick: () => setIsBuy(false) }, tr("\u5356\u51FA / \u505A\u7A7A", "Sell / Short"))), /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-balance" }, /* @__PURE__ */ React28.createElement("span", null, tr("\u53EF\u7528\u8D44\u91D1", "Available")), /* @__PURE__ */ React28.createElement("strong", null, Number.isFinite(available) ? fmt(available) : "\u2014", " USDC")), /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-balance" }, /* @__PURE__ */ React28.createElement("span", null, tr("\u5F53\u524D\u4ED3\u4F4D", "Current position")), /* @__PURE__ */ React28.createElement("strong", null, position ? fmt(position.size, decimals) : fmt(0, decimals), " ", coin)), kind === "limit" ? /* @__PURE__ */ React28.createElement("label", { className: "dhl-ticket-field" }, /* @__PURE__ */ React28.createElement("span", null, tr("\u9650\u4EF7", "Limit price")), /* @__PURE__ */ React28.createElement("div", null, /* @__PURE__ */ React28.createElement("input", { type: "number", min: "0", value: price, onChange: (e) => setPrice(e.target.value), placeholder: fmtPrice(mark) }), /* @__PURE__ */ React28.createElement("small", null, "USDC"))) : null, /* @__PURE__ */ React28.createElement("label", { className: "dhl-ticket-field" }, /* @__PURE__ */ React28.createElement("span", null, tr("\u6570\u91CF", "Size")), /* @__PURE__ */ React28.createElement("div", null, /* @__PURE__ */ React28.createElement("input", { type: "number", min: "0", step: 10 ** -decimals, value: size3, onChange: (e) => {
20348
21760
  setSize(e.target.value);
20349
21761
  setPct(0);
20350
- }, placeholder: "0" }), /* @__PURE__ */ React23.createElement("small", null, coin))), /* @__PURE__ */ React23.createElement("label", { className: "dhl-ticket-reason" }, /* @__PURE__ */ React23.createElement("span", null, tr("\u4E0B\u5355\u539F\u56E0", "Order reason")), /* @__PURE__ */ React23.createElement("textarea", { value: reason, maxLength: 1e3, onChange: (e) => setReason(e.target.value), placeholder: tr("\u5199\u4E0B\u4EA4\u6613\u4F9D\u636E\u3001\u5931\u6548\u6761\u4EF6\u4E0E\u98CE\u9669", "Record the thesis, invalidation and risk"), rows: 3 })), /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-range" }, /* @__PURE__ */ React23.createElement("input", { type: "range", min: "0", max: "100", step: "25", value: pct, onChange: (e) => setPercent(Number(e.target.value)), "aria-label": tr("\u4F7F\u7528\u53EF\u7528\u989D\u5EA6\u767E\u5206\u6BD4", "Percent of available capacity") }), /* @__PURE__ */ React23.createElement("output", null, pct, "%")), /* @__PURE__ */ React23.createElement("label", { className: "dhl-ticket-check" }, /* @__PURE__ */ React23.createElement("input", { type: "checkbox", checked: reduceOnly, onChange: (e) => setReduceOnly(e.target.checked) }), tr("\u53EA\u51CF\u4ED3", "Reduce only")), /* @__PURE__ */ React23.createElement("label", { className: "dhl-ticket-check" }, /* @__PURE__ */ React23.createElement("input", { type: "checkbox", checked: withTargets, onChange: (e) => setWithTargets(e.target.checked) }), tr("\u6B62\u76C8 / \u6B62\u635F", "Take profit / Stop loss")), withTargets ? /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-targets" }, /* @__PURE__ */ React23.createElement("label", null, tr("\u6B62\u76C8\u4EF7", "Take profit"), /* @__PURE__ */ React23.createElement("input", { type: "number", min: "0", value: tp, onChange: (e) => setTp(e.target.value), placeholder: tr("\u53EF\u9009", "Optional") })), /* @__PURE__ */ React23.createElement("label", null, tr("\u6B62\u635F\u4EF7", "Stop loss"), /* @__PURE__ */ React23.createElement("input", { type: "number", min: "0", value: sl, onChange: (e) => setSl(e.target.value), placeholder: tr("\u53EF\u9009", "Optional") })), /* @__PURE__ */ React23.createElement("p", { className: "dhl-ticket-validation" }, tr("\u7236\u5355\u672A\u5B8C\u5168\u6210\u4EA4\u65F6\uFF0C\u6B62\u76C8\u6B62\u635F\u5C1A\u672A\u751F\u6548\uFF1B\u90E8\u5206\u6210\u4EA4\u540E\u64A4\u9500\u7236\u5355\u4F1A\u64A4\u9500\u5B50\u5355\u3002\u63D0\u4EA4\u540E\u8BF7\u6838\u5BF9\u4ED3\u4F4D\u4E0E\u5B9E\u9645\u4FDD\u62A4\u6302\u5355\u3002\u6A21\u62DF\u76D8\u6682\u4E0D\u652F\u6301\u89E6\u53D1\u5355\u3002", "Targets are inactive until the parent fully fills. Cancelling a partially filled parent cancels its children. Verify the final position and protection orders. Paper mode does not simulate triggers."))) : null, !enabled ? /* @__PURE__ */ React23.createElement("button", { type: "button", className: "dhl-ticket-enable", onClick: () => setEnabled(true) }, tr("\u542F\u7528\u4EA4\u6613\u7968\u636E", "Enable trade ticket")) : /* @__PURE__ */ React23.createElement("button", { type: "button", className: "dhl-ticket-submit", onClick: submit, disabled: errors.length > 0 }, tr("\u63D0\u4EA4\u5230\u4EA4\u6613\u4F1A\u8BDD", "Send to trading chat")), enabled && errors.length > 0 ? /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-validation", role: "status" }, errors[0]) : null, s.accountError ? /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-validation", role: "status" }, tr("\u8D26\u6237\u8BFB\u53D6\u5931\u8D25", "Account read failed"), "\uFF1A", s.accountError) : null, /* @__PURE__ */ React23.createElement("div", { className: "dhl-ticket-summary" }, /* @__PURE__ */ React23.createElement("div", null, /* @__PURE__ */ React23.createElement("span", null, tr("\u8BA2\u5355\u4EF7\u503C", "Order value")), /* @__PURE__ */ React23.createElement("strong", null, orderValue > 0 ? `${fmt(orderValue)} USDC` : "\u2014")), /* @__PURE__ */ React23.createElement("div", null, /* @__PURE__ */ React23.createElement("span", null, tr("\u9884\u4F30\u4FDD\u8BC1\u91D1", "Estimated margin")), /* @__PURE__ */ React23.createElement("strong", null, orderValue > 0 ? `${fmt(orderValue / leverage)} USDC` : "\u2014")), /* @__PURE__ */ React23.createElement("div", null, /* @__PURE__ */ React23.createElement("span", null, tr("\u53C2\u8003\u4EF7\u5DEE", "Reference spread")), /* @__PURE__ */ React23.createElement("strong", null, spreadPct == null ? "\u2014" : `${spreadPct.toFixed(3)}%`)), /* @__PURE__ */ React23.createElement("div", null, /* @__PURE__ */ React23.createElement("span", null, tr("\u624B\u7EED\u8D39", "Fees")), /* @__PURE__ */ React23.createElement("strong", null, tr("\u4EA4\u6613\u6240\u7ED3\u7B97", "Charged by exchange")))), /* @__PURE__ */ React23.createElement("p", { className: "dhl-ticket-note" }, tr("\u7968\u636E\u53EA\u63D0\u4EA4\u4EA4\u6613\u610F\u56FE\u3002\u5B9E\u9645\u4EF7\u683C\u3001\u6760\u6746\u4E0E\u8D26\u6237\u98CE\u9669\u7531\u4F1A\u8BDD\u5728\u6267\u884C\u524D\u91CD\u65B0\u6838\u5BF9\u3002", "The ticket submits a trade intent. The chat rechecks price, leverage, and account risk before execution.")), s.account?.mode === "paper" ? /* @__PURE__ */ React23.createElement("p", { className: "dhl-ticket-note" }, tr(s.account.simulation, "Fixed slippage; order book depth, fees, funding and liquidation are not simulated.")) : null);
21762
+ }, placeholder: "0" }), /* @__PURE__ */ React28.createElement("small", null, coin))), /* @__PURE__ */ React28.createElement("label", { className: "dhl-ticket-reason" }, /* @__PURE__ */ React28.createElement("span", null, tr("\u4E0B\u5355\u539F\u56E0", "Order reason")), /* @__PURE__ */ React28.createElement("textarea", { value: reason, maxLength: 1e3, onChange: (e) => setReason(e.target.value), placeholder: tr("\u5199\u4E0B\u4EA4\u6613\u4F9D\u636E\u3001\u5931\u6548\u6761\u4EF6\u4E0E\u98CE\u9669", "Record the thesis, invalidation and risk"), rows: 3 })), /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-range" }, /* @__PURE__ */ React28.createElement("input", { type: "range", min: "0", max: "100", step: "25", value: pct, onChange: (e) => setPercent(Number(e.target.value)), "aria-label": tr("\u4F7F\u7528\u53EF\u7528\u989D\u5EA6\u767E\u5206\u6BD4", "Percent of available capacity") }), /* @__PURE__ */ React28.createElement("output", null, pct, "%")), /* @__PURE__ */ React28.createElement("label", { className: "dhl-ticket-check" }, /* @__PURE__ */ React28.createElement("input", { type: "checkbox", checked: reduceOnly, onChange: (e) => setReduceOnly(e.target.checked) }), tr("\u53EA\u51CF\u4ED3", "Reduce only")), /* @__PURE__ */ React28.createElement("label", { className: "dhl-ticket-check" }, /* @__PURE__ */ React28.createElement("input", { type: "checkbox", checked: withTargets, onChange: (e) => setWithTargets(e.target.checked) }), tr("\u6B62\u76C8 / \u6B62\u635F", "Take profit / Stop loss")), withTargets ? /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-targets" }, /* @__PURE__ */ React28.createElement("label", null, tr("\u6B62\u76C8\u4EF7", "Take profit"), /* @__PURE__ */ React28.createElement("input", { type: "number", min: "0", value: tp, onChange: (e) => setTp(e.target.value), placeholder: tr("\u53EF\u9009", "Optional") })), /* @__PURE__ */ React28.createElement("label", null, tr("\u6B62\u635F\u4EF7", "Stop loss"), /* @__PURE__ */ React28.createElement("input", { type: "number", min: "0", value: sl, onChange: (e) => setSl(e.target.value), placeholder: tr("\u53EF\u9009", "Optional") })), /* @__PURE__ */ React28.createElement("p", { className: "dhl-ticket-validation" }, tr("\u7236\u5355\u672A\u5B8C\u5168\u6210\u4EA4\u65F6\uFF0C\u6B62\u76C8\u6B62\u635F\u5C1A\u672A\u751F\u6548\uFF1B\u90E8\u5206\u6210\u4EA4\u540E\u64A4\u9500\u7236\u5355\u4F1A\u64A4\u9500\u5B50\u5355\u3002\u63D0\u4EA4\u540E\u8BF7\u6838\u5BF9\u4ED3\u4F4D\u4E0E\u5B9E\u9645\u4FDD\u62A4\u6302\u5355\u3002\u6A21\u62DF\u76D8\u6682\u4E0D\u652F\u6301\u89E6\u53D1\u5355\u3002", "Targets are inactive until the parent fully fills. Cancelling a partially filled parent cancels its children. Verify the final position and protection orders. Paper mode does not simulate triggers."))) : null, !enabled ? /* @__PURE__ */ React28.createElement("button", { type: "button", className: "dhl-ticket-enable", onClick: () => setEnabled(true) }, tr("\u542F\u7528\u4EA4\u6613\u7968\u636E", "Enable trade ticket")) : /* @__PURE__ */ React28.createElement("button", { type: "button", className: "dhl-ticket-submit", onClick: submit, disabled: errors.length > 0 }, tr("\u63D0\u4EA4\u5230\u4EA4\u6613\u4F1A\u8BDD", "Send to trading chat")), enabled && errors.length > 0 ? /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-validation", role: "status" }, errors[0]) : null, s.accountError ? /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-validation", role: "status" }, tr("\u8D26\u6237\u8BFB\u53D6\u5931\u8D25", "Account read failed"), "\uFF1A", s.accountError) : null, /* @__PURE__ */ React28.createElement("div", { className: "dhl-ticket-summary" }, /* @__PURE__ */ React28.createElement("div", null, /* @__PURE__ */ React28.createElement("span", null, tr("\u8BA2\u5355\u4EF7\u503C", "Order value")), /* @__PURE__ */ React28.createElement("strong", null, orderValue > 0 ? `${fmt(orderValue)} USDC` : "\u2014")), /* @__PURE__ */ React28.createElement("div", null, /* @__PURE__ */ React28.createElement("span", null, tr("\u9884\u4F30\u4FDD\u8BC1\u91D1", "Estimated margin")), /* @__PURE__ */ React28.createElement("strong", null, orderValue > 0 ? `${fmt(orderValue / leverage)} USDC` : "\u2014")), /* @__PURE__ */ React28.createElement("div", null, /* @__PURE__ */ React28.createElement("span", null, tr("\u53C2\u8003\u4EF7\u5DEE", "Reference spread")), /* @__PURE__ */ React28.createElement("strong", null, spreadPct == null ? "\u2014" : `${spreadPct.toFixed(3)}%`)), /* @__PURE__ */ React28.createElement("div", null, /* @__PURE__ */ React28.createElement("span", null, tr("\u624B\u7EED\u8D39", "Fees")), /* @__PURE__ */ React28.createElement("strong", null, tr("\u4EA4\u6613\u6240\u7ED3\u7B97", "Charged by exchange")))), /* @__PURE__ */ React28.createElement("p", { className: "dhl-ticket-note" }, tr("\u7968\u636E\u53EA\u63D0\u4EA4\u4EA4\u6613\u610F\u56FE\u3002\u5B9E\u9645\u4EF7\u683C\u3001\u6760\u6746\u4E0E\u8D26\u6237\u98CE\u9669\u7531\u4F1A\u8BDD\u5728\u6267\u884C\u524D\u91CD\u65B0\u6838\u5BF9\u3002", "The ticket submits a trade intent. The chat rechecks price, leverage, and account risk before execution.")), s.account?.mode === "paper" ? /* @__PURE__ */ React28.createElement("p", { className: "dhl-ticket-note" }, tr(s.account.simulation, "Configured slippage and fees; depth, funding and liquidation are not simulated.")) : null);
21763
+ }
21764
+
21765
+ // src/client/native-session-list.ts
21766
+ function readNativeSessionList(list) {
21767
+ return list?.getSnapshot?.() ?? list?.get?.() ?? null;
20351
21768
  }
20352
21769
 
20353
21770
  // src/client/components/chat-column.tsx
20354
21771
  function FixedChatView({ renderSlot }) {
20355
- return /* @__PURE__ */ React24.createElement(React24.Fragment, null, renderSlot("conversation.session", { view: "chat" }));
21772
+ return /* @__PURE__ */ React29.createElement(React29.Fragment, null, renderSlot("conversation.session", { view: "chat" }));
20356
21773
  }
20357
21774
  function ChatColumn({ width, fill = false, onCollapse, SessionProvider, renderFactorySlot }) {
20358
21775
  const s = useStore();
20359
21776
  const activeId = s.activeSessionId;
20360
21777
  const nativeKnown = Boolean(activeId && s.nativeSessionIds.includes(activeId));
20361
- const [bindError, setBindError] = React24.useState(null);
20362
- const [ref, setRef] = React24.useState(null);
20363
- const [legacy, setLegacy] = React24.useState(false);
20364
- const [showTicket, setShowTicket] = React24.useState(false);
20365
- React24.useEffect(() => {
21778
+ const [bindError, setBindError] = React29.useState(null);
21779
+ const [ref, setRef] = React29.useState(null);
21780
+ const [legacy, setLegacy] = React29.useState(false);
21781
+ const [showTicket, setShowTicket] = React29.useState(false);
21782
+ React29.useEffect(() => {
20366
21783
  if (s.ticketDraft) setShowTicket(true);
20367
21784
  }, [s.ticketDraft?.at]);
20368
- React24.useEffect(() => {
21785
+ React29.useEffect(() => {
20369
21786
  setShowTicket(false);
20370
21787
  }, [s.activeSessionId]);
20371
- React24.useEffect(() => {
21788
+ React29.useEffect(() => {
20372
21789
  setActiveRef(null);
20373
21790
  setRef(null);
20374
21791
  setLegacy(false);
@@ -20380,7 +21797,7 @@ function ChatColumn({ width, fill = false, onCollapse, SessionProvider, renderFa
20380
21797
  try {
20381
21798
  const sessionsSvc = getContext()?.get("sessions");
20382
21799
  if (!sessionsSvc) throw new Error(tr("sessions \u670D\u52A1\u4E0D\u53EF\u7528", "DSH sessions service unavailable"));
20383
- const nativeList = sessionsSvc.list?.get?.();
21800
+ const nativeList = readNativeSessionList(sessionsSvc.list);
20384
21801
  if (nativeList && !nativeList.byId?.[activeId]) {
20385
21802
  if (nativeList.phase === "ready") throw new Error(tr("\u8BE5\u4EA4\u6613\u4F1A\u8BDD\u5DF2\u4E0D\u5728 DSH \u4F1A\u8BDD\u5217\u8868\u4E2D", "This trading session no longer exists in DSH"));
20386
21803
  return;
@@ -20417,17 +21834,17 @@ function ChatColumn({ width, fill = false, onCollapse, SessionProvider, renderFa
20417
21834
  }
20418
21835
  };
20419
21836
  }, [activeId, nativeKnown, s.nativeSessionsReady]);
20420
- const title = React24.useMemo(() => {
21837
+ const title = React29.useMemo(() => {
20421
21838
  if (!activeId) return "";
20422
- const row = getContext()?.get("sessions")?.list?.get?.()?.byId?.[activeId];
21839
+ const row = readNativeSessionList(getContext()?.get("sessions")?.list)?.byId?.[activeId];
20423
21840
  return s.sessionTitles?.[activeId] ?? row?.displayTitle ?? s.sessions.find((r) => r.sessionId === activeId)?.title ?? t("session.defaultTitle", "Hyperliquid \u4EA4\u6613\u4F1A\u8BDD");
20424
21841
  }, [activeId, s.sessions, s.sessionTitles, s.localeRevision]);
20425
21842
  const readyRef = ref?.sessionId === activeId ? ref : null;
20426
21843
  const pendingQuotes = s.chartReferences.filter((item) => item.sessionId === activeId);
20427
- React24.useEffect(() => {
21844
+ React29.useEffect(() => {
20428
21845
  if (pendingQuotes.length && showTicket) setShowTicket(false);
20429
21846
  }, [pendingQuotes.length, showTicket]);
20430
- React24.useEffect(() => {
21847
+ React29.useEffect(() => {
20431
21848
  if (!pendingQuotes.length || !readyRef || showTicket) return;
20432
21849
  const sessionCtx = readyRef.binding?.ctx;
20433
21850
  const input = sessionCtx ? getContext()?.get("conversation")?.input?.for?.(sessionCtx) : null;
@@ -20457,25 +21874,25 @@ function ChatColumn({ width, fill = false, onCollapse, SessionProvider, renderFa
20457
21874
  requestAnimationFrame(() => input.focus?.());
20458
21875
  }
20459
21876
  }, [activeId, readyRef, showTicket, s.chartReferences]);
20460
- const askMarket = React24.useCallback(() => {
21877
+ const askMarket = React29.useCallback(() => {
20461
21878
  queueToAgent(tr(
20462
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`,
20463
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.`
20464
21881
  ));
20465
21882
  }, [s.activeCoin]);
20466
- const askRisk = React24.useCallback(() => {
21883
+ const askRisk = React29.useCallback(() => {
20467
21884
  queueToAgent(tr(
20468
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`,
20469
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.`
20470
21887
  ));
20471
21888
  }, [s.activeCoin]);
20472
- const content = !activeId ? /* @__PURE__ */ React24.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__ */ React24.createElement("div", { className: "dhl-inline-error", role: "alert" }, t("chat.bindError", "\u4F1A\u8BDD\u7ED1\u5B9A\u5931\u8D25\uFF1A") + bindError) : legacy ? /* @__PURE__ */ React24.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__ */ React24.createElement("div", { className: "dhl-empty" }, t("chat.linking", "\u6B63\u5728\u63A5\u5165\u4F1A\u8BDD\u2026")) : !SessionProvider || !renderFactorySlot ? /* @__PURE__ */ React24.createElement("div", { className: "dhl-inline-error", role: "alert" }, t("chat.noFactory", "DSH \u539F\u751F\u4F1A\u8BDD\u7EC4\u4EF6\u4E0D\u53EF\u7528\u3002")) : /* @__PURE__ */ React24.createElement("div", { className: "dhl-chatmount", "data-trading-session": activeId }, /* @__PURE__ */ React24.createElement(SessionProvider, { session: readyRef }, renderFactorySlot("conversation.content", { variant: "embedded", phase: "active", hero: false }, { slots: { views: FixedChatView } })));
20473
- return /* @__PURE__ */ React24.createElement("div", { id: "dhl-panel-chat", className: "dhl-col-chat", style: fill ? { flex: "1 1 0", width: "auto" } : width ? { width } : void 0 }, /* @__PURE__ */ React24.createElement("div", { className: "dhl-chat-header" }, /* @__PURE__ */ React24.createElement("div", { className: "dhl-chat-heading" }, /* @__PURE__ */ React24.createElement("strong", null, t("chat.title", "\u4EA4\u6613\u4F1A\u8BDD")), activeId ? /* @__PURE__ */ React24.createElement("span", { title }, title) : null), /* @__PURE__ */ React24.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__ */ React24.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__ */ React24.createElement(FoldIcon, { direction: "right" }))), activeId && !showTicket && !legacy ? /* @__PURE__ */ React24.createElement("div", { className: "dhl-chat-prompts", role: "group", "aria-label": t("chat.prompts", "\u5FEB\u6377\u63D0\u95EE") }, /* @__PURE__ */ React24.createElement("button", { type: "button", onClick: askMarket }, t("chat.analyze", "\u5206\u6790\u884C\u60C5")), /* @__PURE__ */ React24.createElement("button", { type: "button", onClick: askRisk }, t("chat.risk", "\u68C0\u67E5\u98CE\u9669"))) : null, showTicket ? /* @__PURE__ */ React24.createElement(TradeTicket, { key: activeId ?? "anonymous", onSubmitted: () => setShowTicket(false) }) : content);
21889
+ 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);
20474
21891
  }
20475
21892
 
20476
21893
  // src/client/components/wallet-details.tsx
20477
- var React25 = __toESM(require("react"), 1);
20478
- var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
21894
+ var React30 = __toESM(require("react"), 1);
21895
+ var import_dsh_client_ui_primitives8 = require("@deepseek-ai/dsh-client-ui-primitives");
20479
21896
  var PERIODS = [
20480
21897
  ["day", "24 \u5C0F\u65F6", "24h"],
20481
21898
  ["week", "7 \u5929", "7d"],
@@ -20488,15 +21905,15 @@ function ProfitLine({ points: points2 }) {
20488
21905
  const min2 = Math.min(...values);
20489
21906
  const span = Math.max(...values) - min2 || 1;
20490
21907
  const path = values.map((value, index) => `${index ? "L" : "M"}${(index / (values.length - 1) * 100).toFixed(2)},${(40 - (value - min2) / span * 36).toFixed(2)}`).join(" ");
20491
- return /* @__PURE__ */ React25.createElement("svg", { className: "dhl-wallet-graph", viewBox: "0 0 100 44", preserveAspectRatio: "none", role: "img", "aria-label": tr("\u6536\u76CA\u8D70\u52BF", "P&L trend") }, /* @__PURE__ */ React25.createElement("path", { d: path, fill: "none", stroke: "currentColor", strokeWidth: "1.5", vectorEffect: "non-scaling-stroke" }));
21908
+ return /* @__PURE__ */ React30.createElement("svg", { className: "dhl-wallet-graph", viewBox: "0 0 100 44", preserveAspectRatio: "none", role: "img", "aria-label": tr("\u6536\u76CA\u8D70\u52BF", "P&L trend") }, /* @__PURE__ */ React30.createElement("path", { d: path, fill: "none", stroke: "currentColor", strokeWidth: "1.5", vectorEffect: "non-scaling-stroke" }));
20492
21909
  }
20493
21910
  function WalletDetails({ open, onClose }) {
20494
21911
  const s = useStore();
20495
- const [period, setPeriod] = React25.useState("day");
20496
- const [portfolio, setPortfolio] = React25.useState(null);
20497
- const [error, setError] = React25.useState(null);
20498
- const [loading, setLoading] = React25.useState(false);
20499
- React25.useEffect(() => {
21912
+ const [period, setPeriod] = React30.useState("day");
21913
+ const [portfolio, setPortfolio] = React30.useState(null);
21914
+ const [error, setError] = React30.useState(null);
21915
+ const [loading, setLoading] = React30.useState(false);
21916
+ React30.useEffect(() => {
20500
21917
  if (!open) return void 0;
20501
21918
  let active = true;
20502
21919
  setPortfolio(null);
@@ -20521,7 +21938,8 @@ function WalletDetails({ open, onClose }) {
20521
21938
  const stats = account?.mode === "paper" ? [
20522
21939
  [tr("\u8D26\u6237\u6743\u76CA", "Equity"), `${fmt(account.equity)} USDC`],
20523
21940
  [tr("\u53EF\u7528\u4FDD\u8BC1\u91D1", "Available margin"), `${fmt(account.availableMargin)} USDC`],
20524
- [tr("\u7D2F\u8BA1\u5DF2\u5B9E\u73B0\u6536\u76CA", "Total realized P&L"), `${fmt(portfolio?.realizedPnl)} USDC`],
21941
+ [tr("\u4FDD\u7559\u6210\u4EA4\u6BDB\u76C8\u4E8F", "Retained fills gross P&L"), `${fmt(portfolio?.realizedPnl)} USDC`],
21942
+ [tr("\u4FDD\u7559\u6210\u4EA4\u6263\u8D39\u51C0\u989D", "Retained fills net after fees"), portfolio?.netRealizedPnl == null ? "\u2014" : `${fmt(portfolio.netRealizedPnl)} USDC`],
20525
21943
  [tr("\u672A\u5B9E\u73B0\u6536\u76CA", "Unrealized P&L"), `${fmt(account.uPnl)} USDC`]
20526
21944
  ] : [
20527
21945
  [shared ? tr("USDC \u4F59\u989D", "USDC balance") : tr("\u6C38\u7EED\u8D26\u6237\u4EF7\u503C", "Perp account value"), `${fmt(shared ? account?.usdcBalance : account?.marginSummary?.accountValue)} USDC`],
@@ -20529,12 +21947,12 @@ function WalletDetails({ open, onClose }) {
20529
21947
  [tr("\u5F53\u524D\u4ED3\u4F4D\u672A\u5B9E\u73B0\u6536\u76CA", "Open position P&L"), `${fmt((account?.positions ?? []).reduce((sum, p) => sum + Number(p.uPnl ?? 0), 0))} USDC`],
20530
21948
  [tr("\u4EA4\u6613\u6240\u7EC4\u5408\u8D26\u6237\u4EF7\u503C", "Exchange portfolio value"), Number.isFinite(value) ? `${fmt(value)} USDC` : "\u2014"]
20531
21949
  ];
20532
- return /* @__PURE__ */ React25.createElement(import_dsh_client_ui_primitives5.Modal, { open, onClose, title: tr("\u94B1\u5305\u8BE6\u60C5\u4E0E\u6536\u76CA", "Wallet and P&L"), closeLabel: tr("\u5173\u95ED", "Close"), className: "dhl-wallet-modal" }, /* @__PURE__ */ React25.createElement("div", { className: "dhl-wallet-address" }, s.status?.address ?? tr("\u6A21\u62DF\u8D26\u6237 \xB7 \u672C\u5730\u8D26\u672C", "Paper account \xB7 local ledger")), /* @__PURE__ */ React25.createElement("div", { className: "dhl-wallet-stats" }, stats.map(([label, amount]) => /* @__PURE__ */ React25.createElement("div", { key: label }, /* @__PURE__ */ React25.createElement("span", null, label), /* @__PURE__ */ React25.createElement("strong", null, amount)))), account?.mode === "live" ? /* @__PURE__ */ React25.createElement(React25.Fragment, null, /* @__PURE__ */ React25.createElement("div", { className: "dhl-wallet-periods" }, PERIODS.map(([id, zh, en]) => /* @__PURE__ */ React25.createElement("button", { type: "button", key: id, className: period === id ? "on" : "", onClick: () => setPeriod(id) }, tr(zh, en)))), /* @__PURE__ */ React25.createElement("div", { className: "dhl-wallet-pnl" }, /* @__PURE__ */ React25.createElement("span", null, tr("\u671F\u95F4\u6536\u76CA", "Period P&L")), /* @__PURE__ */ React25.createElement("strong", { className: pnl >= 0 ? "dhl-up" : "dhl-down" }, Number.isFinite(pnl) ? `${pnl >= 0 ? "+" : ""}${fmt(pnl)} USDC` : "\u2014")), /* @__PURE__ */ React25.createElement(ProfitLine, { points: sample?.pnlHistory ?? [] }), /* @__PURE__ */ React25.createElement("p", { className: "dhl-ticket-note" }, tr("\u6536\u76CA\u66F2\u7EBF\u4F7F\u7528 Hyperliquid \u7EC4\u5408\u8D26\u6237\u6837\u672C\uFF08\u542B\u5165\u51FA\u91D1\u8C03\u6574\uFF09\uFF1B\u4E0E\u5F53\u524D\u53EF\u4EA4\u6613\u4F59\u989D\u53E3\u5F84\u4E0D\u540C\u3002", "P&L uses Hyperliquid portfolio samples adjusted for deposits and withdrawals; it differs from tradable balance."))) : /* @__PURE__ */ React25.createElement("p", { className: "dhl-ticket-note" }, tr("\u6A21\u62DF\u76D8\u6536\u76CA\u6309\u672C\u5730\u6210\u4EA4\u4E0E\u5F53\u524D\u6807\u8BB0\u4EF7\u683C\u8BA1\u7B97\uFF0C\u672A\u8BA1\u624B\u7EED\u8D39\u3001\u8D44\u91D1\u8D39\u53CA\u5F3A\u5E73\u3002", "Paper P&L uses local fills and current marks, excluding fees, funding and liquidation.")), loading ? /* @__PURE__ */ React25.createElement("div", { className: "dhl-empty" }, tr("\u6B63\u5728\u8BFB\u53D6\u6536\u76CA\u2026", "Loading P&L\u2026")) : null, error ? /* @__PURE__ */ React25.createElement("div", { className: "dhl-inline-error", role: "status" }, error) : null);
21950
+ return /* @__PURE__ */ React30.createElement(import_dsh_client_ui_primitives8.Modal, { open, onClose, title: tr("\u94B1\u5305\u8BE6\u60C5\u4E0E\u6536\u76CA", "Wallet and P&L"), closeLabel: tr("\u5173\u95ED", "Close"), className: "dhl-wallet-modal" }, /* @__PURE__ */ React30.createElement("div", { className: "dhl-wallet-address" }, s.status?.address ?? tr("\u6A21\u62DF\u8D26\u6237 \xB7 \u672C\u5730\u8D26\u672C", "Paper account \xB7 local ledger")), /* @__PURE__ */ React30.createElement("div", { className: "dhl-wallet-stats" }, stats.map(([label, amount]) => /* @__PURE__ */ React30.createElement("div", { key: label }, /* @__PURE__ */ React30.createElement("span", null, label), /* @__PURE__ */ React30.createElement("strong", null, amount)))), account?.mode === "live" ? /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement("div", { className: "dhl-wallet-periods" }, PERIODS.map(([id, zh, en]) => /* @__PURE__ */ React30.createElement("button", { type: "button", key: id, className: period === id ? "on" : "", onClick: () => setPeriod(id) }, tr(zh, en)))), /* @__PURE__ */ React30.createElement("div", { className: "dhl-wallet-pnl" }, /* @__PURE__ */ React30.createElement("span", null, tr("\u671F\u95F4\u6536\u76CA", "Period P&L")), /* @__PURE__ */ React30.createElement("strong", { className: pnl >= 0 ? "dhl-up" : "dhl-down" }, Number.isFinite(pnl) ? `${pnl >= 0 ? "+" : ""}${fmt(pnl)} USDC` : "\u2014")), /* @__PURE__ */ React30.createElement(ProfitLine, { points: sample?.pnlHistory ?? [] }), /* @__PURE__ */ React30.createElement("p", { className: "dhl-ticket-note" }, tr("\u6536\u76CA\u66F2\u7EBF\u4F7F\u7528 Hyperliquid \u7EC4\u5408\u8D26\u6237\u6837\u672C\uFF08\u542B\u5165\u51FA\u91D1\u8C03\u6574\uFF09\uFF1B\u4E0E\u5F53\u524D\u53EF\u4EA4\u6613\u4F59\u989D\u53E3\u5F84\u4E0D\u540C\u3002", "P&L uses Hyperliquid portfolio samples adjusted for deposits and withdrawals; it differs from tradable balance."))) : /* @__PURE__ */ React30.createElement("p", { className: "dhl-ticket-note" }, tr("\u6A21\u62DF\u6743\u76CA\u5DF2\u6263\u9664\u65B0\u6210\u4EA4\u7684\u914D\u7F6E\u624B\u7EED\u8D39\uFF1B\u5386\u53F2\u7EDF\u8BA1\u4EC5\u8986\u76D6\u4FDD\u7559\u6210\u4EA4\uFF0C\u65E7\u8BB0\u5F55\u8D39\u7528\u53EF\u80FD\u672A\u77E5\u3002\u672A\u6A21\u62DF\u8D44\u91D1\u8D39\u53CA\u5F3A\u5E73\u3002", "Paper equity deducts configured fees on new fills. History totals cover retained fills only; legacy fees may be unknown. No funding or liquidation simulation.")), loading ? /* @__PURE__ */ React30.createElement("div", { className: "dhl-empty" }, tr("\u6B63\u5728\u8BFB\u53D6\u6536\u76CA\u2026", "Loading P&L\u2026")) : null, error ? /* @__PURE__ */ React30.createElement("div", { className: "dhl-inline-error", role: "status" }, error) : null);
20533
21951
  }
20534
21952
 
20535
21953
  // src/client/components/trading-limits.tsx
20536
- var React26 = __toESM(require("react"), 1);
20537
- var import_dsh_client_ui_primitives6 = require("@deepseek-ai/dsh-client-ui-primitives");
21954
+ var React31 = __toESM(require("react"), 1);
21955
+ var import_dsh_client_ui_primitives9 = require("@deepseek-ai/dsh-client-ui-primitives");
20538
21956
  var EMPTY = { allowedCoins: "", maxOrderUsd: "", maxDailyUsd: "", maxPositionUsd: "", maxLeverage: "" };
20539
21957
  function fromLimits(limits) {
20540
21958
  return {
@@ -20549,16 +21967,20 @@ function TradingLimits({ open, onClose }) {
20549
21967
  const st = useStore().status;
20550
21968
  const limits = st?.riskLimits;
20551
21969
  const paper = st?.tradingMode === "paper";
20552
- const [draft, setDraft] = React26.useState(EMPTY);
20553
- const [busy, setBusy] = React26.useState(false);
20554
- const [error, setError] = React26.useState("");
20555
- React26.useEffect(() => {
21970
+ const [draft, setDraft] = React31.useState(EMPTY);
21971
+ const [busy, setBusy] = React31.useState(false);
21972
+ const [error, setError] = React31.useState("");
21973
+ const [costs, setCosts] = React31.useState({ feeBps: "4.5", slippageBps: "5" });
21974
+ React31.useEffect(() => {
21975
+ if (open) setCosts({ feeBps: String(st?.paperCosts?.feeBps ?? 4.5), slippageBps: String(st?.paperCosts?.slippageBps ?? 5) });
21976
+ }, [open]);
21977
+ React31.useEffect(() => {
20556
21978
  if (open && limits) {
20557
21979
  setDraft(fromLimits(limits));
20558
21980
  setError("");
20559
21981
  }
20560
21982
  }, [open, Boolean(limits)]);
20561
- const change = (key2, value) => setDraft((current2) => ({ ...current2, [key2]: value }));
21983
+ const change = (key3, value) => setDraft((current2) => ({ ...current2, [key3]: value }));
20562
21984
  const save = async (event) => {
20563
21985
  event?.preventDefault();
20564
21986
  setBusy(true);
@@ -20593,18 +22015,22 @@ function TradingLimits({ open, onClose }) {
20593
22015
  setBusy(false);
20594
22016
  }
20595
22017
  };
20596
- const field = (key2, label, props, wide = false) => /* @__PURE__ */ React26.createElement("label", { className: wide ? "dhl-risk-wide" : void 0 }, /* @__PURE__ */ React26.createElement("span", null, label), /* @__PURE__ */ React26.createElement(import_dsh_client_ui_primitives6.Input, { className: "dhl-risk-input", value: draft[key2], onChange: (event) => change(key2, event.target.value), required: true, ...props }));
20597
- const footer = limits ? /* @__PURE__ */ React26.createElement(React26.Fragment, null, st?.riskSettingsSource === "desk" ? /* @__PURE__ */ React26.createElement(import_dsh_client_ui_primitives6.Button, { variant: "outline", disabled: busy, onClick: () => void reset() }, tr("\u6062\u590D\u63D2\u4EF6\u914D\u7F6E", "Restore plugin config")) : null, /* @__PURE__ */ React26.createElement(import_dsh_client_ui_primitives6.Button, { variant: "primary", type: "submit", form: "dhl-risk-form", disabled: busy }, busy ? tr("\u4FDD\u5B58\u4E2D\u2026", "Saving\u2026") : tr("\u4FDD\u5B58", "Save"))) : null;
20598
- return /* @__PURE__ */ React26.createElement(import_dsh_client_ui_primitives6.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__ */ React26.createElement("section", { className: "dhl-limits-section" }, /* @__PURE__ */ React26.createElement("h3", null, tr("\u4EA4\u6613\u9650\u989D", "Trading limits"), /* @__PURE__ */ React26.createElement("span", { className: "dhl-limits-ledger" }, paper ? tr("\u6A21\u62DF\u76D8\u8D26\u672C", "Paper ledger") : tr("\u5B9E\u76D8\u8D26\u672C", "Live ledger"))), limits ? /* @__PURE__ */ React26.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__ */ React26.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__ */ React26.createElement("p", { className: "dhl-risk-error", role: "alert" }, error) : null) : /* @__PURE__ */ React26.createElement("div", { className: "dhl-empty" }, tr("\u6B63\u5728\u8BFB\u53D6\u2026", "Loading\u2026")), /* @__PURE__ */ React26.createElement("p", { className: "dhl-limits-note" }, tr(
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(
20599
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",
20600
22022
  "Changes apply immediately to paper and live trading and survive restart. Each mode has its own daily budget; reducing and closing remain available."
20601
- ))), /* @__PURE__ */ React26.createElement("section", { className: "dhl-limits-section" }, /* @__PURE__ */ React26.createElement("h3", null, tr("\u94B1\u5305\u7B7E\u540D", "Wallet signing")), /* @__PURE__ */ React26.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(
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: () => {
22024
+ setBusy(true);
22025
+ setError("");
22026
+ 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(
20602
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",
20603
22029
  "After the limits check, live orders use the dsh-web3 wallet signing policy. Manage it in DSH Settings \u2192 Web3 wallet."
20604
22030
  ) : tr(
20605
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",
20606
22032
  "Live orders use the private key from plugin configuration. Manage the key and signing method in DSH plugin settings."
20607
- )), !paper && !st?.autonomousLive ? /* @__PURE__ */ React26.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));
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));
20608
22034
  }
20609
22035
 
20610
22036
  // src/client/app.tsx
@@ -20612,15 +22038,15 @@ function App(props) {
20612
22038
  const s = useStore();
20613
22039
  const st = s.status;
20614
22040
  const marketStale = isMarketStale(s);
20615
- const [confirmLive, setConfirmLive] = React27.useState(null);
20616
- const [networkBusy, setNetworkBusy] = React27.useState(false);
20617
- const [walletOpen, setWalletOpen] = React27.useState(false);
20618
- const [moreOpen, setMoreOpen] = React27.useState(false);
20619
- const [limitsOpen, setLimitsOpen] = React27.useState(false);
20620
- const moreRef = React27.useRef(null);
22041
+ const [confirmLive, setConfirmLive] = React32.useState(null);
22042
+ const [networkBusy, setNetworkBusy] = React32.useState(false);
22043
+ const [walletOpen, setWalletOpen] = React32.useState(false);
22044
+ const [moreOpen, setMoreOpen] = React32.useState(false);
22045
+ const [limitsOpen, setLimitsOpen] = React32.useState(false);
22046
+ const moreRef = React32.useRef(null);
20621
22047
  const network = st?.network === "testnet" ? "testnet" : "mainnet";
20622
22048
  const mode = st?.tradingMode === "paper" ? "paper" : "live";
20623
- const applyEnv = React27.useCallback(async (next) => {
22049
+ const applyEnv = React32.useCallback(async (next) => {
20624
22050
  setNetworkBusy(true);
20625
22051
  try {
20626
22052
  const current2 = store.get().status;
@@ -20633,12 +22059,12 @@ function App(props) {
20633
22059
  setConfirmLive(null);
20634
22060
  }
20635
22061
  }, []);
20636
- const requestEnv = React27.useCallback((next) => {
22062
+ const requestEnv = React32.useCallback((next) => {
20637
22063
  setMoreOpen(false);
20638
22064
  if (next.network === "mainnet" && next.mode === "live") setConfirmLive(next);
20639
22065
  else void applyEnv(next);
20640
22066
  }, [applyEnv]);
20641
- React27.useEffect(() => {
22067
+ React32.useEffect(() => {
20642
22068
  if (!moreOpen) return void 0;
20643
22069
  const onDown = (event) => {
20644
22070
  if (!moreRef.current?.contains(event.target)) setMoreOpen(false);
@@ -20653,12 +22079,12 @@ function App(props) {
20653
22079
  window.removeEventListener("keydown", onKey);
20654
22080
  };
20655
22081
  }, [moreOpen]);
20656
- React27.useEffect(() => startPolling(), []);
20657
- React27.useEffect(() => {
22082
+ React32.useEffect(() => startPolling(), []);
22083
+ React32.useEffect(() => {
20658
22084
  const list = getContext()?.get("sessions")?.list;
20659
22085
  if (!list?.subscribe) return void 0;
20660
22086
  const read = () => {
20661
- const snapshot = list.get?.() ?? {};
22087
+ const snapshot = readNativeSessionList(list) ?? {};
20662
22088
  const byId = snapshot.byId ?? {};
20663
22089
  const titles = {};
20664
22090
  for (const [id, row] of Object.entries(byId)) titles[id] = row?.displayTitle ?? null;
@@ -20667,19 +22093,19 @@ function App(props) {
20667
22093
  read();
20668
22094
  return list.subscribe(read);
20669
22095
  }, []);
20670
- React27.useEffect(() => {
22096
+ React32.useEffect(() => {
20671
22097
  const list = getContext()?.get("workspaces")?.list;
20672
22098
  if (!list?.subscribe) return void 0;
20673
22099
  const read = () => store.patch({ archivedSessionIds: [...list.getSnapshot()?.archivedSessionIds ?? []] });
20674
22100
  read();
20675
22101
  return list.subscribe(read);
20676
22102
  }, []);
20677
- React27.useEffect(() => {
22103
+ React32.useEffect(() => {
20678
22104
  const locale = getContext()?.get("locale");
20679
22105
  if (!locale?.subscribe) return void 0;
20680
22106
  return locale.subscribe(() => store.patch((prev) => ({ localeRevision: prev.localeRevision + 1 })));
20681
22107
  }, []);
20682
- React27.useEffect(() => {
22108
+ React32.useEffect(() => {
20683
22109
  if (!s.sessionsLoaded) return;
20684
22110
  const visible = s.sessions.filter((row) => !s.archivedSessionIds.includes(row.sessionId) && (!s.nativeSessionsReady || s.nativeSessionIds.includes(row.sessionId)));
20685
22111
  if (visible.some((row) => row.sessionId === s.activeSessionId)) return;
@@ -20689,53 +22115,53 @@ function App(props) {
20689
22115
  }, [s.sessions, s.sessionsLoaded, s.archivedSessionIds, s.nativeSessionIds, s.nativeSessionsReady, s.activeSessionId]);
20690
22116
  const wsState = s.snapshot?.ws?.state ?? st?.ws?.state;
20691
22117
  const resyncing = marketStale && !s.statusError && Date.now() - s.resumedAt < 1e4;
20692
- const wsBadge = wsState === "open" && !marketStale ? /* @__PURE__ */ React27.createElement("span", { className: "dhl-badge ok", title: `WebSocket: ${wsState}` }, t("status.online", "\u5728\u7EBF")) : resyncing ? /* @__PURE__ */ React27.createElement("span", { className: "dhl-badge", title: `WebSocket: ${wsState ?? "off"}` }, tr("\u540C\u6B65\u4E2D", "Syncing")) : /* @__PURE__ */ React27.createElement("span", { className: "dhl-badge err", title: `WebSocket: ${wsState ?? "off"}` }, t("status.offline", "\u79BB\u7EBF"));
22118
+ const wsBadge = wsState === "open" && !marketStale ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge ok", title: `WebSocket: ${wsState}` }, t("status.online", "\u5728\u7EBF")) : resyncing ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge", title: `WebSocket: ${wsState ?? "off"}` }, tr("\u540C\u6B65\u4E2D", "Syncing")) : /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge err", title: `WebSocket: ${wsState ?? "off"}` }, t("status.offline", "\u79BB\u7EBF"));
20693
22119
  const [layout, setLayout, commitLayout] = useLayout(
20694
22120
  s.activeSessionId,
20695
22121
  s.activeSessionId && s.workspaceSessionId === s.activeSessionId ? s.workspace?.layout : null,
20696
22122
  (next) => saveSessionWorkspace({ layout: next })
20697
22123
  );
20698
- const resizeSessions = React27.useCallback((delta) => {
22124
+ const resizeSessions = React32.useCallback((delta) => {
20699
22125
  setLayout((prev) => ({ sessionsWidth: clampWidth(prev.sessionsWidth + delta, LIMITS.sessions) }));
20700
22126
  }, [setLayout]);
20701
- const resizeChat = React27.useCallback((delta) => {
22127
+ const resizeChat = React32.useCallback((delta) => {
20702
22128
  setLayout((prev) => ({ chatWidth: clampWidth(prev.chatWidth + delta, LIMITS.chat) }));
20703
22129
  }, [setLayout]);
20704
- const resetSessions = React27.useCallback(() => {
22130
+ const resetSessions = React32.useCallback(() => {
20705
22131
  setLayout({ sessionsWidth: DEFAULTS.sessionsWidth });
20706
22132
  commitLayout();
20707
22133
  }, [setLayout, commitLayout]);
20708
- const resetChat = React27.useCallback(() => {
22134
+ const resetChat = React32.useCallback(() => {
20709
22135
  setLayout({ chatWidth: DEFAULTS.chatWidth });
20710
22136
  commitLayout();
20711
22137
  }, [setLayout, commitLayout]);
20712
- const toggleSessions = React27.useCallback(() => {
22138
+ const toggleSessions = React32.useCallback(() => {
20713
22139
  setLayout((prev) => ({ sessionsCollapsed: !prev.sessionsCollapsed }));
20714
22140
  commitLayout();
20715
22141
  }, [setLayout, commitLayout]);
20716
- const toggleDesk = React27.useCallback(() => {
22142
+ const toggleDesk = React32.useCallback(() => {
20717
22143
  setLayout((prev) => ({ deskCollapsed: !prev.deskCollapsed }));
20718
22144
  commitLayout();
20719
22145
  }, [setLayout, commitLayout]);
20720
- const toggleChat = React27.useCallback(() => {
22146
+ const toggleChat = React32.useCallback(() => {
20721
22147
  setLayout((prev) => ({ chatCollapsed: !prev.chatCollapsed }));
20722
22148
  commitLayout();
20723
22149
  }, [setLayout, commitLayout]);
20724
22150
  const showSessions = !layout.sessionsCollapsed;
20725
22151
  const showDesk = !layout.deskCollapsed;
20726
22152
  const showChat = !layout.chatCollapsed;
20727
- return /* @__PURE__ */ React27.createElement("div", { className: "dhl-root" }, /* @__PURE__ */ React27.createElement("div", { className: "dhl-topbar" }, /* @__PURE__ */ React27.createElement("span", { className: "dhl-brand" }, /* @__PURE__ */ React27.createElement(BrandMark, null), "Hyperliquid ", /* @__PURE__ */ React27.createElement("span", { style: { fontWeight: 400, color: "var(--dsw-alias-label-secondary)" } }, t("app.suffix", "\u4EA4\u6613\u53F0"))), /* @__PURE__ */ React27.createElement("div", { className: "dhl-spacer" }), s.statusError ? /* @__PURE__ */ React27.createElement("span", { className: "dhl-badge err" }, t("status.unavailable", "\u8FDC\u7AEF\u4E0D\u53EF\u7528")) : null, st && network === "testnet" ? /* @__PURE__ */ React27.createElement("span", { className: "dhl-badge paper" }, t("network.testnet", "\u6D4B\u8BD5\u7F51")) : null, st && mode === "paper" ? /* @__PURE__ */ React27.createElement("span", { className: "dhl-badge paper" }, t("mode.paper", "\u6A21\u62DF\u76D8")) : null, wsBadge, /* @__PURE__ */ React27.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__ */ React27.createElement(UiIcon, { name: "chevronDown", size: 14 })), /* @__PURE__ */ React27.createElement("div", { className: "dhl-more", ref: moreRef }, /* @__PURE__ */ React27.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__ */ React27.createElement(UiIcon, { name: "settings", size: 17 })), moreOpen ? /* @__PURE__ */ React27.createElement("div", { className: "dhl-more-popover", role: "menu" }, /* @__PURE__ */ React27.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React27.createElement("span", null, t("network.label", "\u7F51\u7EDC")), /* @__PURE__ */ React27.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React27.createElement("button", { type: "button", className: network === "mainnet" ? "on" : "", disabled: networkBusy || !st, onClick: () => network !== "mainnet" && requestEnv({ network: "mainnet", mode }) }, t("network.mainnet", "\u4E3B\u7F51")), /* @__PURE__ */ React27.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__ */ React27.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React27.createElement("span", null, tr("\u8D26\u6237", "Account")), /* @__PURE__ */ React27.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React27.createElement("button", { type: "button", className: mode === "live" ? "on" : "", disabled: networkBusy || !st, onClick: () => mode !== "live" && requestEnv({ network, mode: "live" }) }, t("mode.live", "\u5B9E\u76D8")), /* @__PURE__ */ React27.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__ */ React27.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React27.createElement("span", null, tr("\u76D8\u53E3 / \u6210\u4EA4\u4F4D\u7F6E", "Book / trades position")), /* @__PURE__ */ React27.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React27.createElement("button", { type: "button", className: s.tapePlacement === "auto" ? "on" : "", onClick: () => setTapePlacement("auto") }, tr("\u81EA\u9002\u5E94", "Auto")), /* @__PURE__ */ React27.createElement("button", { type: "button", className: s.tapePlacement === "below" ? "on" : "", onClick: () => setTapePlacement("below") }, tr("\u56FE\u8868\u4E0B\u65B9", "Below chart")))), /* @__PURE__ */ React27.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React27.createElement("span", null, tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings")), /* @__PURE__ */ React27.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React27.createElement("button", { type: "button", onClick: () => {
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: () => {
20728
22154
  setMoreOpen(false);
20729
22155
  setLimitsOpen(true);
20730
- } }, tr("\u4EA4\u6613\u9650\u989D\u4E0E\u7B7E\u540D\u89C4\u5219", "Limits & signing rules"))))) : null)), /* @__PURE__ */ React27.createElement(import_dsh_client_ui_primitives7.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__ */ React27.createElement(React27.Fragment, null, /* @__PURE__ */ React27.createElement(import_dsh_client_ui_primitives7.Button, { variant: "outline", onClick: () => setConfirmLive(null) }, t("modal.cancel", "\u53D6\u6D88")), /* @__PURE__ */ React27.createElement(import_dsh_client_ui_primitives7.Button, { variant: "primary", disabled: networkBusy, onClick: () => {
22156
+ } }, 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: () => {
20731
22157
  if (confirmLive) void applyEnv(confirmLive);
20732
- } }, tr("\u786E\u8BA4\u5207\u6362", "Switch"))) }, /* @__PURE__ */ React27.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__ */ React27.createElement(WalletDetails, { open: walletOpen, onClose: () => setWalletOpen(false) }), /* @__PURE__ */ React27.createElement(TradingLimits, { open: limitsOpen, onClose: () => setLimitsOpen(false) }), /* @__PURE__ */ React27.createElement("div", { className: "dhl-toasts" }, marketStale && !resyncing ? /* @__PURE__ */ React27.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__ */ React27.createElement("div", { className: "dhl-notice", onClick: () => notify(null) }, /* @__PURE__ */ React27.createElement(UiIcon, { name: "info", size: 16 }), /* @__PURE__ */ React27.createElement("span", { style: { flex: 1 } }, s.notice.text), /* @__PURE__ */ React27.createElement(UiIcon, { name: "close", size: 14 })) : null), /* @__PURE__ */ React27.createElement("div", { className: "dhl-body" }, showSessions ? /* @__PURE__ */ React27.createElement(SessionsColumn, { width: layout.sessionsWidth, fill: !showDesk && !showChat, onCollapse: toggleSessions }) : /* @__PURE__ */ React27.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__ */ React27.createElement(PanelIcon, { kind: "sessions" })), showSessions && (showDesk || showChat) ? /* @__PURE__ */ React27.createElement(Divider, { side: "left", onResize: resizeSessions, onCommit: commitLayout, onReset: resetSessions }) : null, showDesk ? /* @__PURE__ */ React27.createElement(DeskColumn, { onCollapse: toggleDesk }) : /* @__PURE__ */ React27.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__ */ React27.createElement(PanelIcon, { kind: "desk" })), showDesk && showChat ? /* @__PURE__ */ React27.createElement(Divider, { side: "right", onResize: resizeChat, onCommit: commitLayout, onReset: resetChat }) : null, showChat ? /* @__PURE__ */ React27.createElement(ChatColumn, { width: layout.chatWidth, fill: !showDesk, onCollapse: toggleChat, SessionProvider: props.SessionProvider ?? props.sessionProvider, renderFactorySlot: props.renderFactorySlot }) : /* @__PURE__ */ React27.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__ */ React27.createElement(PanelIcon, { kind: "chat" }))));
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" }))));
20733
22159
  }
20734
22160
 
20735
22161
  // src/client/components/panel-entry.tsx
20736
- var React28 = __toESM(require("react"), 1);
22162
+ var React33 = __toESM(require("react"), 1);
20737
22163
  function PanelEntry(props) {
20738
- return /* @__PURE__ */ React28.createElement("span", { className: "dhl-entrymark" }, /* @__PURE__ */ React28.createElement(BrandMark, { size: props?.size ?? 18 }));
22164
+ return /* @__PURE__ */ React33.createElement("span", { className: "dhl-entrymark" }, /* @__PURE__ */ React33.createElement(BrandMark, { size: props?.size ?? 18 }));
20739
22165
  }
20740
22166
 
20741
22167
  // src/client/index.tsx
@@ -20767,30 +22193,30 @@ function apply(ctx) {
20767
22193
  locale: NS,
20768
22194
  children: { "hyper.panel.seat": { kind: "single", scope: "session" } }
20769
22195
  },
20770
- (props) => React29.createElement(App, {
22196
+ (props) => React34.createElement(App, {
20771
22197
  SessionProvider: props?.SessionProvider,
20772
22198
  renderFactorySlot: props?.renderFactorySlot
20773
22199
  })
20774
22200
  )), NS + ": main panel");
20775
22201
  ctx.effect(() => slots.inject("sidebar.panellist", () => slots.register(
20776
22202
  { name: "sidebar.panellist", id: NS, order: 20, label: () => t("app.title", "Hyperliquid \u4EA4\u6613\u53F0"), registrant: NS, locale: NS },
20777
- (props) => React29.createElement(PanelEntry, props)
22203
+ (props) => React34.createElement(PanelEntry, props)
20778
22204
  )), NS + ": panel entry");
20779
22205
  }
20780
22206
  registerDshExtensions(ctx, slots);
20781
22207
  const layout = ctx.get("layout");
20782
22208
  if (layout?.panelInfo?.subscribe && slots) ctx.effect(() => {
20783
- const key2 = `${NS}.panel-selected.v1`;
22209
+ const key3 = `${NS}.panel-selected.v1`;
20784
22210
  let restore = false;
20785
22211
  try {
20786
- restore = sessionStorage.getItem(key2) === "1";
22212
+ restore = sessionStorage.getItem(key3) === "1";
20787
22213
  } catch {
20788
22214
  }
20789
22215
  let ready = false;
20790
22216
  const remember = () => {
20791
22217
  if (!ready) return;
20792
22218
  try {
20793
- sessionStorage.setItem(key2, layout.panelInfo.getSnapshot()?.activePanelId === NS ? "1" : "0");
22219
+ sessionStorage.setItem(key3, layout.panelInfo.getSnapshot()?.activePanelId === NS ? "1" : "0");
20794
22220
  } catch {
20795
22221
  }
20796
22222
  };