diff --git a/AGENTS.md b/AGENTS.md
index 5228e96..a4bc5dd 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -43,6 +43,7 @@
| Trading-data, CoinMarketCap-data, auto-refresh, or cross-script validation | docs/userscript-validation.md |
| Behavioral tests, test lint, affected selection, or source coverage | docs/test-policy.md |
| Read-only review | skills/userscript-review/SKILL.md |
+| Chart mirror storage and IndexedDB | docs/binance-chart-storage-development.md |
| Release or publish | skills/userscript-release/SKILL.md |
| Codex/browser/proxy/helper/connection timeout | global timeout rule; if available, ~/.dotfiles/knowledge/shared/CODEX_TOOL_TIMEOUT_TRIAGE.md |
diff --git a/README.md b/README.md
index 26f1b7d..6e0ad3c 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,7 @@ This repository is the source of truth and distribution point for the scripts. G
- Infers direction from the current open/close tab.
- Provides a quantity multiplier panel.
+- Includes chart mirror storage optimization without a separate installation.
- Handles orderbook display precision, available quantity, current orders, and ladder order boundaries.
- Designed for manual traders who frequently inspect the orderbook and submit limit orders.
@@ -108,6 +109,7 @@ npm run build:binance-userscripts
## Documentation
+- [Binance chart storage optimizer](docs/binance-chart-storage-development.md)
- [Signal gateway architecture and usage (Chinese)](docs/signal-gateway-architecture.zh-CN.md)
- [Binance orderbook trade development](docs/binance-orderbook-trade-development.md)
- [Binance Strategy 29 Bollinger signals and migration](docs/binance-strategy29-bollinger-development.md)
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 1a207fe..a9dd74f 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -51,6 +51,7 @@
- 根据当前开仓/平仓 tab 自动识别操作方向。
- 支持数量倍率面板,减少重复输入。
+- 内置图表镜像存储优化,无需额外安装脚本。
- 处理订单簿价格精度、可用数量、当前委托和阶梯挂单相关边界。
- 适合高频查看盘口、需要快速提交限价单的手动交易场景。
diff --git a/docs/binance-chart-storage-development.md b/docs/binance-chart-storage-development.md
new file mode 100644
index 0000000..754dcab
--- /dev/null
+++ b/docs/binance-chart-storage-development.md
@@ -0,0 +1,99 @@
+# Orderbook Chart Storage Optimization
+
+`src/binance-orderbook-trade/chart-storage/` owns the production implementation. Build
+the existing `scripts/binance-orderbook-trade.user.js` with
+`npm run build:binance-orderbook-trade`. There is no separate install entry.
+The earlier `experiments/binance-chart-storage/` files are historical research
+and independent baselines; production imports none of them.
+
+## Behavior and scope
+
+The module installs before the orderbook business initializer at document-start
+in the page's MAIN_WORLD. It only admits
+top-level Binance perpetual trading routes accepted by the shared route parser,
+including USDT and USDC symbols. Futures home, landing and other subpages receive
+no storage hook or UI. The orderbook retains its own existing frame and SPA
+behavior. Landing-to-trading SPA navigation does not re-run the storage installer;
+only a fresh matching page starts early interception. It never places orders, changes leverage, reads credentials, changes
+the proxy, clears databases or replaces global storage methods.
+
+Only the pinned TradingView module `70940` mirror destination expression changes.
+The complete original factory must match before first execution. Late injection,
+source drift and a missing capture after a 30-second timer retire interception
+and leave native registration intact. Metadata does not guarantee early injection
+on every extension configuration; rejection remains a supported outcome.
+
+Each accepted mirror uses one IndexedDB read/write transaction and compares the
+destination values before writing. Equal JSON trees must retain signed zero,
+property order and dense array structure. Missing keys remain distinct from stored
+null. Ordinary saves, source reads, Basic chart settings and `clear()` retain
+their native implementations. No record cache persists between batches.
+
+Input admission defaults to 512 entries, 4 MiB per batch and 16 pending batches
+with 64 MiB of copied input. Unsupported values, capacity limits, initialization
+or driver/configuration rejection and synchronous transaction-creation failure
+execute the original native expression once, before any optimized transaction
+exists. Native handling may exceed the optimizer's limits; these limits bound
+optimization, not Binance's original work. Shared references, cycles and non-JSON
+input use that native path instead of silently changing their stored meaning.
+
+Within a transaction, unsupported old values and exhausted comparison budgets
+cause direct writes. They do not reject otherwise valid replacements. A genuine
+transaction failure aborts the entire batch and stops future optimization; it
+never replays the failed batch. The original caller still receives the failure
+through its Promise boundary. Subsequent calls resume native handling.
+
+## Lifecycle and diagnostics
+
+`self.__BINANCE_CHART_STORAGE__.snapshot()` returns fixed aggregate counters and
+startup status. `stop()` drains the current instance's accepted work, including
+every member of an early-rejecting native Promise array. Calls arriving during
+drain wait for that finite fence, then run their native expression. After stop,
+native arrays and errors pass through synchronously and statistics freeze.
+The public interface exposes no stored keys, values or storage methods.
+
+`acceptedBatches` counts created optimized transactions. `committedTransactions`
+counts real completion events, while `committedWrites` and `skippedWrites` count
+only committed work. `rejectedBatches` and `nativePassthroughBatches` describe
+native routing before transaction creation, not failed saves. `failedBatches`
+counts failed optimized transactions. The separate native-only experiment's
+`completed` and `dispatches` fields must not be interpreted as commit counters.
+
+Stopping does not close shared connections, undo committed data, drain other tabs
+or guarantee persistence before page destruction. Leaving a supported route
+retires optimization when a retained callback runs; pagehide also requests stop.
+
+## Compatibility limits
+
+The native per-key mirror can partially commit; an optimized destination batch
+commits or aborts as a whole. Its interleaving with native clear and other tabs
+therefore differs. Source reads remain non-atomic, and two databases cannot share
+this transaction. No newest-revision ordering, cross-tab coordination, repair of
+already queued work or universal blank-page recovery is promised. The change
+addresses repeated destination transactions and unchanged writes specifically.
+
+The retained upstream factory is generated from the exact public response and
+verified with its manifest and inverse replacement. To update that source pin,
+first inspect and regenerate the fixture provenance; never broaden the matcher
+to accept an unknown module. The generation command accepts an explicit output:
+
+```sh
+node experiments/binance-chart-storage/generate-mirror-module.js /path/to/verified/TradingView.99bc5074.js src/binance-orderbook-trade/chart-storage/mirror-module.js
+```
+
+## Validation and release
+
+- Runtime unit scenarios execute the real bootstrap and exact native factory.
+- Production writer scenarios use real IndexedDB and the captured localForage,
+ covering original-value semantics, admission, reconnect, abort and drain.
+- Multi-tab scenarios use competing different revisions, native clear, reload
+ and actual transaction events; no fixed sleep determines business completion.
+- The anonymous smoke runner executes the generated install artifact in a fresh
+ Chrome context. Tampermonkey installation requires separate exact installed
+ and loaded-source checks in the user's authorized test tab.
+
+Run the affected Node and browser tests, `npm run lint:tests`, the build/check,
+one passing full `npm test`, and `git diff --check`. Publish through a reviewed
+GitHub PR; verify the main raw artifact before synchronizing Tampermonkey. A
+successful chart and committed test batch do not establish long-running stability
+or prove that every historic white-screen incident had the same cause.
diff --git a/docs/binance-futures-blank-page-diagnosis.md b/docs/binance-futures-blank-page-diagnosis.md
index cbca2a4..e7899fc 100644
--- a/docs/binance-futures-blank-page-diagnosis.md
+++ b/docs/binance-futures-blank-page-diagnosis.md
@@ -109,3 +109,824 @@ Do not patch Binance's router or call its continuation from a userscript.
The separate userscript bug was broad route recognition (`home` became `HOME`)
and Strategy31's non-trading status lifecycle. Fixing those defects does not
establish that the host startup stall is fixed.
+
+## 2026-10-06: native lock-queue hotspot and verified session recovery
+
+This follow-up identified a native browser bottleneck, beyond the earlier
+application-level cache gate. It did not identify the initial transaction
+producer or establish a permanent fix.
+
+In the normal Chrome profile, USUSDT rendered while 1000LUNCUSDT, PHAUSDT and
+ALICEUSDT retained the Futures skeleton. Their theme-start marks had no matching
+end. Existing ALICE requests included pending native IndexedDB opens for
+`theme-config-futures-trade-ui` and `i18n-data-zh-CN`. The user reported that
+unsigned-in DIAUSDT rendered both the orderbook and TradingView in incognito.
+This comparison alone did not isolate extensions, authentication and storage.
+
+The user also supplied a screenshot of `chrome://indexeddb-internals/` showing
+only its heading and filter input, without database rows or a Force close button.
+The exact-version [frontend source](https://github.com/chromium/chromium/blob/154.0.8037.98/content/browser/resources/indexed_db/indexeddb_internals.ts#L188)
+registers the input listener only after metadata arrives. The
+[backend](https://github.com/chromium/chromium/blob/154.0.8037.98/content/browser/indexed_db/indexed_db_context_impl.cc#L596)
+waits for bucket metadata callbacks, including work posted to bucket contexts;
+the UI also waits for all loaded storage partitions. One stalled branch can
+therefore prevent the entire list from appearing.
+
+A three-second native sample of Chrome 154.0.8037.98 ARM64 captured all 233
+samples of one busy worker inside `Connection::RemoveTransaction`; 232 of them
+continued through the blocking-check path below:
+
+```text
+BucketContext::RunTasks
+Database::RunTasks
+Connection::RemoveTransaction
+Transaction::IsTransactionBlockingOtherClients
+PartitionedLockManager::IsBlockingAnyRequest
+```
+
+The deepest frames repeatedly included `SupportsUserData::GetUserData` through
+the lock-request filter. Offline symbolication used the
+[official dSYM](https://dl.google.com/chrome/mac/stable/dsym/googlechrome-154.0.8037.98-arm64-dsym.tar.bz2),
+whose UUID exactly matched the running Framework:
+`4C4C4433-5555-3144-A154-EC6770D35233`. `atos` reported an unsupported DWARF
+attribute but returned function names; no source-line resolution is claimed.
+
+The matching source shows that
+[`RemoveTransaction`](https://github.com/chromium/chromium/blob/154.0.8037.98/content/browser/indexed_db/instance/connection.cc#L238)
+checks remaining started transactions, while
+[`IsBlockingAnyRequest`](https://github.com/chromium/chromium/blob/154.0.8037.98/components/services/storage/indexed_db/locks/partitioned_lock_manager.cc#L151)
+walks held locks and their waiting queues. These loops explain the sampled work;
+the sample does not reveal queue lengths, database names or the originating
+JavaScript calls. Do not label it a proven deadlock, corruption, or userscript
+defect.
+
+Recovery observations, with explicit permission to close the four named tabs:
+
+- Gracefully closing only USUSDT's existing theme-cache connection did not
+ restore the failed pages. A successful `close()` call did not prove all work
+ on that connection had finished.
+- Closing all four normal-profile Binance tabs reduced the sampled Chrome
+ process CPU from about 104.5% to 4.6%. The same worker waited in 254 of 255
+ subsequent samples.
+- Without restarting Chrome or clearing site data, the user observed that the
+ existing IndexedDB internal page automatically displayed its list.
+- A newly opened, sole DIAUSDT tab rendered the orderbook and TradingView,
+ verified in a screenshot. Its initial theme-cache gate took about 287 ms.
+- One ordinary reload also rendered both components; the initial theme gate
+ took about 103 ms. The page remained mounted at a later four-minute check.
+
+The initial culprit tab, transaction producer, and the contribution of native
+Binance code versus extensions remain unknown. Incognito success and this
+recovery do not prove either side innocent. Keeping one trading tab is the
+verified session workaround; its long-term stability and reopening multiple
+trading tabs were not validated. No proxy, extension, or persistent-cache change
+was made in this follow-up, and Force close was not performed.
+
+Upstream [CL 6133080](https://chromium-review.googlesource.com/c/chromium/src/+/6133080)
+optimized this blocking check, including a single-client shortcut; its core
+behavior is already present in this Chrome version.
+[CL 7252092](https://chromium-review.googlesource.com/c/chromium/src/+/7252092)
+adds diagnostic connection counts rather than a performance fix. Both public
+CL message/comment threads and final diffs were inspected. The linked
+[issue 384476946](https://issues.chromium.org/issues/384476946) was not readable
+anonymously, so its full discussion, status and applicability remain unverified.
+No confirmed new repair version or applicable official switch was found.
+
+Local incident artifacts are under `/tmp/binance-multi-startup-o2R1Oy/`, including
+`symbolicated-idb-worker.json`, `recovery-result.json`, and the before/after
+screenshots. The large downloaded symbol files are disposable and are not
+required to read those retained results.
+
+## 2026-10-06: recurrence and chart-save transaction amplification
+
+After additional trading tabs were opened, the user supplied two IndexedDB
+internal-page screenshots. One explicitly showed `chart_delivery` /
+`keyvaluepairs`, with a running readwrite transaction followed by many blocked
+readwrite transactions, aged about 127 seconds. The other showed blocked writes
+to a `usage` store, but omitted its database heading. Transaction identifiers
+are not queue lengths. These screenshots are snapshots, not continuous traces.
+
+A bounded transaction observer on USUSDT subsequently counted 13,050 newly
+created `chart_delivery` readwrite transactions, 1,460 `chart_futures` readonly
+transactions and 591 `chart_futures` readwrite transactions. The observation
+was shorter than 105 seconds; its precise stop timestamp was not retained, so
+it does not establish a 60-second rate. The first through 7,000th sampled
+delivery writes spanned about 510 ms. A 2,000-listener limit meant most delivery
+transactions were untracked after creation; zero observed chart completions
+does not prove that all 13,050 transactions remained blocked. The observer did
+not read records or initiate transactions, and the native method was restored.
+
+The loaded [TradingView chunk](https://bin.bnbstatic.com/static/chunks/TradingView.99bc5074.js)
+was byte-for-byte identical to the inspected public source. Module `70940`:
+
+1. Reads all current-namespace keys and selects `#TV_SYMBOL-*` and
+ `myTradingView*` keys.
+2. Reads every selected value concurrently.
+3. Unconditionally writes every selected key to the other chart namespace,
+ again concurrently. The namespaces include `chart_futures` and
+ `chart_delivery`; a USDT page therefore writes the delivery database without
+ a COIN-M page being open.
+
+This synchronization runs after native saves and directly on active-chart
+changes. Its entry count must not be labeled a save count. Native save callbacks
+also write the current layout and symbol data before invoking synchronization.
+
+A second observation combined transaction counts, drawing-event subscriptions
+and conditional breakpoints that always returned false. Breakpoints were bound
+to verified executable locations in the loaded source; they did not replace
+save callbacks. During 45.115 seconds it observed:
+
+| Observation | Count |
+| --- | ---: |
+| Native scheduled-save executions | 21 |
+| Drawing `properties_changed` / `remove` events | 15 / 5 |
+| `onAutoSaveNeeded` events | 1 |
+| New synchronization starts | 3 |
+| Key-read batches / total selected keys | 27 / 7,830 |
+| Synchronization batches reaching destination writes | 0 |
+| New current-chart readonly / readwrite transactions | 7,836 / 24 |
+
+The read batches averaged 290 keys. There were more resumed read batches than
+new synchronization starts, demonstrating that this window included earlier
+asynchronous work. The prior 13,050 writes equal 45 times 290, but that arithmetic
+alone does not prove the prior batch count or a constant historical key count.
+Debugger instrumentation can affect scheduling and performance; these counts
+are not an uninstrumented performance benchmark.
+
+A separate 40.393-second drawing observer identified all 11 property-change
+events as `LineToolOrder`; six removals originated in the native shape update
+loop in [chunk 66202](https://bin.bnbstatic.com/static/chunks/66202.233a75ac.js).
+The user confirmed an automatic task was running during this observation.
+Native `Gr` schedules a complete save after every drawing event except `click`
+and `move`, using independent 100-ms timers. The precise automatic task was not
+inspected or stopped.
+
+At the same time, a screenshot confirmed MUBARAKUSDT had a populated orderbook
+but an empty TradingView region, with no chart iframe. This recurrence was not
+fixed by the earlier session recovery. The evidence identifies active native
+save amplification; it does not prove a corrupted database or exclude every
+other producer. The separate `pika-i18n-usage` producer was also observed.
+
+All conditional breakpoints and drawing observers were removed, and the native
+transaction method was verified restored. No records were read or deleted by
+these probes. Evidence files are in `/tmp/binance-idb-producer-nfcyS5/`, including
+`us-transaction-observation.json`, `combined-save-mirror-count.json` and
+`drawing-origin-count.json`.
+
+### Order-line visibility control
+
+The current [TradingView library](https://www.binance.com/static/chart/tradingview/en/trading-platform-30/bundles/library.50c4aba0e2bfdfef5b8b.js)
+defines `LineToolTrading.state()` as returning `null`.
+[LineToolOrder](https://www.binance.com/static/chart/tradingview/en/trading-platform-30/bundles/line-tool-order.bd1676028df5fdf272ee.js)
+inherits that implementation. The pane serializer excludes null source states:
+these order lines are not saved in the chart snapshot. Their property and remove
+events nevertheless reach Binance's drawing-save listener. This identifies
+unnecessary whole-chart persistence triggered by transient order graphics.
+
+With explicit permission, the native USUSDT chart menu's checked `当前委托`
+(`Open Orders`) option was temporarily unchecked, then restored. The automatic
+task was not stopped or controlled by the diagnostic. One initial toggle and
+one initial restore attempt encountered a closed tooltip and failed their
+pre-action assertions; neither attempt clicked anything. The actual checkbox
+transitions were separately verified as false and then true.
+
+- A 60.001-second observation beginning about 27 seconds after hiding the lines
+ recorded zero drawing events, zero scheduled native saves and zero chart
+ writes. Only ten current-chart readonly transactions were created.
+- The browser process still showed about 92.2% CPU, and MUBARAKUSDT still had no
+ TradingView iframe. Stopping this producer did not immediately drain or abort
+ previously queued work.
+- Restoring the original checked state produced thirteen property-change
+ events and thirteen native saves within the remaining approximately
+ eleven-second observation segment. The setting was verified checked again.
+
+This on/off/on comparison establishes that the native order-display path was
+an active save producer. It does not by itself prove every failed page was
+blocked exclusively by that producer. Hiding these graphics is a validated
+way to stop this source while leaving the order list available; permanent
+application and another all-tab recovery require separate authorization.
+
+Simply serializing callbacks is not a complete repair: active-chart changes
+bypass that entry point, some callbacks discard their completion Promise, and
+the native mirror catches failures before all remaining writes necessarily
+finish. It also retains the same total work and can accumulate a JS queue.
+A repair preserving order graphics would need to filter the native save
+listener specifically, including reliable identification of removed order
+graphics, while preserving manual drawings and other subscribers. Such a
+userscript implementation has not been validated or deployed.
+
+The original display state was restored, all diagnostic globals and breakpoints
+were removed, and `IDBDatabase.prototype.transaction` was verified native.
+Additional evidence: `orders-hidden-count.json`, `orders-restored-count.json`
+and `before-hide-save-count.json` in the same incident directory.
+
+### Contribution of the userscript save coalescer
+
+The user questioned whether the earlier batch-save optimization was involved.
+The USUSDT page's loaded orderbook source was version `2.7.217` and contained the
+complete current generated install artifact byte-for-byte. This establishes
+loaded-source identity for that tab, not just installed metadata.
+
+The October 5 change `a846922` replaced each burst/round's last `pendingSave`
+with an array of accepted callbacks. `deliverSaveCallbacks` serializes once,
+then synchronously invokes every callback with a separate copy of the final
+snapshot. It does not await callback Promises. For N native persistence
+callbacks, the old intercepted batch started one native persistence chain;
+the current batch starts N chains. Each successful chain can subsequently
+mirror K historical keys. Thus `fullSaveCount: 1` describes serialization,
+not one database save or one transaction. Current tests explicitly require one
+serialization and N callback deliveries; they do not establish fewer native
+database writes.
+
+A 45.009-second live observation directly counted one coalescer batch containing
+eight accepted callbacks and eight callback invocations. The same window
+counted eighteen native scheduled-save executions. These window counts need not
+represent identical sets of requests, but they prove the current coalescer was
+actively replaying multiple persistence callbacks in one batch. Evidence is in
+`coalescer-attribution.json`; the conditional breakpoints were removed afterward.
+
+This is a verified contribution and a performance-contract gap, not proof that
+the change caused every earlier blank page. Native saves outside the coalescer
+and active-chart synchronization also remain. Returning to unconditional
+last-callback dropping would lose other callers' completion behavior. A repair
+must distinguish redundant native persistence from callbacks that must be
+delivered, or remove the unnecessary native broker-drawing save trigger at a
+reliably identified boundary. No such durable repair is claimed here.
+
+### Historical global-facade prototype: first stage
+
+The following three stages preserve the original global-facade research. That
+design changes native mixed-operation ordering and is no longer the production
+candidate. The current module-scoped mirror experiment is described afterwards.
+
+With authorization for an isolated experiment, a prototype was added under
+[`experiments/binance-chart-storage/`](../experiments/binance-chart-storage/README.md).
+It uses an exact public Binance Rspack runtime and localForage module `43917`
+fixture, a fresh Playwright Chromium context, synthetic chart records and real
+IndexedDB. All external requests in these lab tests are blocked. The two initial
+entrypoints (`adapter.js` and `capture.js`) reject origins other than
+`https://chart-storage.test`; no adapter was installed in a
+real Binance document, and no production database or trading action was touched.
+
+The tested intervention is the library's actual `createInstance` boundary after
+its first factory execution. A synchronous capture hook runs before the host's
+`require` returns. A separate facade survives the library's asynchronous driver
+initialization, queues calls across instances in issue order and combines them
+into bounded native transactions. Each write compares against the stored value
+inside that transaction and skips only an unchanged existing record. Every
+accepted optimized-batch Promise and callback completes after commit or abort. It does
+not reuse one transaction behind multiple unmodified localForage handlers.
+
+For 290 prepopulated synthetic records and eight sequential unchanged-save
+rounds, native localForage produced 4,640 transactions and 2,320 puts. The same
+workflow through the prototype produced 16 transactions and zero puts. Eight
+simultaneous save rounds produced ten transactions and zero puts. Both prototype
+workloads delivered 4,640 distinct callbacks, returned matching results, retained
+matching persisted data, and ended with no pending requests. Comparison increased
+native gets from 2,320 to 4,640 in the sequential workload; this is evidence of
+reduced transaction/write amplification, not reduced total reads or a measured
+CPU/latency improvement.
+
+The browser tests also cover initialization, mixed-operation ordering,
+null-versus-missing keys, native barriers, capacity errors, abort rollback,
+callback exceptions, connection close/version change, stop/drain, persistence
+after reload and simultaneous two-page writes. A held native transaction ensures
+the two-page test has both writes queued before releasing the lock; only one
+physical put is needed when both request the same new value.
+
+All 31 Chromium scenarios and three Node origin-guard tests passed, including
+real quota exhaustion configured before the first database write. That quota
+case rejected every operation in the batch with `QuotaExceededError`, completed
+all callbacks and preserved prior values. Test-policy lint and experiment syntax
+checks passed. The rendered lab report was inspected. Results and the remaining
+production boundary are recorded in the experiment README.
+
+This remains a restricted prototype. The test browser is Chromium
+`151.0.7922.34`, not the affected Chrome `154.0.8037.98` profile. Module identity,
+early injection, real chart values and all native callers require validation
+before an actual page adapter can be designed. Batched rollback and read
+completion timing differ from native localForage, and native barriers retain
+their own one-time connection recovery. The prototype cannot make two databases
+atomic, repair separate application read/modify/write races or drain previously
+queued transactions. No permanent blank-page repair is claimed by these tests.
+
+### Historical storage integration follow-up: 2026-10-06
+
+Further source inspection and regression tests identified two integration
+obstacles before any production storage interception:
+
+1. Installing the original chunk `push` wrapper before the Rspack runtime
+ missed first execution. The runtime retains that wrapper as a parent append
+ callback, reached after registration and synchronous host execution. The
+ new `bootstrap.js` observes the runtime's first `push` assignment and captures
+ the export before the host uses it, for both module/runtime script orders.
+ Diagnostic failure rejects observation without poisoning the host's cached
+ module. Cleanup also preserves a newly created queue during an active append.
+2. TradingView's save callback returns JSON parsed inside its iframe. Comparing
+ its object prototype with the parent's `Object.prototype` rejected valid
+ snapshots. The adapter now recognizes native plain JSON objects across realms;
+ real iframe regressions verify nested data and own `__proto__` properties,
+ while Date, class and forged-prototype values remain rejected.
+
+The current user USUSDT page's module factory was read without requiring the
+module, reading records or refreshing the page. Its complete module-property
+SHA-256 was `f33d0f78c185ff592f1f0781e21baab63e7db937054866c37afdfda028e9e647`,
+matching the fixture. This verifies one current build, not a stable public API.
+
+The added passive `preflight.js` briefly observes the real export's
+`createInstance` and returns each original instance. It records fixed chart
+database/default-store metadata and creation times, with at most sixteen
+timestamp records. It does not wrap instance methods, initialize storage,
+record keys/values, or enable the adapter. Only this preflight and bootstrap
+additionally allow the exact top-level USUSDT incident path. The storage adapter
+and original capture remain restricted to the lab origin. Manual stop and a
+30-second deadline restore the native factory; dedicated tests prove retained
+original instances still work and passive observation adds no IndexedDB opens,
+transactions, gets or puts. Other cleanup branches have not each been separately
+tested.
+
+The expanded validation passed all 45 Chromium scenarios (31 prototype and
+14 integration) and eight Node origin/path/frame rejection cases. Test-policy
+lint and syntax checks also passed. These fixture-based scenarios use real
+IndexedDB; they do not exercise the user's installed extension or account.
+
+Anonymous Chrome `154.0.8037.98` observations then used separate temporary
+profiles, no extensions and no login, with synchronous `addInitScript`
+installation before navigation. The initial run established capture before the
+first chart instance. A later screenshot showed the default Basic chart and
+orderbook working, so waiting for a TradingView iframe in that mode was an
+incorrect test premise. One mode-selection attempt also timed out because the
+actual native label is `Trading View`, including the space.
+
+Using the verified label produced an iframe and seven canvases, but screenshot
+inspection still showed the loading animation. That run's raw `ui_ready` result
+is too weak to establish a rendered chart. The final observation therefore
+waited for native `activeChart().dataReady()` and subsequent paint frames, then
+inspected a screenshot showing USUSDT candles, moving averages, volume bars and
+the populated orderbook. The captured library's no-callback `dataReady()` checks
+for nonempty main-series data and does not initiate storage operations.
+
+In this final run the module was captured at 2,184.1 ms, before the first chart
+instance at 3,008.7 ms. At 7,429.2 ms there was one TradingView iframe, twelve
+canvases and `dataReady() === true`; no page errors were observed. Twenty futures
+instances and zero delivery instances were observed before stop. This establishes
+the capture ordering and one successfully rendered anonymous page. All temporary
+browsers were closed, and the final observer reported `active=false` with no
+failure. Results and the inspected screenshot are retained in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-storage-preflight-chart-ready-1nRAMB/`.
+
+At that stage, production blockers remained. The actual source creates and retains localForage
+instances during React rendering, and Basic charts share these databases with
+TradingView. The adapter's then-current stop drained accepted work but rejected later
+calls through retained facades, so restoring the factory alone cannot safely
+disable it in an already mounted page. Real changed snapshots, Basic settings
+and clear operations, capacity and connection behavior, native layout
+read/modify/write and cross-database mirroring need integration coverage. Batch
+rollback and read completion timing still differ from native behavior.
+
+The anonymous injection does not verify Tampermonkey document-start/page-world
+timing, an affected logged-in profile, sustained trading load or blank-page
+recovery. No production userscript source or install artifact changed; no adapter
+was installed in Binance and no existing trading tab was refreshed by this
+follow-up. The storage-layer experiment demonstrates reduced transaction and
+unchanged-write work, not a deployed repair.
+
+### Historical facade lifecycle and original host workflows: 2026-10-06
+
+The next implementation stage addressed the specific stop incompatibility above.
+Six failing browser regressions first reproduced rejected retained-facade calls,
+premature native-factory restoration and incomplete descriptor cleanup. The
+adapter now closes optimized admission, drains the finite accepted queue and
+only then restores the original factory descriptor. Calls through retained or
+newly created managed instances during drain wait for that boundary and resume
+through the original native methods afterwards. Full arguments, native receivers
+and method-specific callback signatures are preserved. Reinstallation on the
+same export is prohibited until a fresh page lifecycle, preventing old facades
+from bypassing a second installation's queue.
+
+The nine lifecycle scenarios include a real write lock, a version-one connection
+blocking the library's version-two database initialization, abort completion,
+callback reentry, native Blob and API use after stop, ownership changes and
+descriptor restoration. A review also identified value-inspection reentry:
+a transparent Proxy could call stop during JSON validation before admission.
+That failing regression is fixed by rechecking the active phase immediately
+before enqueue. An ownership change during drain rejects stop and preserves
+the external factory instead of overwriting it. No queued operation is retried.
+
+An independent native-library comparison established that simultaneous native
+`setItem` and `getItem` calls can read the previous value: the captured set method
+creates its transaction one Promise stage later. Post-stop tests now compare
+against that actual native baseline, while separately proving final persistence
+and the inability to overtake the accepted optimization queue. A stopped adapter
+does not impose its stronger issue-order semantics on later native calls.
+
+Compatibility tests also now execute the original host persistence functions,
+rather than only a synthetic get/set workflow. The
+[public host fixture](../test/fixtures/binance-chart-storage/host-persistence.md)
+contains 273 exact original source fragments and 203 original dependency factories
+from seven public bundles. An independent reviewer verified full source hashes,
+byte counts and every original UTF-16 range. The fixture retains the original
+TradingView save/load functions, save callback and cross-database mirror, together
+with Basic storage, annotation conversion and old-key migration. It supplies only
+the documented storage/widget/reference context and runs through the captured
+Rspack runtime and real localForage. Upstream Basic React subscriptions, debounce,
+JSON serialization and actual chart rendering remain outside these fixtures.
+
+Fourteen host-workflow scenarios run the same workflows against native storage
+and the adapter. They verify drawing removal without resurrection, layout
+retention, both databases' mirror contents, all six Basic storage categories,
+old-key migration and shared-store clear. Four saves with revisions `[1,1,2,2]`
+used 64 transactions and 36 puts natively, versus 28 transactions and 11 puts
+through the adapter. The expected data matched in both databases.
+
+A bounded load then seeded 290 identical historical records in each database
+and executed eight rounds of four concurrent original save/mirror chains.
+Each round changed the current snapshot and drawing. All 32 chains completed;
+both databases' 293 keys, all 580 historical values and final snapshot, layout,
+drawing and native load results matched expectations.
+The four chains within a round use the same revision, and rounds are awaited
+sequentially; this does not test competing different revisions.
+
+| Isolated 32-chain workload | Transactions | Puts | Gets |
+| --- | ---: | ---: | ---: |
+| Native | 18,912 | 9,472 | 9,408 |
+| Adapter | 112 | 32 | 18,880 |
+
+The adapter skipped 9,440 unchanged writes; its 112 transactions include 32 native
+barriers. Failure, rejection, abort and pending-operation counts were zero.
+Setup and final verification I/O are excluded. The measured installation-lifetime
+peak was 1,172 operations and 181,767 bytes; workload counters and lifetime peaks
+are labeled separately in the attachments. Individual lab elapsed-time samples
+are not evidence of sustained production latency or CPU improvement.
+The complete load report and extracted measurements are retained as
+`/tmp/chart-storage-load-retained-20261006.json` and
+`/tmp/chart-storage-load-measurements-20261006.json`.
+
+The tests deliberately retain a further compatibility difference. Concurrent
+native sets can create transactions after a later-invoked Basic clear, leaving
+those new records present; the adapter enforces issue order and removes them.
+The normal shared-clear path awaits completed saves first and matches in both
+implementations. Separate assertions expose the concurrent difference instead
+of hiding it behind an equivalence claim.
+
+All 68 Chromium scenarios (31 prototype, 14 startup integration, nine lifecycle
+and 14 original host workflows) and eight origin/path/frame Node cases passed.
+Full test-policy lint, syntax checks and independent code review passed. This
+stage corrected the experiment's stop behavior and broadened its compatibility
+evidence. It still does not establish Tampermonkey page-world timing, real saved
+data compatibility, long-running stability, mounted live UI behavior or recovery
+of the affected profile. No production userscript, installed script, user database
+or trading action was modified by the experiment.
+
+### Current module-scoped mirror candidate: 2026-10-06
+
+The global facade above is historical research, not the production candidate.
+Its issue-order queue changes the captured native driver's ordering between
+`setItem` and `getItem`, `removeItem` or `clear`. In particular, native set-value
+preparation can defer its transaction until after a later Basic clear; the facade
+orders that set before clear. Preserving each implementation's own expected
+result does not make them equivalent. Native storage methods must remain native.
+
+The new experiment replaces only the destination-write `Wr=fr.map(...)` RHS in
+public module `70940` of `TradingView.99bc5074.js`. Acorn identifies a unique
+expression at `[33783,34025)` within the complete method at `[6665,36819)`.
+The method's SHA-256 is
+`37d249d1ceec4a41b54a959c1ab2c4b52b3061e504cf8cd81a824962af8c6c88`.
+Generation pins the full public source and method, rejects a colliding service
+identifier and verifies that undoing the one insertion reproduces the whole
+original method byte for byte. It emits static JavaScript without runtime
+`eval` or `new Function`:
+
+```js
+Wr = __dispatchChartMirror(Sr, fr, () => ORIGINAL_FR_MAP_EXPRESSION)
+```
+
+Registration compares the exact full `Function.prototype.toString` result before
+replacing the factory. The bootstrap selects `70940` only with the explicit
+replacement callback; default `43917` observation is unchanged. Mismatch rejects
+observation while preserving native module execution and cache behavior. A
+review found that a replacement callback could synchronously stop its observer
+yet still return a candidate that was installed. Both runtime orders reproduced
+that failure before a post-callback active-state check fixed it. Stop now retains
+the original registration in that case; ordinary cleanup restores queue and
+factory slots while preserving already executed cached exports.
+
+Source `keys()` and every source `getItem()` remain unchanged, as do the original
+mirror guards, catch and target exclusion. The ordinary TradingView save, Basic
+settings and reset paths are outside the replacement. The dedicated writer never
+hooks `createInstance`, `setItem`, `getItem`, `removeItem` or `clear`. It uses one
+real destination transaction to read current values and write changed or missing
+records; null and missing are distinguished. The tests execute the original
+callback fragments and dependencies rather than rewriting the host save logic.
+See the [scoped fixture](../test/fixtures/binance-chart-storage/mirror-scoped.md)
+and [current experiment contract](../experiments/binance-chart-storage/README.md).
+
+The writer defaults to `scope: 'lab'`, restricted to `https://chart-storage.test`.
+An explicit `isolated-ususdt-probe` scope permits only the exact top-level
+`https://www.binance.com/zh-CN/futures/USUSDT` page. Origin/path/frame checks cannot
+prove anonymity; a dedicated harness must own a fresh Chrome process/profile
+without login or extensions. Default production-origin refusal is unchanged.
+Both scopes require the native IndexedDB `keyvaluepairs` stores in `chart_futures`
+and `chart_delivery`. The writer
+validates/copies JSON and bounds entries, input bytes, stored-read bytes and
+pending work. Empty input performs no initialization or transaction. A failed
+batch rolls back completely and is never replayed. Stop drains only the finite
+accepted set; later mirror calls wait for that fence and evaluate the retained
+original async per-key map, while ordinary native operations remain independent.
+
+This narrowing preserves ordinary API behavior but deliberately changes mirror
+atomicity and interleaving. Native per-key mirror writes can partly commit;
+the new destination batch commits or aborts as a whole and can interleave
+differently with native clear or another tab. Limits add explicit rejection
+conditions. The original outer catch still owns reporting. Source reads remain
+non-atomic, separate databases remain independent, and application read/modify/
+write races remain outside the fix.
+
+The new suites contain 19 scoped-integration scenarios and 18 writer scenarios.
+They cover exact source identity, native set/get/remove/clear results and callback
+order, method identity, original guards and save chains, null/empty data,
+registration/cache cleanup, reentrant stop, real abort/quota rollback, limits,
+blocked initialization, clear interleaving and another tab's changed data.
+Before the explicit page-probe scope, the combined experiment run passed 105 Chromium scenarios, eight Node
+origin/path/frame cases and the full test-policy lint. These include the retained
+historical suites; that count does not turn the facade into the current candidate.
+After the explicit scope was added, three new origin/scope regressions first
+failed, then the expanded Node suite passed all 18 cases. The 37 scoped/writer
+browser scenarios and full test-policy lint also passed. Scope permission and
+anonymous-profile isolation are distinct contracts.
+
+The current bounded workload again starts with 290 historical keys per database
+and completes eight rounds of four concurrent original save/mirror chains. Each
+round uses one changed revision, and later rounds await the preceding round.
+Every historical value, both databases' 293 keys and final saved drawing data
+match. Setup and final verification I/O are excluded.
+
+| Current 32-chain workload | Transactions | Puts | Gets |
+| --- | ---: | ---: | ---: |
+| Native | 18,912 | 9,472 | 9,408 |
+| Scoped mirror writer | 9,568 | 112 | 18,784 |
+
+The 112 puts comprise 96 ordinary source writes and 16 changed
+destination writes. All native source reads remain; comparing 293 destination
+keys in each of 32 mirrors adds 9,376 gets. Both workloads finish with zero
+pending or aborted transactions. Counts are verified in
+`/tmp/chart-storage-final-20261006.json` and the extracted
+`/tmp/chart-storage-mirror-measurements-20261006.json`. They demonstrate less
+transaction/write work, not fewer reads, sustained CPU/latency improvement or
+recovery of the affected profile.
+
+A separate anonymous Chrome `154.0.8037.98` run used a temporary profile without
+extensions or login and injected the full static `70940` replacement before
+registration. Its service would execute only the exact original native thunk;
+the lab writer was not installed. Strict pin validation attempted once, matched
+once, and the replaced full module executed once, with capture at 5,975.4 ms.
+At 10,015.6 ms the native chart reported `dataReady() === true`. The inspected
+screenshot showed USUSDT candles, moving averages, volume and a populated
+orderbook. There were no page errors; observation stopped and the browser closed.
+Results and the screenshot are retained in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-mirror-native-preflight-HeCAar/`.
+
+That run recorded **zero mirror dispatches**. It verifies complete module startup
+and rendered readiness with a native-only service, but neither invokes the
+replaced mirror branch nor proves real batched writes.
+
+A subsequent independent anonymous Chrome `154.0.8037.98` shape probe exercised
+the original native mirror. During the explicitly bounded native interval change
+from `60` to `15`, its completed count advanced from 6 to 7. The earlier mirrors
+are not all attributed to one click. Final totals were seven native completions,
+all targeting `chart_delivery` with IndexedDB, zero failures and zero JSON-shape
+failures. The observed maximum was four entries, 49,003 batch bytes and 24,504
+bytes for one record. This sample fits the current JSON and size contracts; it
+does not establish compatibility for every user's saved layouts or drawings.
+The final chart remained data-ready at resolution `15`; the inspected screenshot
+showed the chart and populated orderbook. Observation stopped and the temporary
+browser closed. Evidence is retained in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-mirror-shape-probe-yDfadK/`.
+This is real native mirror invocation and data-shape evidence, not real-page
+bulk-writer validation.
+
+### Anonymous bulk and native-only preflight: 2026-10-07 (Beijing)
+
+A later independent anonymous Chrome `154.0.8037.98` run enabled the actual batch
+writer through the explicit USUSDT probe scope. Its report begins at
+`2026-10-06T16:24:24.572Z`, October 7 in Beijing. The pinned factory matched and
+executed once. Five accepted batches committed five transactions, with eight
+committed record writes and eleven unchanged writes skipped. Failed, rejected, aborted
+and pending counts were zero; peaks were two pending batches and 98,006 bytes.
+The one-hour chart stage observed four completed mirrors; the bounded `60` to
+`15` transition then advanced completions from 4 to 5. The first four are not
+all attributed to one click.
+
+After `writer.stop()`, selecting the four-hour interval advanced completed
+mirrors from 5 to 6 and `nativeCalls` from zero to one. The complete writer
+statistics remained unchanged. Each of the three stages read back four target
+records and matched them to its captured dispatch snapshot. The runner required
+native chart data readiness; the inspected final screenshot showed four-hour
+USUSDT candles, moving averages, volume and the populated orderbook. There were
+no page errors, observation stopped, and the temporary browser closed. The report
+and screenshot are retained in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-mirror-bulk-probe-3YcdXL/`.
+This proves bounded real-page batch execution and native continuation through
+the mounted callback in an independent anonymous browser. It does not enable
+the writer in the user's logged-in profile or establish blank-page recovery.
+
+Three earlier attempts stopped at the anonymous-page UI check before matching
+the factory or dispatching a mirror. Registration text selected both a button
+and a link; selecting the unique accessible button corrected that check. Those
+attempts provide no evidence of a storage failure. The checked-in anonymous
+runner, entry and contract now make the workflow independently repeatable,
+with a fresh Chrome context, fixed-URL navigation/runtime guards, explicit failure
+accounting and browser cleanup. The standalone command is documented in the
+[experiment README](../experiments/binance-chart-storage/README.md).
+
+The origin/scope suite has 18 Node cases, the anonymous-probe contract adds four,
+and native-only preflight adds 18: all 40 passed. The 37 mirror browser scenarios
+also passed. These counts identify the later validation scope separately from
+the earlier 105-browser/eight-Node experiment run.
+
+Tampermonkey delivery needs its own native-only acceptance on an authorized test page.
+The official [Content Script API documentation](https://www.tampermonkey.net/documentation.php?q=content_script_api)
+states that default **Content Script** mode has no real `document-start` support;
+on Chrome, ordinary **UserScripts API** has the same restriction. **UserScripts
+API Dynamic** injects both wrapper and script immediately and supports it. The
+official [`@sandbox` documentation](https://www.tampermonkey.net/documentation.php?q=sandbox)
+describes `raw` as requesting `MAIN_WORLD`, but permits another enabled sandbox
+when injection there is unavailable, such as under CSP. Metadata alone therefore
+cannot prove either document-start timing or MAIN_WORLD access to the native
+queue. The extension must independently demonstrate both, strict pin before
+first factory execution, native mirror completion and clean stop before an
+optimized entrypoint can be considered. Missing evidence must reject replacement.
+
+The independently reviewed `native-preflight-core.js`, userscript entry and
+build now generate `native-preflight.user.js` as a self-contained review artifact.
+Its build asserts a four-source dependency graph with no writer or runtime loader.
+For 30 seconds the core records bounded in-memory diagnostics and synchronously
+returns the exact original native thunk result, retaining its array and error
+identities. It does not inspect persisted values or add storage calls. Manual
+stop, pagehide, scope changes and the deadline retire observation. The artifact
+was neither installed nor published at that stage; its metadata and offline
+tests did not establish Tampermonkey acceptance.
+
+The generated native-only artifact was then run through `addInitScript` in a
+fresh anonymous Chrome `154.0.8037.98` context, beginning at
+`2026-10-06T16:30:27.651Z` (October 7 Beijing time). It recorded one attempt,
+one exact match and one factory execution, with module capture completed, four
+native thunk dispatches and 15 counted entries. Failure was null and page errors
+were zero. Manual stop set `active=false`, and the temporary browser closed.
+The inspected screenshot showed the chart, moving averages, volume and orderbook.
+Evidence is retained in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-native-artifact-probe-JSBjWC/`.
+The preflight's `completed` field describes factory capture, and its dispatch
+count is not an asynchronous write-commit count. This is actual built-artifact
+execution through Chrome initialization-script injection, not Tampermonkey
+injection or installation. The artifact was still unpublished and uninstalled
+at the end of that anonymous run.
+
+Remaining production boundaries include verified Tampermonkey timing/MAIN_WORLD,
+the user's logged-in profile and broader saved-data shapes/limits, long-running
+concurrent revisions and native clear interleaving, and the blank-page root cause
+and affected-profile recovery. The anonymous mounted stop case is one bounded
+sample. Default scope still rejects production origins;
+the explicit exact-page probe scope relies on its harness for profile isolation.
+No production userscript or public install artifact imports the experiment.
+The anonymous runs did not change installed scripts or extension settings.
+
+### Tampermonkey native-only acceptance: 2026-10-07 (Beijing)
+
+With explicit installation and new-tab test authorization, the native-only
+`Binance Chart Mirror Native Preflight (Experiment)` version `0.0.1` was installed
+under UUID `d0601269-eb03-446c-8c9a-a9836e343cb2`. The Editors create operation
+returned HTTP 405 and a subsequent list showed no installation. The native
+installer path then produced the unique script in the authoritative list.
+Fresh MCP source readback, parsed with the repository transport parser, matched
+the complete 86,589-byte artifact with SHA-256
+`7aff1d564a8cf9f21bbdb4a039db034c7a593dd846c23616d50bf40d3d856e1e`.
+
+A new test tab navigated to the exact USUSDT URL at `2026-10-06T18:39:55.733Z`
+(02:39:55 Beijing time). Before navigation, a target-local network block was
+set for the orderbook script's `adjustLeverage` endpoint because its initial
+flat-account observation can otherwise change leverage. Existing user tabs were
+not refreshed, and no financial control or chart interval control was clicked.
+
+| Observation | Result |
+| --- | --- |
+| First sample, page age 1,234.6 ms | Main-world diagnostic present, active and waiting |
+| Page age 18,705.7 ms | One attempt, exact source match and factory execution; capture complete; chart data ready |
+| Page age 45,083.2 ms | Stopped by deadline; three native dispatches, 885 total entries; no diagnostic failure |
+| Page age 94,216.8 ms | Same stopped counters; chart data ready at resolution `15` |
+| Queue restoration | `push` is a data property, with no accessor getter |
+
+The actual loaded Tampermonkey wrapper contained exactly one byte-identical
+copy of the artifact. Its execution context was the top-level Binance default
+world (`isDefault: true`), establishing MAIN_WORLD execution for this navigation.
+The initial event buffer was truncated; a fresh Debugger enumeration on the
+same loaded page recovered the script source without truncation or navigation.
+The source proof therefore does not rely on a complete initial event timeline.
+No extension injection setting was inspected or changed, and this single
+successful early injection is not a guarantee for every future navigation.
+
+The inspected chart crop showed candles, indicators, annotations and volume.
+Manual stop returned the already retired state. Both test-owned tabs were
+closed, disposing the temporary endpoint block and CDP attachment. The separate
+statistics script remains installed, restricted to the exact USUSDT page; a
+fresh matching page can start another observation. Its 30-second timer is a
+target duration, not a hard wall-clock cutoff under browser scheduling delays.
+No batch writer was enabled in the user's profile and nothing was published.
+
+This passes the bounded installed-source, loaded-source, MAIN_WORLD, exact
+factory capture, native dispatch and diagnostic-retirement checks. `completed`
+still means module capture, while `dispatches` counts thunk entry, not resolved
+native writes or committed transactions. Asynchronous completion, long-running
+concurrent writes, affected-profile blank-page recovery and reliable injection
+across navigations remain unverified. The evidence is retained in
+`/tmp/binance-tampermonkey-native-preflight-hlIxL9/report.json`, alongside the
+loaded experimental source and the inspected `chart-candles.png` crop.
+
+### Production successor acceptance: 2026-10-07 (Beijing)
+
+The user then authorized continuing through publication. The initial production
+candidate used `src/binance-chart-storage/`, generated as
+`scripts/binance-chart-storage.user.js` version `0.1.0`. This separate installer
+was superseded by the orderbook integration below before publication.
+It imports no experiment implementation. Review identified and
+fixed signed-zero/key-order comparison, shared-reference cloning, unsupported
+old values and transaction-creation failure handling. Unsupported work returns
+to the original map only before transaction creation; genuine transaction failure
+never replays. See the [production contract](binance-chart-storage-development.md)
+for its atomicity and lifecycle limits.
+
+Validation passed all 2,145 Node/DOM tests, 126 chart-storage browser scenarios
+(including 18 production writer and three production concurrency scenarios),
+full test lint and affected build/syntax checks. The prior coverage inventory
+test failed because its explicit installer list lacked the new artifact; the
+list was extended and the entire Node/DOM suite then passed. Five production
+value/connection regressions first failed against the historical writer.
+
+The actual generated artifact was tested in a fresh anonymous Chrome profile:
+five optimized transactions committed six writes and skipped thirteen unchanged
+writes, with no failure, abort or remaining work. After stop, a native interval
+change produced four native destination commits while all optimizer counters
+remained frozen. The inspected screenshot showed the TradingView chart. The
+report is `/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-chart-storage-smoke-kwTGVT/result.json`.
+An earlier smoke stopped on an HTTP 202 assertion; the corrected runner records
+status and waits for semantic readiness. Its successful navigation sequence was
+202 then 200. HTTP status alone remains insufficient UI evidence.
+
+The local Tampermonkey statistics installation was replaced in place by the
+production artifact. Installed source matched all 99,037 bytes and SHA-256
+`8fc21dda5322c0465d4bf6dc5149bfd4b50ec02e885ca7134ad047d617cb0501`.
+A new USUSDT test tab started at `2026-10-07T01:08:16.254Z`, with a target-local
+leverage-endpoint block set before navigation. Its actual loaded wrapper
+contained one identical artifact in the top-level Binance MAIN_WORLD. At page
+age 198,760.1 ms, six optimized transactions had committed six writes and skipped
+1,764 unchanged writes. Failed, rejected, aborted and pending counters were zero;
+peak copied pending input was 3,605,478 bytes. Native chart data readiness was
+true at resolution `15`, and the inspected crop showed candles and annotations.
+
+The test tab closed, disposing its temporary block. Existing user pages were not
+refreshed, and no financial control was clicked. The final explicit stop diagnostic
+contained a JavaScript syntax error before invocation, so this local run does not
+claim manual-stop completion; that path passed independently in the anonymous
+artifact test. Its last observed pending count was zero. The local report, loaded
+source and chart crops are in `/tmp/binance-chart-storage-release-live-wLHfqz/`.
+Neither live run read back saved keys or values. Persistent contents, competing
+revision integrity and reload are covered by the isolated real-IDB tests.
+These are bounded acceptance results, not a universal white-screen recovery or
+long-running stability claim.
+
+### Orderbook integration acceptance: 2026-10-07 (Beijing)
+
+The final implementation lives in `src/binance-orderbook-trade/chart-storage/`
+and ships only through the existing orderbook installer, version `2.7.218`.
+The standalone production entry and build target were removed. This optimizes
+the current matching Binance page's pinned TradingView mirror destination
+writes; it is neither a Chrome-wide userscript optimization nor limited to
+orders submitted by the orderbook. The destination database is shared by
+same-origin Binance pages and may contain historical symbol records.
+
+The storage installer runs before orderbook business initialization. During
+anonymous early-injection validation, the complete artifact exposed an existing
+startup assumption: both `document.head` and `document.documentElement` could
+still be absent when disabled-control styles were inserted. A regression first
+reproduced the `null.appendChild` failure. The initializer now observes creation
+of the HTML root, disconnects and initializes once; storage interception still
+starts immediately. The original iframe and SPA orderbook behavior is retained.
+
+The final artifact has 661,567 bytes and SHA-256
+`f281dd8c7b5f384e6f05ef1f7e837201923a0194d7fcdebd135997ee8e766f64`.
+All 2,143 Node/DOM tests, 132 chart-storage browser scenarios, test lint,
+build/syntax and final independent read-only review passed. The six integration
+scenarios include missing-root startup, source rejection, late injection,
+home-to-trading navigation and a real iframe.
+
+The anonymous Chrome smoke executed this complete artifact with both HTML root
+and head absent at injection. The orderbook initialized with exactly one panel
+and one style element and zero page errors. The anonymous host lacked its
+open/close mode anchor, so the panel remained hidden under its existing
+placement contract. The inspected screenshot showed candles and the depth
+overlay. Two interval changes committed five optimized transactions, six writes
+and thirteen skipped writes, with no failed, rejected, aborted or pending work.
+After stop, another interval change committed four native transactions and four
+writes while optimizer statistics stayed frozen. Chart data readiness was true
+throughout all three stages. The browser was closed. Evidence is retained in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-chart-storage-smoke-30Xntg/`.
diff --git a/docs/binance-orderbook-trade-development.md b/docs/binance-orderbook-trade-development.md
index ad1c795..4fb524c 100644
--- a/docs/binance-orderbook-trade-development.md
+++ b/docs/binance-orderbook-trade-development.md
@@ -20,6 +20,16 @@ scripts/binance-orderbook-trade.user.js
## Runtime
+### Chart mirror storage
+
+`chart-storage/install.js` runs before the orderbook business initializer. This
+module batches the pinned TradingView mirror destination writes and skips
+unchanged records. It is included in the existing orderbook installer, with no
+separate script. Its top-frame guard applies only to storage interception;
+source drift or late injection must leave the orderbook panel functional.
+See [the storage contract](binance-chart-storage-development.md) for native
+continuation, transaction boundaries and verification requirements.
+
### Chart save completion and serialization
`core/chart-save-coalescer.js` batches the observed default
diff --git a/e2e/binance-orderbook/helpers/chart-storage-concurrency.js b/e2e/binance-orderbook/helpers/chart-storage-concurrency.js
new file mode 100644
index 0000000..30778e4
--- /dev/null
+++ b/e2e/binance-orderbook/helpers/chart-storage-concurrency.js
@@ -0,0 +1,95 @@
+import { openProductionMirrorWriterLab } from './chart-storage-production-writer.js';
+
+/** Observe real transaction creation so queued work never relies on a timed wait. */
+export async function openChartStorageConcurrency(page) {
+ await openProductionMirrorWriterLab(page);
+ await page.evaluate(async () => {
+ await window.__STORAGE_LAB__.native.ready();
+ const nativeTransaction = IDBDatabase.prototype.transaction;
+ const state = {
+ pending: Object.create(null),
+ writes: [],
+ waiters: [],
+ waitForWrites(count) {
+ if (state.writes.length >= count) return Promise.resolve();
+ return new Promise(resolve => state.waiters.push({ count, resolve }));
+ },
+ restore() { IDBDatabase.prototype.transaction = nativeTransaction; },
+ };
+ IDBDatabase.prototype.transaction = function (...args) {
+ const transaction = Reflect.apply(nativeTransaction, this, args);
+ if (this.name === 'chart_futures' && transaction.mode === 'readwrite') {
+ const record = { outcome: 'pending' };
+ state.writes.push(record);
+ transaction.addEventListener('complete', () => { record.outcome = 'complete'; }, { once: true });
+ transaction.addEventListener('abort', () => { record.outcome = 'abort'; }, { once: true });
+ for (const waiter of state.waiters.splice(0)) {
+ if (state.writes.length >= waiter.count) waiter.resolve();
+ else state.waiters.push(waiter);
+ }
+ }
+ return transaction;
+ };
+ window.__CHART_CONCURRENCY__ = state;
+ });
+}
+
+/** Return after admission creates a real transaction, while its lock stays blocked. */
+export async function queueChartMirrorBatch(page, id, entries) {
+ await page.evaluate(async ({ id, entries }) => {
+ const state = window.__CHART_CONCURRENCY__;
+ const mirror = window.__MIRROR_WRITER__;
+ const next = state.writes.length + 1;
+ state.pending[id] = Promise.all(mirror.writer.dispatch(window.__STORAGE_LAB__.native, entries, mirror.nativeThunk));
+ await state.waitForWrites(next);
+ }, { id, entries });
+}
+
+export async function queueNativeChartClear(page, id) {
+ await page.evaluate(async id => {
+ const state = window.__CHART_CONCURRENCY__;
+ const next = state.writes.length + 1;
+ state.pending[id] = window.__STORAGE_LAB__.native.clear();
+ await state.waitForWrites(next);
+ }, id);
+}
+
+/** One native readonly transaction prevents the observation itself from mixing revisions. */
+export async function queueChartSnapshot(page, id) {
+ await page.evaluate(id => {
+ const transaction = window.__STORAGE_LAB__.native._dbInfo.db.transaction('keyvaluepairs', 'readonly');
+ const store = transaction.objectStore('keyvaluepairs');
+ const keys = store.getAllKeys();
+ const values = store.getAll();
+ window.__CHART_CONCURRENCY__.pending[id] = new Promise((resolve, reject) => {
+ transaction.addEventListener('complete', () => {
+ resolve(keys.result.map((key, index) => [key, values.result[index]]));
+ }, { once: true });
+ transaction.addEventListener('abort', () => reject(transaction.error), { once: true });
+ });
+ }, id);
+}
+
+export async function finishChartOperations(page, ids) {
+ return page.evaluate(async ids => {
+ const pending = window.__CHART_CONCURRENCY__.pending;
+ const results = await Promise.all(ids.map(id => pending[id]));
+ for (const id of ids) delete pending[id];
+ return results;
+ }, ids);
+}
+
+export async function chartConcurrencyState(page) {
+ return page.evaluate(() => ({
+ writes: window.__CHART_CONCURRENCY__.writes.map(record => record.outcome),
+ stats: window.__MIRROR_WRITER__.writer.getStats(),
+ nativeCalls: window.__MIRROR_WRITER__.nativeCalls,
+ }));
+}
+
+export async function stopChartConcurrency(page) {
+ await page.evaluate(async () => {
+ await window.__MIRROR_WRITER__.writer.stop();
+ window.__CHART_CONCURRENCY__.restore();
+ });
+}
diff --git a/e2e/binance-orderbook/helpers/chart-storage-host-workflows.js b/e2e/binance-orderbook/helpers/chart-storage-host-workflows.js
new file mode 100644
index 0000000..bbb4335
--- /dev/null
+++ b/e2e/binance-orderbook/helpers/chart-storage-host-workflows.js
@@ -0,0 +1,42 @@
+import assert from 'node:assert/strict';
+import { createHash } from 'node:crypto';
+import { readFile } from 'node:fs/promises';
+import { openStorageLab, STORAGE_LAB_ORIGIN } from './chart-storage-lab.js';
+
+const fixtureRoot = new URL('../../../test/fixtures/binance-chart-storage/', import.meta.url);
+const sha = value => createHash('sha256').update(value).digest('hex');
+
+/** Keep the executable fixture tied to every exact source slice recorded by extraction. */
+async function verifiedFixture() {
+ const [source, manifestText] = await Promise.all([
+ readFile(new URL('host-persistence.js', fixtureRoot), 'utf8'),
+ readFile(new URL('host-persistence-manifest.json', fixtureRoot), 'utf8'),
+ ]);
+ const manifest = JSON.parse(manifestText);
+ assert.equal(sha(source), manifest.fixture.sha256, 'Host persistence fixture hash changed');
+ for (const fragment of manifest.fragments) {
+ assert.equal(sha(source.slice(...fragment.fixture_utf16_range)), fragment.sha256, fragment.label);
+ }
+ return source;
+}
+
+/** The existing lab still blocks all external requests; this adds one exact local asset. */
+export async function openChartHostWorkflows(page, options) {
+ await openStorageLab(page, options);
+ const source = await verifiedFixture();
+ await page.context().route(`${STORAGE_LAB_ORIGIN}/host-persistence.js`, route => route.fulfill({
+ contentType: 'text/javascript', body: source,
+ }));
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/host-persistence.js` });
+ await page.evaluate(() => {
+ let runtimeRequire;
+ self.webpackChunkfutures_trade_ui.push([
+ ['codex-chart-host-workflows-entry'], {}, require => { runtimeRequire = require; },
+ ]);
+ window.__CHART_HOST__ = {
+ tradingView: runtimeRequire('codex-chart-host-tv'),
+ basic: runtimeRequire('codex-chart-host-basic'),
+ mirror: runtimeRequire('codex-chart-host-mirror'),
+ };
+ });
+}
diff --git a/e2e/binance-orderbook/helpers/chart-storage-lab.js b/e2e/binance-orderbook/helpers/chart-storage-lab.js
new file mode 100644
index 0000000..0a5e663
--- /dev/null
+++ b/e2e/binance-orderbook/helpers/chart-storage-lab.js
@@ -0,0 +1,235 @@
+import { readFile } from 'node:fs/promises';
+
+export const STORAGE_LAB_ORIGIN = 'https://chart-storage.test';
+const configuredContexts = new WeakSet();
+const fixtureRoot = new URL('../../../test/fixtures/binance-chart-storage/', import.meta.url);
+const adapterUrl = new URL('../../../experiments/binance-chart-storage/adapter.js', import.meta.url);
+const captureUrl = new URL('../../../experiments/binance-chart-storage/capture.js', import.meta.url);
+const bootstrapUrl = new URL('../../../experiments/binance-chart-storage/bootstrap.js', import.meta.url);
+const preflightUrl = new URL('../../../experiments/binance-chart-storage/preflight.js', import.meta.url);
+const pageHtml = `
+Storage LabStorage Lab
+Isolated Chromium · real IndexedDB · public localForage fixture · all external requests blocked
+Ready for an isolated chart-storage experiment.
+
+`;
+
+/** Count native database operations without replacing the storage workflow. */
+function installNativeObservation() {
+ const nativeTransaction = IDBDatabase.prototype.transaction;
+ const nativePut = IDBObjectStore.prototype.put;
+ const nativeGet = IDBObjectStore.prototype.get;
+ const nativeOpen = IDBFactory.prototype.open;
+ const isTarget = name => name === 'chart_futures' || name === 'chart_delivery';
+ const connections = new Map();
+ const state = {
+ enabled: false,
+ abortNextPut: false,
+ transactions: 0,
+ puts: 0,
+ gets: 0,
+ opens: 0,
+ completed: 0,
+ aborted: 0,
+ pending: 0,
+ versionChanges: 0,
+ };
+ IDBDatabase.prototype.transaction = function (...args) {
+ const transaction = nativeTransaction.apply(this, args);
+ if (isTarget(this.name) && !connections.has(this)) {
+ connections.set(this, this.version);
+ this.addEventListener('versionchange', () => {
+ if (state.enabled) state.versionChanges += 1;
+ });
+ }
+ if (state.enabled && isTarget(this.name)) {
+ state.transactions += 1;
+ state.pending += 1;
+ transaction.addEventListener('complete', () => {
+ state.completed += 1;
+ state.pending -= 1;
+ }, { once: true });
+ transaction.addEventListener('abort', () => {
+ state.aborted += 1;
+ state.pending -= 1;
+ }, { once: true });
+ }
+ return transaction;
+ };
+ IDBObjectStore.prototype.put = function (...args) {
+ const request = nativePut.apply(this, args);
+ if (state.enabled && isTarget(this.transaction.db.name)) state.puts += 1;
+ if (state.abortNextPut && isTarget(this.transaction.db.name)) {
+ state.abortNextPut = false;
+ request.addEventListener('success', () => this.transaction.abort(), { once: true });
+ }
+ return request;
+ };
+ IDBObjectStore.prototype.get = function (...args) {
+ if (state.enabled && isTarget(this.transaction.db.name)) state.gets += 1;
+ return nativeGet.apply(this, args);
+ };
+ IDBFactory.prototype.open = function (...args) {
+ if (state.enabled && isTarget(args[0])) state.opens += 1;
+ return nativeOpen.apply(this, args);
+ };
+ window.__STORAGE_NATIVE__ = {
+ snapshot: () => ({ ...state }),
+ reset() {
+ Object.assign(state, {
+ enabled: true, abortNextPut: false, transactions: 0, puts: 0,
+ gets: 0, opens: 0, completed: 0, aborted: 0, pending: 0,
+ versionChanges: 0,
+ });
+ },
+ abortNextPut() { state.abortNextPut = true; },
+ closeTarget(name) {
+ let count = 0;
+ for (const database of connections.keys()) {
+ if (database.name === name) {
+ database.close();
+ count += 1;
+ }
+ }
+ return count;
+ },
+ version(name) {
+ return Math.max(...[...connections].filter(([database]) => database.name === name).map(([, version]) => version));
+ },
+ };
+}
+
+/** The context owns every request, including runtime attempts to load remote chunks. */
+async function configureContext(context) {
+ if (configuredContexts.has(context)) return;
+ await context.route('**/*', async route => {
+ const url = new URL(route.request().url());
+ if (url.origin !== STORAGE_LAB_ORIGIN) return route.abort('blockedbyclient');
+ if (url.pathname === '/' || url.pathname === '/empty') {
+ return route.fulfill({
+ contentType: 'text/html',
+ body: url.pathname === '/empty'
+ ? pageHtml.replace('', '')
+ : url.searchParams.has('capture')
+ ? pageHtml.replace('', '')
+ : pageHtml,
+ });
+ }
+ const asset = {
+ '/webpack-runtime.js': new URL('webpack-runtime.js', fixtureRoot),
+ '/localforage-module.js': new URL('localforage-module.js', fixtureRoot),
+ '/adapter.js': adapterUrl,
+ '/capture.js': captureUrl,
+ '/bootstrap.js': bootstrapUrl,
+ '/preflight.js': preflightUrl,
+ }[url.pathname];
+ if (!asset) return route.abort('blockedbyclient');
+ return route.fulfill({ contentType: 'text/javascript', body: await readFile(asset, 'utf8') });
+ });
+ await context.addInitScript(installNativeObservation);
+ configuredContexts.add(context);
+}
+
+/** Registration precedes the host's first require, preserving the real runtime cache. */
+export async function initializeStorageLab(page, { install = true, options = {}, captureInstalled = false } = {}) {
+ await page.evaluate(async ({ install, options, captureInstalled }) => {
+ let runtimeRequire;
+ self.webpackChunkfutures_trade_ui.push([
+ ['codex-chart-storage-host'], {}, require => { runtimeRequire = require; },
+ ]);
+ if (typeof runtimeRequire !== 'function') throw new Error('The real runtime did not expose require');
+ const originalFactory = runtimeRequire.m[43917];
+ let factoryExecutions = 0;
+ runtimeRequire.m[43917] = function (...args) {
+ factoryExecutions += 1;
+ return originalFactory.apply(this, args);
+ };
+ const hostLocalforage = runtimeRequire(43917);
+ const nativeCreateInstance = captureInstalled
+ ? window.__STORAGE_CAPTURE_NATIVE_CREATE__
+ : hostLocalforage.createInstance;
+ const native = nativeCreateInstance.call(hostLocalforage, {
+ name: 'chart_futures', storeName: 'keyvaluepairs', driver: hostLocalforage.INDEXEDDB,
+ });
+ window.__STORAGE_LAB__ = {
+ localforage: hostLocalforage,
+ nativeCreateInstance,
+ native,
+ cacheIdentity() {
+ return {
+ sameExport: runtimeRequire(43917) === hostLocalforage,
+ factoryExecutions,
+ };
+ },
+ create(name = 'chart_futures', extra = {}) {
+ return hostLocalforage.createInstance({
+ name, storeName: 'keyvaluepairs', driver: hostLocalforage.INDEXEDDB, ...extra,
+ });
+ },
+ controller: captureInstalled
+ ? window.__STORAGE_CAPTURE_CONTROLLER__
+ : install ? (await import('/adapter.js')).installChartStoragePrototype(hostLocalforage, options) : null,
+ };
+ }, { install, options, captureInstalled });
+}
+
+export async function openStorageLab(page, { capture = false, captureInstall = false, initialize = true, ...options } = {}) {
+ await configureContext(page.context());
+ await page.goto(`${STORAGE_LAB_ORIGIN}/${capture ? '?capture=1' : ''}`);
+ if (capture) {
+ await page.evaluate(async ({ captureInstall, options }) => {
+ const { observeChartStorageModule } = await import('/capture.js');
+ window.__STORAGE_CAPTURE_ORIGINAL_PUSH__ = self.webpackChunkfutures_trade_ui.push;
+ if (captureInstall) {
+ const { installChartStoragePrototype } = await import('/adapter.js');
+ window.__STORAGE_CAPTURE__ = observeChartStorageModule(undefined, {
+ onCapture(localforage) {
+ window.__STORAGE_CAPTURE_NATIVE_CREATE__ = localforage.createInstance;
+ window.__STORAGE_CAPTURE_CONTROLLER__ = installChartStoragePrototype(localforage, options);
+ },
+ });
+ } else {
+ window.__STORAGE_CAPTURE__ = observeChartStorageModule();
+ }
+ }, { captureInstall, options: options.options || {} });
+ if (!initialize) {
+ await page.evaluate(() => {
+ const queue = self.webpackChunkfutures_trade_ui;
+ const observedPush = queue.push;
+ window.__STORAGE_CAPTURE_OBSERVED_PUSH__ = observedPush;
+ queue.push = function (chunk) {
+ if (Object.hasOwn(chunk[1], '43917')) window.__STORAGE_ORIGINAL_MODULE_FACTORY__ = chunk[1][43917];
+ return observedPush.call(this, chunk);
+ };
+ });
+ }
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/localforage-module.js` });
+ if (!initialize) {
+ await page.evaluate(() => {
+ self.webpackChunkfutures_trade_ui.push = window.__STORAGE_CAPTURE_OBSERVED_PUSH__;
+ });
+ }
+ }
+ if (initialize) await initializeStorageLab(page, { ...options, captureInstalled: captureInstall });
+}
+
+/** Failure scenarios register their own bounded module boundary into the real runtime. */
+export async function openStorageRuntime(page) {
+ await configureContext(page.context());
+ await page.goto(`${STORAGE_LAB_ORIGIN}/?capture=1`);
+}
+
+/** Startup integration scenarios install capture before either real host script. */
+export async function openEmptyStorageLab(page) {
+ await configureContext(page.context());
+ await page.goto(`${STORAGE_LAB_ORIGIN}/empty`);
+}
+
+export async function displayStorageResult(page, result) {
+ await page.locator('#result').evaluate((element, value) => {
+ element.textContent = JSON.stringify(value, null, 2);
+ }, result);
+}
diff --git a/e2e/binance-orderbook/helpers/chart-storage-mirror-scoped.js b/e2e/binance-orderbook/helpers/chart-storage-mirror-scoped.js
new file mode 100644
index 0000000..4956b3f
--- /dev/null
+++ b/e2e/binance-orderbook/helpers/chart-storage-mirror-scoped.js
@@ -0,0 +1,48 @@
+import assert from 'node:assert/strict';
+import { createHash } from 'node:crypto';
+import { readFile } from 'node:fs/promises';
+import { openEmptyStorageLab, STORAGE_LAB_ORIGIN } from './chart-storage-lab.js';
+import { openChartHostWorkflows } from './chart-storage-host-workflows.js';
+
+const fixtureRoot = new URL('../../../test/fixtures/binance-chart-storage/', import.meta.url);
+const experimentRoot = new URL('../../../experiments/binance-chart-storage/', import.meta.url);
+
+/** The existing lab route owns and blocks every request outside these exact assets. */
+async function addScopedAssets(context, includeWriter) {
+ const manifest = JSON.parse(await readFile(new URL('mirror-scoped-manifest.json', fixtureRoot), 'utf8'));
+ for (const [asset, root] of [
+ ['mirror-module.js', experimentRoot], ['mirror-scoped-callback.js', fixtureRoot],
+ ...(includeWriter ? [['mirror-writer.js', experimentRoot]] : []),
+ ]) {
+ const source = await readFile(new URL(asset, root), 'utf8');
+ if (asset !== 'mirror-writer.js') {
+ assert.equal(createHash('sha256').update(source).digest('hex'), manifest.outputs[asset].sha256, `${asset} hash changed`);
+ }
+ await context.route(`${STORAGE_LAB_ORIGIN}/${asset}`, route => route.fulfill({ contentType: 'text/javascript', body: source }));
+ }
+}
+
+export async function openMirrorBootstrapLab(page) {
+ await openEmptyStorageLab(page);
+ await addScopedAssets(page.context(), false);
+}
+
+/** Execute original host storage business functions against unmodified localForage instances. */
+export async function openChartMirrorScoped(page, { mode = 'scoped' } = {}) {
+ await openChartHostWorkflows(page, { install: false });
+ await addScopedAssets(page.context(), mode === 'scoped');
+ await page.evaluate(async mode => {
+ const { createMirrorCallbacks } = await import('/mirror-scoped-callback.js');
+ let runtimeRequire;
+ self.webpackChunkfutures_trade_ui.push([['mirror-scoped-callback-entry'], {}, require => { runtimeRequire = require; }]);
+ const localforage = window.__STORAGE_LAB__.localforage;
+ const state = { localforage, nativeCreateInstance: localforage.createInstance, writer: null, dispatches: 0 };
+ if (mode === 'scoped') state.writer = (await import('/mirror-writer.js')).createChartMirrorWriter();
+ const callbacks = createMirrorCallbacks(runtimeRequire, (target, entries, nativeThunk) => {
+ state.dispatches += 1;
+ return state.writer.dispatch(target, entries, nativeThunk);
+ });
+ state.create = mode === 'native' ? callbacks.native : callbacks.patched;
+ window.__MIRROR_SCOPED__ = state;
+ }, mode);
+}
diff --git a/e2e/binance-orderbook/helpers/chart-storage-mirror-writer.js b/e2e/binance-orderbook/helpers/chart-storage-mirror-writer.js
new file mode 100644
index 0000000..5232329
--- /dev/null
+++ b/e2e/binance-orderbook/helpers/chart-storage-mirror-writer.js
@@ -0,0 +1,57 @@
+import { readFile } from 'node:fs/promises';
+import { openStorageLab, STORAGE_LAB_ORIGIN } from './chart-storage-lab.js';
+
+/** Route only the new writer; the existing lab owns every other request. */
+export async function openMirrorWriterLab(page, { limits = {} } = {}) {
+ await openStorageLab(page, { install: false });
+ await page.route(`${STORAGE_LAB_ORIGIN}/mirror-writer.js`, async route => {
+ await route.fulfill({
+ contentType: 'text/javascript',
+ body: await readFile(new URL('../../../experiments/binance-chart-storage/mirror-writer.js', import.meta.url), 'utf8'),
+ });
+ });
+ await page.evaluate(async limits => {
+ const { createChartMirrorWriter } = await import('/mirror-writer.js');
+ window.__MIRROR_WRITER__ = {
+ writer: createChartMirrorWriter({ limits }),
+ nativeCalls: 0,
+ nativeThunk() {
+ window.__MIRROR_WRITER__.nativeCalls += 1;
+ throw new Error('An active mirror batch must not replay its native expression');
+ },
+ };
+ }, limits);
+}
+
+/** Native request events retain a real write lock without a synthetic clock. */
+export async function holdMirrorWriteLock(page) {
+ await page.evaluate(() => {
+ const transaction = window.__STORAGE_LAB__.native._dbInfo.db.transaction('keyvaluepairs', 'readwrite');
+ const store = transaction.objectStore('keyvaluepairs');
+ const pulses = [];
+ const lock = {
+ released: false,
+ progress: () => new Promise(resolve => pulses.push(resolve)),
+ completion: new Promise((resolve, reject) => {
+ transaction.addEventListener('complete', () => resolve('complete'), { once: true });
+ transaction.addEventListener('abort', () => reject(transaction.error), { once: true });
+ }),
+ };
+ function hold() {
+ store.get('#TV_SYMBOL-LOCK').onsuccess = () => {
+ for (const resolve of pulses.splice(0)) resolve();
+ if (!lock.released) hold();
+ };
+ }
+ window.__MIRROR_WRITE_LOCK__ = lock;
+ hold();
+ });
+ await page.evaluate(() => window.__MIRROR_WRITE_LOCK__.progress());
+}
+
+export async function releaseMirrorWriteLock(page) {
+ await page.evaluate(async () => {
+ window.__MIRROR_WRITE_LOCK__.released = true;
+ await window.__MIRROR_WRITE_LOCK__.completion;
+ });
+}
diff --git a/e2e/binance-orderbook/helpers/chart-storage-production-writer.js b/e2e/binance-orderbook/helpers/chart-storage-production-writer.js
new file mode 100644
index 0000000..6f76080
--- /dev/null
+++ b/e2e/binance-orderbook/helpers/chart-storage-production-writer.js
@@ -0,0 +1,33 @@
+import { readFile } from 'node:fs/promises';
+import { openStorageLab, STORAGE_LAB_ORIGIN } from './chart-storage-lab.js';
+
+/** Exercise the production writer against the captured native localForage driver. */
+export async function openProductionMirrorWriterLab(page, { limits = {} } = {}) {
+ await openStorageLab(page, { install: false });
+ await page.route(`${STORAGE_LAB_ORIGIN}/{production-writer,json}.js`, async route => {
+ const filename = new URL(route.request().url()).pathname === '/production-writer.js'
+ ? 'mirror-writer.js' : 'json.js';
+ await route.fulfill({
+ contentType: 'text/javascript',
+ body: await readFile(new URL(`../../../src/binance-orderbook-trade/chart-storage/${filename}`, import.meta.url), 'utf8'),
+ });
+ });
+ await page.evaluate(async limits => {
+ const { createChartMirrorWriter } = await import('/production-writer.js');
+ const state = {
+ writer: createChartMirrorWriter({ limits }),
+ nativeCalls: 0,
+ nativeThunk() {
+ state.nativeCalls += 1;
+ throw new Error('An optimized mirror must not replay its native expression');
+ },
+ dispatch(entries, target = window.__STORAGE_LAB__.native) {
+ return state.writer.dispatch(target, entries, () => {
+ state.nativeCalls += 1;
+ return entries.map(([key, value]) => target.setItem(key, value));
+ });
+ },
+ };
+ window.__MIRROR_WRITER__ = state;
+ }, limits);
+}
diff --git a/e2e/binance-orderbook/specs/chart-storage-concurrency.pw.js b/e2e/binance-orderbook/specs/chart-storage-concurrency.pw.js
new file mode 100644
index 0000000..8b46be2
--- /dev/null
+++ b/e2e/binance-orderbook/specs/chart-storage-concurrency.pw.js
@@ -0,0 +1,202 @@
+import { test, expect, reloadPageWithCoverage } from '../test.js';
+import { holdMirrorWriteLock, releaseMirrorWriteLock } from '../helpers/chart-storage-mirror-writer.js';
+import { initializeStorageLab, openStorageLab } from '../helpers/chart-storage-lab.js';
+import {
+ openChartStorageConcurrency,
+ queueChartMirrorBatch,
+ queueNativeChartClear,
+ queueChartSnapshot,
+ finishChartOperations,
+ chartConcurrencyState,
+ stopChartConcurrency,
+} from '../helpers/chart-storage-concurrency.js';
+
+const chartKeys = ['#TV_SYMBOL-BTCUSDT', '#TV_SYMBOL-ETHUSDT', 'myTradingView', 'myTradingView.layout'];
+const entriesFor = (writer, revision) => chartKeys.map((key, index) => [key, { writer, revision, index, points: [revision, index] }]);
+const sortedEntries = entries => [...entries].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
+
+test('user reads complete committed revisions through twelve rounds of three-tab contention and reload', async ({ page, context }) => {
+ // Given three independent writers share native IndexedDB and an unrelated target record.
+ const tabs = [page, await context.newPage(), await context.newPage()];
+ const preserved = [['candlestick-setting', { theme: 'target' }], ['#TV_SYMBOL-TARGET-ONLY', ['keep']]];
+ for (const tab of tabs) await openChartStorageConcurrency(tab);
+ await page.evaluate(async entries => {
+ for (const [key, value] of entries) await window.__STORAGE_LAB__.native.setItem(key, value);
+ }, preserved);
+ const rounds = [];
+ try {
+ // When all three revision batches and intervening native reads queue behind a real write lock each round.
+ for (let round = 0; round < 12; round += 1) {
+ await holdMirrorWriteLock(page);
+ const order = [0, 1, 2].map(offset => (round + offset) % tabs.length);
+ const batches = order.map(writer => entriesFor(writer, round + 1));
+ let blocked;
+ try {
+ for (let index = 0; index < order.length; index += 1) {
+ const entries = index % 2 === 0 ? batches[index] : [...batches[index]].reverse();
+ await queueChartMirrorBatch(tabs[order[index]], 'batch', entries);
+ await queueChartSnapshot(page, `snapshot-${index}`);
+ }
+ blocked = await Promise.all(tabs.map(chartConcurrencyState));
+ } finally {
+ await releaseMirrorWriteLock(page);
+ }
+ const outcomes = await Promise.all(tabs.map(tab => finishChartOperations(tab, ['batch'])));
+ const snapshots = await finishChartOperations(page, ['snapshot-0', 'snapshot-1', 'snapshot-2']);
+ await queueChartSnapshot(tabs[order[0]], 'final');
+ const [final] = await finishChartOperations(tabs[order[0]], ['final']);
+ rounds.push({ blocked, outcomes, snapshots, final, batches });
+ }
+ const states = await Promise.all(tabs.map(chartConcurrencyState));
+
+ // Then every observed commit is its entire batch, all tabs finish each round, and no writer replays natively.
+ expect(rounds).toHaveLength(12);
+ for (const round of rounds) {
+ expect(round.blocked.map(state => state.stats.pendingBatches)).toEqual([1, 1, 1]);
+ expect(round.outcomes).toEqual([[[undefined]], [[undefined]], [[undefined]]]);
+ expect(round.snapshots).toEqual(round.batches.map(batch => sortedEntries([...preserved, ...batch])));
+ expect(round.final).toEqual(sortedEntries([...preserved, ...round.batches[2]]));
+ }
+ for (const state of states) {
+ expect(state.stats).toMatchObject({ acceptedBatches: 12, committedTransactions: 12, committedWrites: 48, pendingBatches: 0, pendingBytes: 0, failedBatches: 0 });
+ expect(state.writes.every(outcome => outcome === 'complete')).toBe(true);
+ expect(state.nativeCalls).toBe(0);
+ }
+
+ // When an independent native reader reloads after the last competing batch has committed.
+ const reader = await context.newPage();
+ try {
+ await openStorageLab(reader, { install: false });
+ await reloadPageWithCoverage(reader);
+ await initializeStorageLab(reader, { install: false });
+ const reloaded = await reader.evaluate(async () => {
+ const entries = [];
+ await window.__STORAGE_LAB__.native.iterate((value, key) => { entries.push([key, value]); });
+ return entries;
+ });
+
+ // Then unchanged native iteration retrieves the exact last committed revision and preserved records.
+ expect(reloaded).toEqual(sortedEntries([...preserved, ...rounds.at(-1).batches[2]]));
+ } finally {
+ await reader.close();
+ }
+ } finally {
+ for (const tab of tabs) await stopChartConcurrency(tab);
+ for (const tab of tabs.slice(1)) await tab.close();
+ }
+});
+
+test('user observes batch clear batch commits in their native cross-tab transaction order', async ({ page, context }) => {
+ // Given ready same-origin tabs share a target containing existing chart and Basic records.
+ const tabs = [page, await context.newPage(), await context.newPage()];
+ for (const tab of tabs) await openChartStorageConcurrency(tab);
+ await page.evaluate(() => window.__STORAGE_LAB__.native.setItem('candlestick-setting', { theme: 'old' }));
+ const before = entriesFor('before-clear', 1);
+ const after = entriesFor('after-clear', 2);
+ await holdMirrorWriteLock(page);
+ let blocked;
+ try {
+ // When a mirror, native clear and another tab's mirror queue with native reads between them.
+ try {
+ await queueChartMirrorBatch(tabs[1], 'before', before);
+ await queueChartSnapshot(page, 'before');
+ await queueNativeChartClear(page, 'clear');
+ await queueChartSnapshot(page, 'cleared');
+ await queueChartMirrorBatch(tabs[2], 'after', after);
+ await queueChartSnapshot(page, 'after');
+ blocked = await Promise.all(tabs.map(chartConcurrencyState));
+ } finally {
+ await releaseMirrorWriteLock(page);
+ }
+ const [snapshots, first, last] = await Promise.all([
+ finishChartOperations(page, ['before', 'clear', 'cleared', 'after']),
+ finishChartOperations(tabs[1], ['before']),
+ finishChartOperations(tabs[2], ['after']),
+ ]);
+ const states = await Promise.all(tabs.map(chartConcurrencyState));
+
+ // Then clear removes every earlier record before the later complete revision repopulates the target.
+ expect(blocked.map(state => state.stats.pendingBatches)).toEqual([0, 1, 1]);
+ expect(blocked.map(state => state.writes.filter(outcome => outcome === 'pending').length)).toEqual([2, 1, 1]);
+ expect(snapshots).toEqual([sortedEntries([['candlestick-setting', { theme: 'old' }], ...before]), undefined, [], sortedEntries(after)]);
+ expect(first).toEqual([[undefined]]);
+ expect(last).toEqual([[undefined]]);
+ expect(states.map(state => state.stats.committedTransactions)).toEqual([0, 1, 1]);
+ expect(states.map(state => state.nativeCalls)).toEqual([0, 0, 0]);
+ } finally {
+ for (const tab of tabs) await stopChartConcurrency(tab);
+ for (const tab of tabs.slice(1)) await tab.close();
+ }
+});
+
+test('user keeps real native values and callbacks after stop drains its last optimized batch', async ({ page }) => {
+ // Given an accepted complete revision waits for the target's real write lock.
+ await openChartStorageConcurrency(page);
+ await holdMirrorWriteLock(page);
+ await queueChartMirrorBatch(page, 'accepted', entriesFor('optimized', 1));
+ let blocked;
+ try {
+ // When stop closes admission and the continuation invokes unchanged native setItem promises and callbacks.
+ try {
+ blocked = await page.evaluate(entries => {
+ const state = window.__CHART_CONCURRENCY__;
+ const writer = window.__MIRROR_WRITER__.writer;
+ state.events = [];
+ state.callbacks = [];
+ state.nativeCalls = 0;
+ state.stopped = false;
+ state.stopping = writer.stop();
+ state.stopping.then(() => { state.stopped = true; state.events.push('stopped'); });
+ state.pending.tail = Promise.all(writer.dispatch(window.__STORAGE_LAB__.native, entries, () => {
+ state.nativeCalls += 1;
+ state.events.push('native-thunk');
+ return entries.map(([key, value]) => window.__STORAGE_LAB__.native.setItem(key, value, (error, result) => {
+ state.callbacks.push({ key, error, value: result });
+ }));
+ }));
+ return { stopped: state.stopped, events: state.events, callbacks: state.callbacks, nativeCalls: state.nativeCalls, stats: writer.getStats() };
+ }, entriesFor('native-draining', 2));
+ } finally {
+ await releaseMirrorWriteLock(page);
+ }
+ const [accepted, tail] = await finishChartOperations(page, ['accepted', 'tail']);
+ await queueChartSnapshot(page, 'drained');
+ const [drained] = await finishChartOperations(page, ['drained']);
+ const continuation = await page.evaluate(async entries => {
+ const state = window.__CHART_CONCURRENCY__;
+ const writer = window.__MIRROR_WRITER__.writer;
+ await state.stopping;
+ const atStop = writer.getStats();
+ let nativeResult;
+ const dispatched = writer.dispatch(null, null, () => {
+ state.nativeCalls += 1;
+ nativeResult = entries.map(([key, value]) => window.__STORAGE_LAB__.native.setItem(key, value, (error, result) => {
+ state.callbacks.push({ key, error, value: result });
+ }));
+ return nativeResult;
+ });
+ const exactArray = dispatched === nativeResult;
+ const exactPromises = dispatched.every((promise, index) => promise === nativeResult[index]);
+ return { values: await Promise.all(dispatched), callbacks: state.callbacks, nativeCalls: state.nativeCalls,
+ events: state.events, stopped: state.stopped, exactArray, exactPromises, atStop, after: writer.getStats() };
+ }, entriesFor('native-stopped', 3));
+ await queueChartSnapshot(page, 'final');
+ const [final] = await finishChartOperations(page, ['final']);
+
+ // Then native continuations persist both revisions and preserve callbacks without accepting another optimized batch.
+ expect(blocked).toMatchObject({ stopped: false, events: [], callbacks: [], nativeCalls: 0, stats: { acceptedBatches: 1, pendingBatches: 1, committedTransactions: 0 } });
+ expect(accepted).toEqual([undefined]);
+ expect(tail).toEqual([entriesFor('native-draining', 2).map(([, value]) => value)]);
+ expect(drained).toEqual(sortedEntries(entriesFor('native-draining', 2)));
+ expect(continuation.values).toEqual(entriesFor('native-stopped', 3).map(([, value]) => value));
+ expect(continuation.callbacks).toEqual([
+ ...entriesFor('native-draining', 2), ...entriesFor('native-stopped', 3),
+ ].map(([key, value]) => ({ key, error: null, value })));
+ expect(continuation).toMatchObject({ events: ['stopped', 'native-thunk'], nativeCalls: 2, stopped: true, exactArray: true, exactPromises: true });
+ expect(continuation.atStop).toMatchObject({ acceptedBatches: 1, committedTransactions: 1, committedWrites: 4, pendingBatches: 0, pendingBytes: 0 });
+ expect(continuation.after).toEqual(continuation.atStop);
+ expect(final).toEqual(sortedEntries(entriesFor('native-stopped', 3)));
+ } finally {
+ await stopChartConcurrency(page);
+ }
+});
diff --git a/e2e/binance-orderbook/specs/chart-storage-host-workflows.pw.js b/e2e/binance-orderbook/specs/chart-storage-host-workflows.pw.js
new file mode 100644
index 0000000..c317373
--- /dev/null
+++ b/e2e/binance-orderbook/specs/chart-storage-host-workflows.pw.js
@@ -0,0 +1,378 @@
+import { test, expect } from '../test.js';
+import { openChartHostWorkflows } from '../helpers/chart-storage-host-workflows.js';
+
+function chart(chartId, symbol, drawing = true) {
+ const mainId = `main-${symbol}`;
+ return {
+ chartId,
+ panes: [{
+ mainSourceId: mainId,
+ sources: [
+ { id: mainId, type: 'MainSeries', state: { symbol } },
+ ...(drawing ? [{
+ id: `drawing-${symbol}`, type: 'LineToolTrendLine', ownerSource: mainId,
+ state: { symbol }, points: [{ time: 1000, price: 20 }, { time: 2000, price: 25 }],
+ }] : []),
+ ],
+ }],
+ };
+}
+
+for (const install of [false, true]) {
+ const mode = install ? 'adapter' : 'native';
+
+ test(`user restores staged chart saves and their native cross-database mirror with ${mode}`, async ({ page }, testInfo) => {
+ // Given both databases have independent settings and the source retains an older layout chart.
+ await openChartHostWorkflows(page, { install });
+ const oldChart = chart('1', 'SOLUSDT', false);
+ const first = { layout: '2h', revision: 1, charts: [chart('2', 'ETHUSDT'), chart('0', 'BTCUSDT')] };
+ await page.evaluate(async ({ oldChart, first }) => {
+ const { localforage } = window.__STORAGE_LAB__;
+ const storage = localforage.createInstance({ name: 'chart_futures' });
+ const delivery = localforage.createInstance({ name: 'chart_delivery' });
+ await storage.setItem('myTradingView.layout', [oldChart]);
+ await storage.setItem('candlestick-setting', { theme: 'source-basic' });
+ await delivery.setItem('candlestick-setting', { theme: 'delivery-basic' });
+ await delivery.setItem('#TV_SYMBOL-DESTINATION-ONLY', []);
+ const widget = { activeChartIndex: () => 1 };
+ await window.__CHART_HOST__.tradingView.saveCustomSettings({ storage, key: 'myTradingView', widget });
+ const mirror = window.__CHART_HOST__.mirror.create('chart_futures');
+ window.__HOST_SCENARIO__ = {
+ storage, delivery, first,
+ save: window.__CHART_HOST__.tradingView.createSaveCallback(storage, 'myTradingView', widget, mirror),
+ };
+ window.__STORAGE_NATIVE__.reset();
+ }, { oldChart, first });
+
+ // When the original host callback saves four iframe snapshots and awaits its original mirror callback.
+ const result = await page.evaluate(async () => {
+ const state = window.__HOST_SCENARIO__;
+ const iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ try {
+ for (const revision of [1, 1, 2, 2]) {
+ const snapshot = iframe.contentWindow.JSON.parse(JSON.stringify({ ...state.first, revision }));
+ await state.save(snapshot);
+ }
+ } finally {
+ iframe.remove();
+ }
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ const { localforage, controller } = window.__STORAGE_LAB__;
+ const source = localforage.createInstance({ name: 'chart_futures' });
+ const target = localforage.createInstance({ name: 'chart_delivery' });
+ const load = storage => window.__CHART_HOST__.tradingView.load({ storage, key: 'myTradingView' });
+ return {
+ source: await load(source), target: await load(target),
+ sourceBasic: await source.getItem('candlestick-setting'), targetBasic: await target.getItem('candlestick-setting'),
+ targetOnly: await target.getItem('#TV_SYMBOL-DESTINATION-ONLY'),
+ sourceKeys: (await source.keys()).sort(), targetKeys: (await target.keys()).sort(),
+ snapshot: await source.getItem('myTradingView'), metrics,
+ sourceDriver: source.driver(), targetDriver: target.driver(),
+ pending: controller ? controller.getStats().pendingOperations : null,
+ };
+ });
+ await testInfo.attach(`host-save-mirror-${mode}.json`, {
+ body: JSON.stringify(result.metrics, null, 2), contentType: 'application/json',
+ });
+
+ // Then rebuilt instances restore charts and drawings while unrelated settings remain local to each database.
+ const expected = {
+ chartSave: { layout: '2h', revision: 2, charts: [chart('0', 'BTCUSDT'), oldChart, chart('2', 'ETHUSDT')] },
+ customSettings: { activeChartIndex: 1 },
+ };
+ expect(result.source).toEqual(expected);
+ expect(result.target).toEqual(expected);
+ expect(result.snapshot).toEqual({ layout: '2h', revision: 2, charts: [chart('2', 'ETHUSDT', false), chart('0', 'BTCUSDT', false)] });
+ expect(result.sourceBasic).toEqual({ theme: 'source-basic' });
+ expect(result.targetBasic).toEqual({ theme: 'delivery-basic' });
+ expect(result.targetOnly).toEqual([]);
+ const sourceKeys = ['#TV_SYMBOL-BTCUSDT', '#TV_SYMBOL-ETHUSDT', 'candlestick-setting', 'myTradingView', 'myTradingView.customSettings', 'myTradingView.layout'];
+ expect(result.sourceKeys).toEqual(sourceKeys);
+ expect(result.targetKeys).toEqual([...sourceKeys, '#TV_SYMBOL-DESTINATION-ONLY'].sort());
+ expect(result.sourceDriver).toBe('asyncStorage');
+ expect(result.targetDriver).toBe('asyncStorage');
+ expect(result.metrics.transactions).toBe(install ? 28 : 64);
+ expect(result.metrics.puts).toBe(install ? 11 : 36);
+ expect(result.metrics.aborted).toBe(0);
+ expect(result.metrics.pending).toBe(0);
+ expect(result.pending).toBe(install ? 0 : null);
+ });
+
+ test(`user removes a symbol's last drawing without reviving it on load with ${mode}`, async ({ page }) => {
+ // Given the original save workflow has persisted a drawing for each of two symbols.
+ await openChartHostWorkflows(page, { install });
+ await page.evaluate(async save => {
+ const storage = window.__STORAGE_LAB__.localforage.createInstance({ name: 'chart_futures' });
+ window.__HOST_SCENARIO__ = { storage };
+ await window.__CHART_HOST__.tradingView.save({ storage, save, key: 'myTradingView', widget: {} });
+ }, { layout: '2h', charts: [chart('0', 'BTCUSDT'), chart('1', 'ETHUSDT')] });
+
+ // When a new snapshot contains no BTC drawing and a newly created instance loads the saved layout.
+ const snapshot = { layout: '2h', charts: [chart('0', 'BTCUSDT', false), chart('1', 'ETHUSDT')] };
+ const result = await page.evaluate(async save => {
+ const storage = window.__HOST_SCENARIO__.storage;
+ await window.__CHART_HOST__.tradingView.save({ storage, save, key: 'myTradingView', widget: {} });
+ const reopened = window.__STORAGE_LAB__.localforage.createInstance({ name: 'chart_futures' });
+ return {
+ btc: await reopened.getItem('#TV_SYMBOL-BTCUSDT'), eth: await reopened.getItem('#TV_SYMBOL-ETHUSDT'),
+ loaded: await window.__CHART_HOST__.tradingView.load({ storage: reopened, key: 'myTradingView' }),
+ };
+ }, snapshot);
+
+ // Then an explicit empty drawing list prevents resurrection while the other symbol's drawing survives.
+ expect(result.btc).toEqual([]);
+ expect(result.eth).toEqual([chart('1', 'ETHUSDT').panes[0].sources[1]]);
+ expect(result.loaded).toEqual({ chartSave: snapshot, customSettings: null });
+ });
+
+ test(`user restores all six Basic storage categories through original host functions with ${mode}`, async ({ page }) => {
+ // Given a fresh Basic storage instance receives the JSON values produced at its host storage boundary.
+ await openChartHostWorkflows(page, { install });
+ const values = {
+ annotations: [{ id: 'CANDLE', annotations: [{
+ id: 'basic-line', type: 'trend', data: [{ time: 1000, value: 20 }, { time: 2000, value: 25 }],
+ }] }],
+ indicators: [{ id: 'CANDLE', indicators: [{ id: 'MA', categories: ['main'] }], weight: 1 }],
+ indicatorSettings: { MA: { lengths: [7, 25, 99] } },
+ settings: { '@colorTable': { 'candle.upBarColor': '#00ff00' }, '@watermark': 'test-watermark', background: '#101010', grid: { show: true } },
+ annotationConfig: { trend: { lineWidth: 2, color: '#ffffff' } },
+ compareSymbolSettings: { ETHUSDT: { color: '#ff9900', visible: true } },
+ };
+
+ // When all original Basic save methods persist their categories and a new instance loads them.
+ const result = await page.evaluate(async values => {
+ const create = () => window.__CHART_HOST__.basic.create({ namespace: 'chart_futures', symbol: 'BTCUSDT' });
+ const storage = create();
+ await Promise.all([
+ storage.saveAnnotations(values.annotations, { interval: '15m' }),
+ storage.saveIndicators(values.indicators), storage.saveIndicatorSettings(values.indicatorSettings),
+ storage.saveSettings(values.settings), storage.saveAnnotationConfig(values.annotationConfig),
+ storage.saveCompareSymbolSettings(values.compareSymbolSettings),
+ ]);
+ const raw = window.__STORAGE_LAB__.localforage.createInstance({ name: 'chart_futures' });
+ return {
+ loaded: await create().load({ interval: '15m' }), keys: (await raw.keys()).sort(),
+ annotations: await raw.getItem('BASIC_KLINE_BTCUSDT'), settings: await raw.getItem('candlestick-setting'),
+ };
+ }, values);
+
+ // Then actual annotation conversion and settings exclusions survive a complete six-category reload.
+ const annotations = [{ id: 'CANDLE', annotations: [{
+ id: 'basic-line', type: 'trend', data: { p1: { time: 1000, value: 20 }, p2: { time: 2000, value: 25 } },
+ }] }];
+ expect(result.annotations).toEqual(annotations);
+ expect(result.settings).toEqual({ background: '#101010', grid: { show: true } });
+ expect(result.keys).toEqual([
+ 'BASIC_KLINE_BTCUSDT', 'BASIC_KLINE_COMPARE_SYMBOL_SETTINGS', 'BASIC_KLINE_INDICATOR',
+ 'candlestick-indicator-setting', 'candlestick-setting', 'original-candlestick',
+ ]);
+ expect(result.loaded).toEqual({
+ storeData: [{ ...values.indicators[0], annotations: annotations[0].annotations }],
+ indicatorSetting: values.indicatorSettings, setting: { background: '#101010', grid: { show: true } },
+ annotationConfig: values.annotationConfig, compareSymbolSettings: values.compareSymbolSettings,
+ });
+ });
+
+ test(`user loads original Basic JSON symbol keys through the native migration with ${mode}`, async ({ page }) => {
+ // Given the real store contains only the host's legacy JSON symbol key and explicit indicator data.
+ await openChartHostWorkflows(page, { install });
+ const oldKey = 'BASIC_KLINE_{"pair":"SOLUSDT","contractType":"PERPETUAL"}';
+ const annotations = [{ type: 'CANDLE', annotations: [{ type: 'trend', data: { time: 1000, value: 20 } }] }];
+ await page.evaluate(async ({ oldKey, annotations }) => {
+ const raw = window.__STORAGE_LAB__.native;
+ await raw.setItem(oldKey, annotations);
+ await raw.setItem('BASIC_KLINE_INDICATOR', [{ id: 'CANDLE', indicators: [] }]);
+ }, { oldKey, annotations });
+
+ // When the original Basic load discovers a missing current key and executes Lc before reading settings.
+ const result = await page.evaluate(async oldKey => {
+ const storage = window.__CHART_HOST__.basic.create({ namespace: 'chart_futures', symbol: 'SOLUSDT' });
+ const loaded = await storage.load({ interval: '15m' });
+ const raw = window.__STORAGE_LAB__.native;
+ return { loaded, old: await raw.getItem(oldKey), current: await raw.getItem('BASIC_KLINE_SOLUSDT'), keys: (await raw.keys()).sort() };
+ }, oldKey);
+
+ // Then migration copies the stored value without deleting its original identity and normalizes the loaded panel.
+ expect(result.old).toEqual(annotations);
+ expect(result.current).toEqual(annotations);
+ expect(result.keys).toEqual(['BASIC_KLINE_INDICATOR', 'BASIC_KLINE_SOLUSDT', oldKey].sort());
+ expect(result.loaded).toEqual({
+ storeData: [{ id: 'CANDLE', type: 'CANDLE', indicators: [], annotations: annotations[0].annotations }],
+ indicatorSetting: null, setting: null, annotationConfig: null, compareSymbolSettings: null,
+ });
+ });
+
+ test(`user clears the shared chart store through Basic while preserving the other database with ${mode}`, async ({ page }) => {
+ // Given Basic and TradingView share one initialized store while delivery retains an independent record.
+ await openChartHostWorkflows(page, { install });
+ await page.evaluate(async () => {
+ const { localforage } = window.__STORAGE_LAB__;
+ const tv = localforage.createInstance({ name: 'chart_futures' });
+ const delivery = localforage.createInstance({ name: 'chart_delivery' });
+ const basic = window.__CHART_HOST__.basic.create({ namespace: 'chart_futures', symbol: 'BTCUSDT' });
+ await basic.saveSettings({ background: 'initial' });
+ await tv.setItem('myTradingView', { revision: 0 });
+ await delivery.setItem('myTradingView', { revision: 'delivery' });
+ window.__HOST_SCENARIO__ = { basic, tv, delivery };
+ });
+
+ // When completed chart and Basic saves are cleared before another chart save starts.
+ const result = await page.evaluate(async () => {
+ const { basic, tv, delivery } = window.__HOST_SCENARIO__;
+ await Promise.all([
+ tv.setItem('#TV_SYMBOL-BEFORE', []), basic.saveSettings({ background: 'before-clear' }),
+ ]);
+ await basic.clear();
+ await tv.setItem('#TV_SYMBOL-AFTER', []);
+ return {
+ keys: await tv.keys(), after: await tv.getItem('#TV_SYMBOL-AFTER'),
+ snapshot: await tv.getItem('myTradingView'), settings: await tv.getItem('candlestick-setting'),
+ delivery: await delivery.getItem('myTradingView'),
+ };
+ });
+
+ // Then shared-store clear removes both chart types in call order and leaves the other database untouched.
+ expect(result).toEqual({ keys: ['#TV_SYMBOL-AFTER'], after: [], snapshot: null, settings: null, delivery: { revision: 'delivery' } });
+ });
+
+ test(`user observes the documented concurrent Basic clear ordering boundary with ${mode}`, async ({ page }) => {
+ // Given retained host instances have finished initialization with one committed chart and Basic setting.
+ await openChartHostWorkflows(page, { install });
+ await page.evaluate(async () => {
+ const tv = window.__STORAGE_LAB__.localforage.createInstance({ name: 'chart_futures' });
+ const basic = window.__CHART_HOST__.basic.create({ namespace: 'chart_futures', symbol: 'BTCUSDT' });
+ await tv.setItem('myTradingView', { revision: 0 });
+ await basic.saveSettings({ background: 'initial' });
+ window.__HOST_SCENARIO__ = { tv, basic };
+ });
+
+ // When saves and the original Basic clear are issued together without awaiting earlier saves.
+ const result = await page.evaluate(async () => {
+ const { tv, basic } = window.__HOST_SCENARIO__;
+ await Promise.all([
+ tv.setItem('#TV_SYMBOL-BEFORE', []), basic.saveSettings({ background: 'before-clear' }),
+ basic.clear(), tv.setItem('#TV_SYMBOL-AFTER', []),
+ ]);
+ return {
+ keys: (await tv.keys()).sort(), snapshot: await tv.getItem('myTradingView'),
+ settings: await tv.getItem('candlestick-setting'),
+ };
+ });
+
+ // Then native set preparation runs after clear while the adapter enforces its stronger issue-order contract.
+ expect(result).toEqual(install
+ ? { keys: ['#TV_SYMBOL-AFTER'], snapshot: null, settings: null }
+ : { keys: ['#TV_SYMBOL-AFTER', '#TV_SYMBOL-BEFORE', 'candlestick-setting'], snapshot: null, settings: { background: 'before-clear' } });
+ });
+
+ test(`user preserves 290 historical drawings through 32 bounded concurrent host save chains with ${mode}`, async ({ page }, testInfo) => {
+ // Given both databases contain identical historical drawings and one fully saved and mirrored initial snapshot.
+ test.setTimeout(30_000);
+ await openChartHostWorkflows(page, { install });
+ const history = Array.from({ length: 290 }, (_, index) => {
+ const symbol = `LAB${String(index).padStart(3, '0')}USDT`;
+ return { key: `#TV_SYMBOL-${symbol}`, value: [chart(String(index), symbol).panes[0].sources[1]] };
+ });
+ const initial = { layout: 's', revision: 0, charts: [chart('0', 'BTCUSDT')] };
+ await page.evaluate(async ({ history, initial }) => {
+ const { localforage } = window.__STORAGE_LAB__;
+ const source = localforage.createInstance({ name: 'chart_futures' });
+ const target = localforage.createInstance({ name: 'chart_delivery' });
+ await Promise.all(history.flatMap(({ key, value }) => [source.setItem(key, value), target.setItem(key, value)]));
+ const save = window.__CHART_HOST__.tradingView.createSaveCallback(
+ source, 'myTradingView', {}, window.__CHART_HOST__.mirror.create('chart_futures'),
+ );
+ await save(initial);
+ const controller = window.__STORAGE_LAB__.controller;
+ window.__HOST_SCENARIO__ = {
+ source, target, save, initial, history, statsBefore: controller ? controller.getStats() : null,
+ };
+ window.__STORAGE_NATIVE__.reset();
+ }, { history, initial });
+
+ // When eight successive revisions each run four original save and mirror chains concurrently to completion.
+ const result = await page.evaluate(async () => {
+ const state = window.__HOST_SCENARIO__;
+ const startedAt = performance.now();
+ let completed = 0;
+ for (let revision = 1; revision <= 8; revision += 1) {
+ const snapshot = structuredClone(state.initial);
+ snapshot.revision = revision;
+ snapshot.charts[0].panes[0].sources[1].points[1].price = 25 + revision;
+ const results = await Promise.all(Array.from({ length: 4 }, () => state.save(snapshot)));
+ completed += results.length;
+ }
+ const elapsedMs = performance.now() - startedAt;
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ const controller = window.__STORAGE_LAB__.controller;
+ let stats = null;
+ let peaks = null;
+ if (controller) {
+ const current = controller.getStats();
+ const counters = [
+ 'acceptedOperations', 'rejectedOperations', 'failedOperations', 'transactions',
+ 'committedTransactions', 'abortedTransactions', 'committedWrites', 'skippedWrites', 'barriers',
+ ];
+ stats = {
+ ...Object.fromEntries(counters.map(name => [name, current[name] - state.statsBefore[name]])),
+ pendingOperations: current.pendingOperations, pendingBytes: current.pendingBytes,
+ };
+ peaks = {
+ scope: 'Since installation, including setup.',
+ pendingOperations: current.peakPendingOperations, pendingBytes: current.peakPendingBytes,
+ setupPendingOperations: state.statsBefore.peakPendingOperations, setupPendingBytes: state.statsBefore.peakPendingBytes,
+ };
+ }
+ const read = async storage => ({
+ keys: (await storage.keys()).sort(),
+ snapshot: await storage.getItem('myTradingView'), layout: await storage.getItem('myTradingView.layout'),
+ drawing: await storage.getItem('#TV_SYMBOL-BTCUSDT'),
+ historical: await Promise.all(state.history.map(async ({ key }) => ({ key, value: await storage.getItem(key) }))),
+ loaded: await window.__CHART_HOST__.tradingView.load({ storage, key: 'myTradingView' }),
+ });
+ return { completed, elapsedMs, metrics, stats, peaks, source: await read(state.source), target: await read(state.target) };
+ });
+ const measurement = {
+ mode, historicalKeysPerDatabase: 290, rounds: 8, concurrentChainsPerRound: 4,
+ completed: result.completed, elapsedMs: result.elapsedMs, metrics: result.metrics, stats: result.stats, peaks: result.peaks,
+ scope: 'Isolated fixture workload only; setup and final verification IO excluded from IDB metrics and controller counters. Peaks include setup.',
+ };
+ await testInfo.attach(`host-bounded-load-${mode}.json`, {
+ body: JSON.stringify(measurement, null, 2), contentType: 'application/json',
+ });
+ console.info(JSON.stringify(measurement));
+
+ // Then every chain completes and both complete databases retain all historical values plus the final changed snapshot and drawing.
+ const finalChart = chart('0', 'BTCUSDT');
+ finalChart.panes[0].sources[1].points[1].price = 33;
+ const expectedKeys = [...history.map(({ key }) => key), '#TV_SYMBOL-BTCUSDT', 'myTradingView', 'myTradingView.layout'].sort();
+ const expected = {
+ keys: expectedKeys,
+ snapshot: { layout: 's', revision: 8, charts: [chart('0', 'BTCUSDT', false)] },
+ layout: [chart('0', 'BTCUSDT', false)], drawing: [finalChart.panes[0].sources[1]], historical: history,
+ loaded: { chartSave: { layout: 's', revision: 8, charts: [finalChart] }, customSettings: null },
+ };
+ expect(result.completed).toBe(32);
+ expect(result.source).toEqual(expected);
+ expect(result.target).toEqual(expected);
+ expect(result.elapsedMs).toBeGreaterThan(0);
+ expect(result.metrics.pending).toBe(0);
+ expect(result.metrics.aborted).toBe(0);
+ expect(result.metrics.completed).toBe(result.metrics.transactions);
+ expect(result.metrics.puts).toBe(install ? 32 : 296 * 32);
+ if (install) {
+ expect(result.metrics.transactions).toBeLessThan(591 * 32);
+ expect(result.stats.pendingOperations).toBe(0);
+ expect(result.stats.pendingBytes).toBe(0);
+ expect(result.stats.failedOperations).toBe(0);
+ expect(result.stats.rejectedOperations).toBe(0);
+ expect(result.stats.acceptedOperations).toBe(591 * 32);
+ expect(result.stats.committedWrites).toBe(32);
+ expect(result.stats.skippedWrites).toBe(296 * 32 - 32);
+ } else {
+ expect(result.metrics.transactions).toBe(591 * 32);
+ expect(result.stats).toBe(null);
+ }
+ });
+}
diff --git a/e2e/binance-orderbook/specs/chart-storage-integration.pw.js b/e2e/binance-orderbook/specs/chart-storage-integration.pw.js
new file mode 100644
index 0000000..f211fb6
--- /dev/null
+++ b/e2e/binance-orderbook/specs/chart-storage-integration.pw.js
@@ -0,0 +1,534 @@
+import { test, expect } from '../test.js';
+import { initializeStorageLab, openEmptyStorageLab, openStorageLab, STORAGE_LAB_ORIGIN } from '../helpers/chart-storage-lab.js';
+import { installScenarioClock, pauseScenarioClock } from '../helpers/scenario-clock.js';
+
+async function installEarlyCapture(page, { existingQueue = true, callbackFails = false } = {}) {
+ await page.evaluate(async ({ existingQueue, callbackFails }) => {
+ const { observeChartStorageBootstrap } = await import('/bootstrap.js');
+ const { installChartStoragePrototype } = await import('/adapter.js');
+ if (existingQueue) self.webpackChunkfutures_trade_ui = [];
+ const state = {
+ captures: 0, factoryExecutions: 0, completion: 'pending',
+ hostResult: null, hostError: null,
+ };
+ window.__STORAGE_STARTUP__ = state;
+ state.capture = observeChartStorageBootstrap({
+ onCapture(localforage) {
+ state.captures += 1;
+ if (callbackFails) throw new Error('Startup capture callback failed');
+ state.capturedExport = localforage;
+ state.nativeCreateInstance = localforage.createInstance;
+ state.controller = installChartStoragePrototype(localforage);
+ },
+ });
+ state.queue = self.webpackChunkfutures_trade_ui;
+ state.capture.captured.then(
+ value => { state.completion = 'fulfilled'; state.completedExport = value; },
+ error => { state.completion = 'rejected'; state.captureError = error.message; },
+ );
+ state.runHost = require => {
+ try {
+ const localforage = require(43917);
+ state.hostExport = localforage;
+ state.store = localforage.createInstance({
+ name: 'chart_futures', storeName: 'keyvaluepairs', driver: localforage.INDEXEDDB,
+ });
+ state.saved = state.store.setItem('#TV_SYMBOL-STARTUP', { interval: '15' });
+ state.hostResult = {
+ captures: state.captures,
+ acceptedBeforeReturn: state.controller ? state.controller.getStats().acceptedOperations : 0,
+ exportMatches: localforage === state.capturedExport,
+ };
+ } catch (error) {
+ state.hostError = error.message;
+ }
+ };
+ }, { existingQueue, callbackFails });
+}
+
+/**
+ * Execute the unchanged library fixture with only its chunk-registration boundary
+ * supplied by the host. The real factory and runtime cache own module execution.
+ */
+async function registerStorageChunk(page, { requireStorage = true } = {}) {
+ await page.evaluate(async ({ requireStorage }) => {
+ const response = await fetch('/localforage-module.js');
+ if (!response.ok) throw new Error(`Storage fixture request failed: ${response.status}`);
+ const source = await response.text();
+ const state = window.__STORAGE_STARTUP__;
+ const registration = {
+ webpackChunkfutures_trade_ui: {
+ push(chunk) {
+ const actualFactory = chunk[1][43917];
+ state.originalFactory = function (...args) {
+ state.factoryExecutions += 1;
+ const result = actualFactory.apply(this, args);
+ state.nativeFactoryBeforeCapture = args[0].exports.createInstance;
+ return result;
+ };
+ chunk[1][43917] = state.originalFactory;
+ chunk[2] = require => {
+ state.require = require;
+ if (requireStorage) state.runHost(require);
+ };
+ return state.queue.push(chunk);
+ },
+ },
+ };
+ new Function('self', source)(registration);
+ }, { requireStorage });
+}
+
+async function installStoragePreflight(page) {
+ await page.evaluate(async () => {
+ const { startChartStoragePreflight } = await import('/preflight.js');
+ const preflight = startChartStoragePreflight();
+ const state = {
+ preflight, queue: self.webpackChunkfutures_trade_ui, factoryExecutions: 0,
+ runHost(require) {
+ state.hostExport = require(43917);
+ state.store = state.hostExport.createInstance({ name: 'chart_futures' });
+ state.snapshotBeforeHostReturns = preflight.snapshot();
+ },
+ };
+ window.__STORAGE_STARTUP__ = state;
+ window.__STORAGE_NATIVE__.reset();
+ });
+}
+
+async function readCaptureCleanup(page) {
+ return page.evaluate(() => {
+ const state = window.__STORAGE_STARTUP__;
+ const push = state.queue.push;
+ state.capture.stop();
+ let laterHostExport;
+ state.queue.push([['storage-lab-after-capture'], {}, require => { laterHostExport = require(43917); }]);
+ const descriptor = Object.getOwnPropertyDescriptor(state.queue, 'push');
+ return {
+ sameExport: laterHostExport === state.hostExport,
+ samePush: state.queue.push === push,
+ ordinaryPush: Object.hasOwn(descriptor, 'value') && descriptor.writable && descriptor.configurable && descriptor.enumerable,
+ factoryRestored: state.require.m[43917] === state.originalFactory,
+ queuedFactoryRestored: state.queue.find(chunk => Object.hasOwn(chunk[1], '43917'))[1][43917] === state.originalFactory,
+ factoryExecutions: state.factoryExecutions,
+ captures: state.captures,
+ };
+ });
+}
+
+for (const order of ['runtime then module', 'module then runtime']) {
+ test(`user captures chart storage before the first host save with ${order}`, async ({ page }) => {
+ // Given capture observes an empty global queue before either real host fixture loads.
+ await openEmptyStorageLab(page);
+ await installEarlyCapture(page);
+
+ // When the real runtime consumes the library chunk and its host requires storage in the same stack.
+ if (order === 'runtime then module') {
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ await registerStorageChunk(page);
+ } else {
+ await registerStorageChunk(page);
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ }
+ const result = await page.evaluate(async () => {
+ const state = window.__STORAGE_STARTUP__;
+ const persisted = state.saved ? await state.saved : null;
+ return {
+ hostResult: state.hostResult, hostError: state.hostError,
+ captures: state.captures, factoryExecutions: state.factoryExecutions,
+ completion: state.completion, persisted,
+ };
+ });
+
+ // Then the first host save already uses the adapter and the actual library factory executes once.
+ expect(result).toEqual({
+ hostResult: { captures: 1, acceptedBeforeReturn: 1, exportMatches: true },
+ hostError: null, captures: 1, factoryExecutions: 1,
+ completion: 'fulfilled', persisted: { interval: '15' },
+ });
+
+ // When later host work uses the restored queue after successful capture is stopped again.
+ const cleanup = await readCaptureCleanup(page);
+
+ // Then no observer accessor or wrapped factory survives and require reuses the existing library export.
+ expect(cleanup).toEqual({
+ sameExport: true, samePush: true, ordinaryPush: true,
+ factoryRestored: true, queuedFactoryRestored: true, factoryExecutions: 1, captures: 1,
+ });
+ });
+}
+
+test('user captures the first chart save when the global chunk queue does not exist yet', async ({ page }) => {
+ // Given the empty document has not created a global runtime queue.
+ await openEmptyStorageLab(page);
+ expect(await page.evaluate(() => Object.hasOwn(self, 'webpackChunkfutures_trade_ui'))).toBe(false);
+ await installEarlyCapture(page, { existingQueue: false });
+
+ // When the runtime initializes its queue and the first module chunk immediately requires chart storage.
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ await registerStorageChunk(page);
+ const result = await page.evaluate(async () => {
+ const state = window.__STORAGE_STARTUP__;
+ return { hostResult: state.hostResult, saved: await state.saved, completion: state.completion };
+ });
+
+ // Then bootstrap-created registration still captures the exact export before the first save returns.
+ expect(result).toEqual({
+ hostResult: { captures: 1, acceptedBeforeReturn: 1, exportMatches: true },
+ saved: { interval: '15' }, completion: 'fulfilled',
+ });
+});
+
+for (const stage of ['before scripts', 'before runtime consumes module', 'after runtime registers module']) {
+ test(`user stops startup observation ${stage} and keeps native host storage available`, async ({ page }) => {
+ // Given capture has not executed the actual library factory at the selected host startup boundary.
+ await openEmptyStorageLab(page);
+ await installEarlyCapture(page);
+ if (stage !== 'before scripts') await registerStorageChunk(page, { requireStorage: false });
+ if (stage === 'after runtime registers module') {
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ }
+ expect(await page.evaluate(() => window.__STORAGE_STARTUP__.factoryExecutions)).toBe(0);
+
+ // When observation stops and the host completes startup before its first storage use.
+ await page.evaluate(() => window.__STORAGE_STARTUP__.capture.stop());
+ if (stage !== 'after runtime registers module') {
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ }
+ if (stage === 'before scripts') await registerStorageChunk(page, { requireStorage: false });
+ const result = await page.evaluate(async () => {
+ const state = window.__STORAGE_STARTUP__;
+ state.runHost(state.require);
+ return {
+ completion: state.completion, captureError: state.captureError,
+ captures: state.captures, hostError: state.hostError,
+ hostResult: state.hostResult, saved: await state.saved,
+ };
+ });
+ const cleanup = await readCaptureCleanup(page);
+
+ // Then only capture rejects while the restored factory executes exactly once and persists the native host save.
+ expect(result.completion).toBe('rejected');
+ expect(result.captureError).toMatch(/stop.*before.*execution/i);
+ expect(result.captures).toBe(0);
+ expect(result.hostError).toBe(null);
+ expect(result.hostResult).toEqual({ captures: 0, acceptedBeforeReturn: 0, exportMatches: false });
+ expect(result.saved).toEqual({ interval: '15' });
+ expect(cleanup).toEqual({
+ sameExport: true, samePush: true, ordinaryPush: true,
+ factoryRestored: true, queuedFactoryRestored: true, factoryExecutions: 1, captures: 0,
+ });
+ });
+}
+
+for (const stage of ['runtime starts', 'module registers']) {
+ test(`user receives an explicit late-bootstrap refusal after the ${stage}`, async ({ page }) => {
+ // Given one real fixture has started before bootstrap can observe a native empty queue.
+ await openEmptyStorageLab(page);
+ const first = stage === 'runtime starts' ? 'webpack-runtime.js' : 'localforage-module.js';
+ const second = stage === 'runtime starts' ? 'localforage-module.js' : 'webpack-runtime.js';
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/${first}` });
+
+ // When bootstrap attempts late installation and the host then completes its unmodified startup.
+ const rejection = await page.evaluate(async () => {
+ const { observeChartStorageBootstrap } = await import('/bootstrap.js');
+ try {
+ const capture = observeChartStorageBootstrap();
+ capture.captured.catch(error => { window.__LATE_CAPTURE_ERROR__ = error.message; });
+ capture.stop();
+ return null;
+ } catch (error) {
+ return error.message;
+ }
+ });
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/${second}` });
+ await initializeStorageLab(page, { install: false });
+ const identity = await page.evaluate(() => window.__STORAGE_LAB__.cacheIdentity());
+
+ // Then refusal is immediate and the original runtime still executes and caches localForage once.
+ expect(rejection).toMatch(/late|before|already|native|empty|runtime/i);
+ expect(identity).toEqual({ sameExport: true, factoryExecutions: 1 });
+ });
+}
+
+test('user retains a working host module when the startup capture callback throws', async ({ page }) => {
+ // Given the observer callback will fail after the real library factory creates its export.
+ await openEmptyStorageLab(page);
+ await installEarlyCapture(page, { callbackFails: true });
+
+ // When the real runtime registers and requires storage in the same host stack.
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ await registerStorageChunk(page);
+ const result = await page.evaluate(async () => {
+ const state = window.__STORAGE_STARTUP__;
+ return {
+ saved: await state.saved, hostError: state.hostError,
+ completion: state.completion, captureError: state.captureError,
+ };
+ });
+ const cleanup = await readCaptureCleanup(page);
+
+ // Then only the observer rejects and the host retains its successful save and original cache identity.
+ expect(result).toEqual({
+ saved: { interval: '15' }, hostError: null,
+ completion: 'rejected', captureError: 'Startup capture callback failed',
+ });
+ expect(cleanup).toEqual({
+ sameExport: true, samePush: true, ordinaryPush: true,
+ factoryRestored: true, queuedFactoryRestored: true, factoryExecutions: 1, captures: 1,
+ });
+});
+
+test('user retains a queued host module when its null runtime callback ends observation', async ({ page }) => {
+ // Given bootstrap creates a previously absent queue before a host chunk with a valid falsy runtime callback.
+ await openEmptyStorageLab(page);
+ await page.evaluate(async () => {
+ const { observeChartStorageBootstrap } = await import('/bootstrap.js');
+ const observer = observeChartStorageBootstrap();
+ const state = { queue: self.webpackChunkfutures_trade_ui, factoryExecutions: 0, outcome: 'pending' };
+ state.originalFactory = module => {
+ state.factoryExecutions += 1;
+ module.exports = { hostValue: 'retained' };
+ };
+ state.chunk = [['chart-storage-null-runtime'], { 43917: state.originalFactory }, null];
+ observer.captured.then(
+ () => { state.outcome = 'fulfilled'; },
+ error => { state.outcome = 'rejected'; state.error = error.message; },
+ );
+ window.__STORAGE_NULL_RUNTIME__ = state;
+ });
+
+ // When registration ends observation before the real runtime consumes the exact original queue.
+ await page.evaluate(() => {
+ const state = window.__STORAGE_NULL_RUNTIME__;
+ state.queue.push(state.chunk);
+ state.sameQueueAfterAppend = self.webpackChunkfutures_trade_ui === state.queue;
+ state.sameChunkAfterAppend = state.queue[0] === state.chunk;
+ });
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ const result = await page.evaluate(() => {
+ const state = window.__STORAGE_NULL_RUNTIME__;
+ let runtimeRequire;
+ self.webpackChunkfutures_trade_ui.push([['chart-storage-null-host'], {}, require => { runtimeRequire = require; }]);
+ const factoryRetained = runtimeRequire.m[43917] === state.originalFactory;
+ const value = factoryRetained ? runtimeRequire(43917) : null;
+ return {
+ outcome: state.outcome, error: state.error,
+ sameQueueAfterAppend: state.sameQueueAfterAppend,
+ sameChunkAfterAppend: state.sameChunkAfterAppend,
+ sameQueueAfterRuntime: self.webpackChunkfutures_trade_ui === state.queue,
+ factoryRetained, factoryExecutions: state.factoryExecutions, value,
+ };
+ });
+
+ // Then the observer rejects its unsupported shape while the unmodified host factory remains registered and callable.
+ expect(result.outcome).toBe('rejected');
+ expect(result.error).toMatch(/runtime callback/i);
+ expect(result.sameQueueAfterAppend).toBe(true);
+ expect(result.sameChunkAfterAppend).toBe(true);
+ expect(result.sameQueueAfterRuntime).toBe(true);
+ expect(result.factoryRetained).toBe(true);
+ expect(result.factoryExecutions).toBe(1);
+ expect(result.value).toEqual({ hostValue: 'retained' });
+});
+
+test('user saves nested chart JSON created by a same-origin iframe without changing its value', async ({ page }) => {
+ // Given the parent owns a real storage facade and an iframe creates the complete chart value in its own realm.
+ await openStorageLab(page);
+ await page.evaluate(() => {
+ const frame = document.createElement('iframe');
+ document.body.appendChild(frame);
+ const json = '{"symbol":"BTCUSDT","layout":{"panes":[{"id":1,"studies":[{"name":"volume","inputs":[20,true,null]}]}]},"__proto__":{"chart":"own-data"}}';
+ window.__STORAGE_FRAME_CHART__ = {
+ frame, value: frame.contentWindow.JSON.parse(json), expected: JSON.parse(json),
+ };
+ });
+
+ // When the iframe value is submitted through create and then read with the parent's native localForage instance.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const { frame, value, expected } = window.__STORAGE_FRAME_CHART__;
+ const foreignPrototype = Object.getPrototypeOf(value);
+ const ownPrototype = Object.getPrototypeOf(value.layout);
+ const outcomes = await Promise.allSettled([lab.create().setItem('#TV_SYMBOL-IFRAME', value)]);
+ const outcome = outcomes[0];
+ return {
+ differentRealm: foreignPrototype === frame.contentWindow.Object.prototype && foreignPrototype !== Object.prototype,
+ status: outcome.status,
+ rejection: outcome.status === 'rejected' ? outcome.reason.message : null,
+ returned: outcome.status === 'fulfilled' ? outcome.value : null,
+ persisted: await lab.native.getItem('#TV_SYMBOL-IFRAME'), expected,
+ original: value,
+ prototypesPreserved: Object.getPrototypeOf(value) === foreignPrototype && Object.getPrototypeOf(value.layout) === ownPrototype,
+ dangerousKeyPreserved: Object.hasOwn(value, '__proto__'),
+ };
+ });
+
+ // Then the real IndexedDB record preserves all nested JSON and the input keeps its original realm and own properties.
+ expect(result.differentRealm).toBe(true);
+ expect(result.status).toBe('fulfilled');
+ expect(result.rejection).toBe(null);
+ expect(result.returned).toEqual(result.expected);
+ expect(result.persisted).toEqual(result.expected);
+ expect(result.original).toEqual(result.expected);
+ expect(result.prototypesPreserved).toBe(true);
+ expect(result.dangerousKeyPreserved).toBe(true);
+});
+
+test('user keeps the original chart when iframe Date and class values are refused', async ({ page }) => {
+ // Given the real database contains a valid chart and iframe values include non-JSON object types.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const frame = document.createElement('iframe');
+ document.body.appendChild(frame);
+ const realm = frame.contentWindow;
+ const ChartModel = realm.eval('(class ChartModel { constructor() { this.interval = "30"; } })');
+ const ObjectNamedClass = realm.eval('(class Object { constructor() { this.interval = "60"; } })');
+ const forgedPrototype = realm.Object.create(null);
+ realm.Object.defineProperty(forgedPrototype, 'constructor', { value: realm.Object });
+ const forgedPlainObject = realm.Object.create(forgedPrototype);
+ forgedPlainObject.interval = '240';
+ const values = [new realm.Date(0), new ChartModel(), new ObjectNamedClass(), new realm.Map([['interval', '15']]), forgedPlainObject];
+ lab.invalidCharts = values.map(value => ({ layout: value }));
+ lab.invalidChartSnapshots = lab.invalidCharts.map(value => JSON.stringify(value));
+ lab.invalidChartPrototypes = values.map(value => Object.getPrototypeOf(value));
+ await lab.native.setItem('#TV_SYMBOL-PRESERVED', { interval: '15', studies: ['volume'] });
+ });
+
+ // When each invalid nested value attempts to replace the same existing chart through the public facade.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const store = lab.create();
+ const outcomes = await Promise.allSettled(lab.invalidCharts.map(value => store.setItem('#TV_SYMBOL-PRESERVED', value)));
+ return {
+ outcomes: outcomes.map(outcome => ({
+ status: outcome.status, message: outcome.status === 'rejected' ? outcome.reason.message : null,
+ })),
+ persisted: await lab.native.getItem('#TV_SYMBOL-PRESERVED'),
+ originalValuesPreserved: lab.invalidCharts.every((value, index) => (
+ JSON.stringify(value) === lab.invalidChartSnapshots[index]
+ && Object.getPrototypeOf(value.layout) === lab.invalidChartPrototypes[index]
+ )),
+ };
+ });
+
+ // Then all unsupported values reject explicitly and neither the stored chart nor the caller's objects change.
+ expect(result.outcomes.map(outcome => outcome.status)).toEqual(['rejected', 'rejected', 'rejected', 'rejected', 'rejected']);
+ for (const outcome of result.outcomes) expect(outcome.message).toMatch(/plain JSON/i);
+ expect(result.persisted).toEqual({ interval: '15', studies: ['volume'] });
+ expect(result.originalValuesPreserved).toBe(true);
+});
+
+test('user observes native chart instance creation through preflight without adding storage work', async ({ page }) => {
+ // Given preflight starts before the real runtime without installing a storage adapter.
+ await openEmptyStorageLab(page);
+ await installStoragePreflight(page);
+
+ // When the first real module chunk creates a chart instance in the same host stack.
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ await registerStorageChunk(page);
+ const observed = await page.evaluate(() => {
+ const state = window.__STORAGE_STARTUP__;
+ return { snapshot: state.snapshotBeforeHostReturns, metrics: window.__STORAGE_NATIVE__.snapshot() };
+ });
+
+ // Then preflight records only the native creation and does not open a database or submit storage operations.
+ expect(observed.snapshot.outcome).toBe('captured');
+ expect(observed.snapshot.active).toBe(true);
+ expect(observed.snapshot.instances).toEqual({ chart_futures: 1, chart_delivery: 0 });
+ expect(observed.snapshot.captureAtMs).toBeGreaterThanOrEqual(0);
+ expect(observed.snapshot.firstInstances).toHaveLength(1);
+ expect(observed.snapshot.firstInstances[0]).toEqual({
+ name: 'chart_futures', storeName: 'keyvaluepairs', atMs: expect.any(Number),
+ });
+ expect(observed.snapshot.firstInstances[0].atMs).toBeGreaterThanOrEqual(observed.snapshot.captureAtMs);
+ expect(observed.metrics).toMatchObject({ opens: 0, transactions: 0, puts: 0, gets: 0, pending: 0 });
+
+ // When the host initializes its native instances, stops observation, and uses the retained original instance.
+ const result = await page.evaluate(async () => {
+ const state = window.__STORAGE_STARTUP__;
+ const control = state.nativeFactoryBeforeCapture.call(state.hostExport, { name: 'chart_futures' });
+ await Promise.all([state.store.ready(), control.ready()]);
+ const methodsAreNative = state.store.getItem === control.getItem && state.store.setItem === control.setItem;
+ const getItem = state.store.getItem;
+ const setItem = state.store.setItem;
+ window.__STORAGE_NATIVE__.reset();
+ state.preflight.stop();
+ const metricsAfterStop = window.__STORAGE_NATIVE__.snapshot();
+ const written = await state.store.setItem('#TV_SYMBOL-PREFLIGHT', { interval: '15', studies: ['volume'] });
+ const read = await state.store.getItem('#TV_SYMBOL-PREFLIGHT');
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ return {
+ methodsAreNative,
+ methodsRetained: state.store.getItem === getItem && state.store.setItem === setItem,
+ factoryRestored: state.hostExport.createInstance === state.nativeFactoryBeforeCapture,
+ factoryExecutions: state.factoryExecutions,
+ sameExport: state.require(43917) === state.hostExport,
+ metricsAfterStop, metrics, written, read, snapshot: state.preflight.snapshot(),
+ };
+ });
+
+ // Then stopping restores the exact factory while the unwrapped retained instance performs one native write and one read.
+ expect(result.methodsAreNative).toBe(true);
+ expect(result.methodsRetained).toBe(true);
+ expect(result.factoryRestored).toBe(true);
+ expect(result.factoryExecutions).toBe(1);
+ expect(result.sameExport).toBe(true);
+ expect(result.metricsAfterStop).toMatchObject({ opens: 0, transactions: 0, puts: 0, gets: 0, pending: 0 });
+ expect(result.metrics).toMatchObject({ opens: 0, transactions: 2, puts: 1, gets: 1 });
+ expect(result.written).toEqual({ interval: '15', studies: ['volume'] });
+ expect(result.read).toEqual(result.written);
+ expect(result.snapshot.active).toBe(false);
+ expect(result.snapshot.instances).toEqual({ chart_futures: 1, chart_delivery: 0 });
+});
+
+test('user has preflight restore the native factory automatically after thirty seconds', async ({ page }) => {
+ // Given a paused virtual clock owns the preflight lifetime before any real host fixture loads.
+ await installScenarioClock(page);
+ await openEmptyStorageLab(page);
+ await pauseScenarioClock(page);
+ await installStoragePreflight(page);
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ await registerStorageChunk(page);
+
+ // When the clock reaches the instant immediately before the bounded observation ends.
+ await page.clock.runFor(29_999);
+ const beforeDeadline = await page.evaluate(() => {
+ const state = window.__STORAGE_STARTUP__;
+ return {
+ active: state.preflight.snapshot().active,
+ factoryRestored: state.hostExport.createInstance === state.nativeFactoryBeforeCapture,
+ };
+ });
+
+ // Then the observer remains active throughout the authorized observation window.
+ expect(beforeDeadline).toEqual({ active: true, factoryRestored: false });
+
+ // When the deadline arrives and the host creates another native instance after automatic cleanup.
+ await page.clock.runFor(1);
+ const afterDeadline = await page.evaluate(() => {
+ const state = window.__STORAGE_STARTUP__;
+ state.hostExport.createInstance({ name: 'chart_delivery' });
+ const descriptor = Object.getOwnPropertyDescriptor(state.queue, 'push');
+ return {
+ snapshot: state.preflight.snapshot(),
+ factoryRestored: state.hostExport.createInstance === state.nativeFactoryBeforeCapture,
+ ordinaryPush: Object.hasOwn(descriptor, 'value') && descriptor.writable && descriptor.configurable,
+ moduleFactoryRestored: state.require.m[43917] === state.originalFactory,
+ factoryExecutions: state.factoryExecutions,
+ metrics: window.__STORAGE_NATIVE__.snapshot(),
+ };
+ });
+
+ // Then all observation hooks are restored at thirty seconds and later native creation is neither counted nor accessed.
+ expect(afterDeadline.snapshot).toMatchObject({
+ outcome: 'captured', active: false, instances: { chart_futures: 1, chart_delivery: 0 },
+ });
+ expect(afterDeadline.snapshot.firstInstances).toHaveLength(1);
+ expect(afterDeadline.factoryRestored).toBe(true);
+ expect(afterDeadline.ordinaryPush).toBe(true);
+ expect(afterDeadline.moduleFactoryRestored).toBe(true);
+ expect(afterDeadline.factoryExecutions).toBe(1);
+ expect(afterDeadline.metrics).toMatchObject({ opens: 0, transactions: 0, puts: 0, gets: 0, pending: 0 });
+});
diff --git a/e2e/binance-orderbook/specs/chart-storage-lifecycle.pw.js b/e2e/binance-orderbook/specs/chart-storage-lifecycle.pw.js
new file mode 100644
index 0000000..42b7faa
--- /dev/null
+++ b/e2e/binance-orderbook/specs/chart-storage-lifecycle.pw.js
@@ -0,0 +1,586 @@
+import { test, expect } from '../test.js';
+import { openStorageLab } from '../helpers/chart-storage-lab.js';
+
+/** Real request events retain the write lock until the scenario releases it. */
+async function holdChartWriteLock(page) {
+ await page.evaluate(() => {
+ const transaction = window.__STORAGE_LAB__.native._dbInfo.db.transaction('keyvaluepairs', 'readwrite');
+ const store = transaction.objectStore('keyvaluepairs');
+ const pulses = [];
+ const lock = {
+ released: false,
+ progress: () => new Promise(resolve => pulses.push(resolve)),
+ completion: new Promise((resolve, reject) => {
+ transaction.addEventListener('complete', () => resolve('complete'), { once: true });
+ transaction.addEventListener('abort', () => reject(transaction.error), { once: true });
+ }),
+ };
+ function hold() {
+ const request = store.get('#TV_SYMBOL-STOP');
+ request.onsuccess = () => {
+ for (const resolve of pulses.splice(0)) resolve();
+ if (!lock.released) hold();
+ };
+ }
+ window.__STORAGE_STOP_LOCK__ = lock;
+ hold();
+ });
+ await page.evaluate(() => window.__STORAGE_STOP_LOCK__.progress());
+}
+
+async function releaseChartWriteLock(page) {
+ await page.evaluate(async () => {
+ const lock = window.__STORAGE_STOP_LOCK__;
+ lock.released = true;
+ await lock.completion;
+ });
+}
+
+test('user preserves accepted writes before retained and newly created stores resume native saves', async ({ page, browser }) => {
+ // Given an independent native baseline and an actual lock blocking the accepted chart batch.
+ const nativeContext = await browser.newContext();
+ let nativeBaseline;
+ try {
+ const nativePage = await nativeContext.newPage();
+ await openStorageLab(nativePage, { install: false });
+ nativeBaseline = await nativePage.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await lab.native.setItem('#TV_SYMBOL-STOP', 1);
+ const retained = lab.create();
+ await retained.ready();
+ const fresh = lab.create();
+ const callbacks = [];
+ const callback = id => (error, value) => callbacks.push({ id, error: error === null, value });
+ const tail = await Promise.allSettled([
+ retained.getItem('#TV_SYMBOL-STOP', callback('retained-read')),
+ retained.setItem('#TV_SYMBOL-RETAINED', 2, callback('retained-write')),
+ fresh.setItem('#TV_SYMBOL-STOP', 3, callback('fresh-write')),
+ fresh.getItem('#TV_SYMBOL-STOP', callback('fresh-read')),
+ ]);
+ return { tail, callbacks, finalValue: await lab.native.getItem('#TV_SYMBOL-STOP') };
+ });
+ } finally {
+ await nativeContext.close();
+ }
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await lab.native.setItem('#TV_SYMBOL-STOP', 0);
+ lab.store = lab.create();
+ await lab.store.ready();
+ });
+ await holdChartWriteLock(page);
+ await page.evaluate(() => {
+ const lab = window.__STORAGE_LAB__;
+ window.__STORAGE_NATIVE__.reset();
+ lab.accepted = Promise.all([
+ lab.store.setItem('#TV_SYMBOL-STOP', 1), lab.store.getItem('#TV_SYMBOL-STOP'),
+ ]);
+ });
+ await expect.poll(() => page.evaluate(() => window.__STORAGE_NATIVE__.snapshot().pending)).toBe(1);
+
+ // When stop and calls through both retained and new stores occur in the same stack.
+ let beforeRelease;
+ try {
+ beforeRelease = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.stopResolved = false;
+ lab.stopping = lab.controller.stop();
+ lab.stopping.then(() => { lab.stopResolved = true; });
+ lab.callbacks = [];
+ const callback = id => (error, value) => lab.callbacks.push({ id, error: error === null, value });
+ const fresh = lab.create();
+ lab.nativeTail = Promise.allSettled([
+ lab.store.getItem('#TV_SYMBOL-STOP', callback('retained-read')),
+ lab.store.setItem('#TV_SYMBOL-RETAINED', 2, callback('retained-write')),
+ fresh.setItem('#TV_SYMBOL-STOP', 3, callback('fresh-write')),
+ fresh.getItem('#TV_SYMBOL-STOP', callback('fresh-read')),
+ ]);
+ lab.readyCallbacks = [];
+ lab.nativeReady = lab.store.ready((...args) => { lab.readyCallbacks.push(args); });
+ let configError;
+ try {
+ lab.store.config({ description: 'Cannot mutate during drain' });
+ } catch (error) {
+ configError = error.message;
+ }
+ await window.__STORAGE_STOP_LOCK__.progress();
+ return {
+ stopResolved: lab.stopResolved,
+ restoredFactory: lab.localforage.createInstance === lab.nativeCreateInstance,
+ callbacks: [...lab.callbacks],
+ acceptedOperations: lab.controller.getStats().acceptedOperations,
+ pendingOperations: lab.controller.getStats().pendingOperations,
+ transactions: window.__STORAGE_NATIVE__.snapshot().transactions,
+ readyCallbacks: [...lab.readyCallbacks],
+ configName: lab.store.config('name'), driver: lab.store.driver(), configError,
+ };
+ });
+ } finally {
+ await releaseChartWriteLock(page);
+ }
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const accepted = await lab.accepted;
+ await lab.stopping;
+ const tail = await lab.nativeTail;
+ await lab.nativeReady;
+ return {
+ accepted, tail, callbacks: lab.callbacks, stats: lab.controller.getStats(),
+ readyCallbacks: lab.readyCallbacks,
+ restoredFactory: lab.localforage.createInstance === lab.nativeCreateInstance,
+ finalValue: await lab.native.getItem('#TV_SYMBOL-STOP'),
+ retainedValue: await lab.native.getItem('#TV_SYMBOL-RETAINED'),
+ };
+ });
+
+ // Then only accepted work crosses the lock and every later call completes after its drain fence.
+ expect(beforeRelease).toEqual({
+ stopResolved: false, restoredFactory: false, callbacks: [],
+ acceptedOperations: 2, pendingOperations: 2, transactions: 1,
+ readyCallbacks: [], configName: 'chart_futures', driver: 'asyncStorage',
+ configError: 'Managed chart configuration is immutable',
+ });
+ expect(result.accepted).toEqual([1, 1]);
+ // Native setItem prepares its value in an extra Promise stage, so a same-stack read can precede it.
+ expect(nativeBaseline.tail).toEqual([1, 2, 3, 1].map(value => ({ status: 'fulfilled', value })));
+ expect(nativeBaseline.finalValue).toBe(3);
+ expect(result.tail).toEqual(nativeBaseline.tail);
+ expect(result.callbacks).toEqual(nativeBaseline.callbacks);
+ expect(result.readyCallbacks).toEqual([[undefined]]);
+ expect(result.finalValue).toBe(3);
+ expect(result.retainedValue).toBe(2);
+ expect(result.restoredFactory).toBe(true);
+ expect(result.stats).toMatchObject({ acceptedOperations: 2, rejectedOperations: 0, pendingOperations: 0, pendingBytes: 0 });
+});
+
+test('user preserves a replacement factory when ownership changes while accepted writes drain', async ({ page }) => {
+ // Given an accepted write waits behind a real native transaction lock.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await lab.native.setItem('#TV_SYMBOL-OWNERSHIP', 0);
+ lab.store = lab.create();
+ await lab.store.ready();
+ });
+ await holdChartWriteLock(page);
+ await page.evaluate(() => {
+ const lab = window.__STORAGE_LAB__;
+ window.__STORAGE_NATIVE__.reset();
+ lab.accepted = lab.store.setItem('#TV_SYMBOL-OWNERSHIP', 1);
+ });
+ await expect.poll(() => page.evaluate(() => window.__STORAGE_NATIVE__.snapshot().pending)).toBe(1);
+
+ // When another owner replaces the factory after stop starts and before the write commits.
+ let whileLocked;
+ try {
+ whileLocked = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.stopSettled = false;
+ lab.stopped = Promise.allSettled([lab.controller.stop()]).then(outcomes => {
+ lab.stopSettled = true;
+ return outcomes;
+ });
+ lab.replacementFactory = function (...args) {
+ return Reflect.apply(lab.nativeCreateInstance, lab.localforage, args);
+ };
+ Object.defineProperty(lab.localforage, 'createInstance', {
+ value: lab.replacementFactory, enumerable: false, writable: true, configurable: true,
+ });
+ await window.__STORAGE_STOP_LOCK__.progress();
+ return { stopSettled: lab.stopSettled, pendingOperations: lab.controller.getStats().pendingOperations };
+ });
+ } finally {
+ await releaseChartWriteLock(page);
+ }
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const accepted = await lab.accepted;
+ const [stopped] = await lab.stopped;
+ const descriptor = Object.getOwnPropertyDescriptor(lab.localforage, 'createInstance');
+ return {
+ accepted, stopStatus: stopped.status, stopError: stopped.reason.message,
+ replacementPreserved: descriptor.value === lab.replacementFactory,
+ descriptor: { enumerable: descriptor.enumerable, writable: descriptor.writable, configurable: descriptor.configurable },
+ persisted: await lab.native.getItem('#TV_SYMBOL-OWNERSHIP'), stats: lab.controller.getStats(),
+ };
+ });
+
+ // Then the accepted write commits and stop refuses to overwrite the other owner's exact descriptor.
+ expect(whileLocked).toEqual({ stopSettled: false, pendingOperations: 1 });
+ expect(result.accepted).toBe(1);
+ expect(result.persisted).toBe(1);
+ expect(result.stopStatus).toBe('rejected');
+ expect(result.stopError).toMatch(/ownership/i);
+ expect(result.replacementPreserved).toBe(true);
+ expect(result.descriptor).toEqual({ enumerable: false, writable: true, configurable: true });
+ expect(result.stats).toMatchObject({ acceptedOperations: 1, failedOperations: 0, pendingOperations: 0, pendingBytes: 0 });
+});
+
+test('user drains saves awaiting a blocked native database upgrade before restoring the factory', async ({ page }) => {
+ // Given an external version-one connection will block the real library's version-two initialization.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const database = await new Promise((resolve, reject) => {
+ const request = indexedDB.open('chart_futures', 1);
+ request.onupgradeneeded = () => request.result.createObjectStore('keyvaluepairs');
+ request.onsuccess = () => resolve(request.result);
+ request.onerror = () => reject(request.error);
+ });
+ const nativeOpen = IDBFactory.prototype.open;
+ let blocked;
+ let opened;
+ const gate = {
+ database, nativeOpen,
+ blocked: new Promise(resolve => { blocked = resolve; }),
+ opened: new Promise(resolve => { opened = resolve; }),
+ };
+ /** Observe the native upgrade request without replacing its initialization or event behavior. */
+ IDBFactory.prototype.open = function (...args) {
+ const request = Reflect.apply(nativeOpen, this, args);
+ if (args[0] === 'chart_futures' && args[1] === 2) {
+ request.addEventListener('blocked', event => {
+ blocked({ oldVersion: event.oldVersion, newVersion: event.newVersion });
+ }, { once: true });
+ request.addEventListener('success', () => opened(request.result.version), { once: true });
+ }
+ return request;
+ };
+ window.__STORAGE_INITIALIZATION_GATE__ = gate;
+ window.__STORAGE_NATIVE__.reset();
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create('chart_futures', { version: 2 });
+ lab.accepted = Promise.all([
+ lab.store.setItem('#TV_SYMBOL-INITIALIZING', 1), lab.store.getItem('#TV_SYMBOL-INITIALIZING'),
+ ]);
+ });
+
+ // When stop and new managed calls occur while IndexedDB explicitly reports the initialization blocked.
+ let blockedState;
+ try {
+ blockedState = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const versionEvent = await window.__STORAGE_INITIALIZATION_GATE__.blocked;
+ lab.stopResolved = false;
+ lab.stopping = lab.controller.stop();
+ lab.stopping.then(() => { lab.stopResolved = true; });
+ lab.callbacks = [];
+ const callback = id => (error, value) => lab.callbacks.push({ id, error: error === null, value });
+ const fresh = lab.create('chart_futures', { version: 2 });
+ lab.tail = Promise.all([
+ lab.store.getItem('#TV_SYMBOL-INITIALIZING', callback('retained-read')),
+ fresh.setItem('#TV_SYMBOL-INITIALIZING', 3, callback('fresh-write')),
+ ]);
+ await Promise.resolve();
+ return {
+ versionEvent, stopResolved: lab.stopResolved, callbacks: [...lab.callbacks],
+ restoredFactory: lab.localforage.createInstance === lab.nativeCreateInstance,
+ acceptedOperations: lab.controller.getStats().acceptedOperations,
+ pendingOperations: lab.controller.getStats().pendingOperations,
+ transactions: window.__STORAGE_NATIVE__.snapshot().transactions,
+ };
+ });
+ } finally {
+ await page.evaluate(async () => {
+ const gate = window.__STORAGE_INITIALIZATION_GATE__;
+ IDBFactory.prototype.open = gate.nativeOpen;
+ gate.database.close();
+ await gate.opened;
+ });
+ }
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const accepted = await lab.accepted;
+ await lab.stopping;
+ const tail = await lab.tail;
+ return {
+ accepted, tail, callbacks: lab.callbacks,
+ restoredFactory: lab.localforage.createInstance === lab.nativeCreateInstance,
+ persisted: await lab.store.getItem('#TV_SYMBOL-INITIALIZING'), stats: lab.controller.getStats(),
+ };
+ });
+
+ // Then factory restoration waits for initialization and accepted commits before native calls resume.
+ expect(blockedState).toEqual({
+ versionEvent: { oldVersion: 1, newVersion: 2 },
+ stopResolved: false, callbacks: [], restoredFactory: false,
+ acceptedOperations: 2, pendingOperations: 2, transactions: 0,
+ });
+ expect(result.accepted).toEqual([1, 1]);
+ expect(result.tail).toEqual([1, 3]);
+ expect(result.callbacks).toEqual([
+ { id: 'retained-read', error: true, value: 1 }, { id: 'fresh-write', error: true, value: 3 },
+ ]);
+ expect(result.restoredFactory).toBe(true);
+ expect(result.persisted).toBe(3);
+ expect(result.stats).toMatchObject({ acceptedOperations: 2, pendingOperations: 0, pendingBytes: 0 });
+});
+
+test('user rejects a save when synchronous value inspection stops admission before enqueue', async ({ page }) => {
+ // Given a ready store has no accepted work when a transparent Proxy invokes stop during inspection.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When value validation synchronously stops the controller before the save can enter its queue.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ let stopping;
+ let inspections = 0;
+ const value = new Proxy({ revision: 1 }, {
+ getPrototypeOf(target) {
+ inspections += 1;
+ stopping = lab.controller.stop();
+ return Reflect.getPrototypeOf(target);
+ },
+ });
+ const callbacks = [];
+ const [outcome] = await Promise.allSettled([
+ lab.store.setItem('#TV_SYMBOL-ADMISSION', value, (error, saved) => {
+ callbacks.push({ message: error?.message, value: saved });
+ }),
+ ]);
+ await stopping;
+ return {
+ status: outcome.status, message: outcome.reason?.message, callbacks, inspections,
+ restoredFactory: lab.localforage.createInstance === lab.nativeCreateInstance,
+ stats: lab.controller.getStats(), metrics: window.__STORAGE_NATIVE__.snapshot(),
+ persisted: await lab.native.getItem('#TV_SYMBOL-ADMISSION'),
+ };
+ });
+
+ // Then the stopped admission rejects once without queuing or replaying the inspected value.
+ expect(result.inspections).toBeGreaterThan(0);
+ expect(result.status).toBe('rejected');
+ expect(result.message).toMatch(/stopped|active|admission/i);
+ expect(result.callbacks).toEqual([{ message: result.message, value: undefined }]);
+ expect(result.restoredFactory).toBe(true);
+ expect(result.stats).toMatchObject({ acceptedOperations: 0, rejectedOperations: 1, pendingOperations: 0, pendingBytes: 0 });
+ expect(result.metrics).toMatchObject({ puts: 0, transactions: 0 });
+ expect(result.persisted).toBe(null);
+});
+
+test('user can stop from a save callback and continue saving without blocking the remaining accepted batches', async ({ page }) => {
+ // Given each accepted write uses its own real transaction and a ready retained store.
+ await openStorageLab(page, { options: { maxBatchOperations: 1 } });
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When the first callback stops optimization and immediately saves through the retained instance.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const callbacks = [];
+ let stopping;
+ let resumed;
+ const accepted = await Promise.all([1, 2, 3].map(value => lab.store.setItem('#TV_SYMBOL-REENTRANT', value, (error, actual) => {
+ callbacks.push({ phase: 'accepted', error: error === null, value: actual });
+ if (value === 1) {
+ stopping = lab.controller.stop();
+ resumed = Promise.allSettled([
+ lab.store.setItem('#TV_SYMBOL-REENTRANT', 4, (failure, saved) => {
+ callbacks.push({ phase: 'native', error: failure === null, value: saved });
+ }),
+ ]);
+ }
+ })));
+ await stopping;
+ const tail = await resumed;
+ return {
+ accepted, tail, callbacks, stats: lab.controller.getStats(),
+ persisted: await lab.native.getItem('#TV_SYMBOL-REENTRANT'),
+ metrics: window.__STORAGE_NATIVE__.snapshot(),
+ };
+ });
+
+ // Then all accepted callbacks settle and the native continuation saves the final revision once.
+ expect(result.accepted).toEqual([1, 2, 3]);
+ expect(result.tail).toEqual([{ status: 'fulfilled', value: 4 }]);
+ expect(result.callbacks).toEqual([
+ { phase: 'accepted', error: true, value: 1 },
+ { phase: 'accepted', error: true, value: 2 },
+ { phase: 'accepted', error: true, value: 3 },
+ { phase: 'native', error: true, value: 4 },
+ ]);
+ expect(result.persisted).toBe(4);
+ expect(result.metrics.puts).toBe(4);
+ expect(result.stats).toMatchObject({ acceptedOperations: 3, failedOperations: 0, pendingOperations: 0 });
+});
+
+test('user receives every aborted batch callback and resumes native storage after stop without retrying failed puts', async ({ page }) => {
+ // Given committed chart values and the next real put aborts its native transaction.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await Promise.all(['A', 'B'].map(key => lab.native.setItem(`#TV_SYMBOL-${key}`, 1)));
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ window.__STORAGE_NATIVE__.abortNextPut();
+ });
+
+ // When stop drains an aborting batch and the retained store immediately queues a native continuation.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const callbacks = [];
+ const callback = id => (error, value) => callbacks.push({ id, name: error?.name, value });
+ const accepted = Promise.allSettled([
+ lab.store.setItem('#TV_SYMBOL-A', 2, callback('write-A')),
+ lab.store.setItem('#TV_SYMBOL-B', 2, callback('write-B')),
+ lab.store.getItem('#TV_SYMBOL-A', callback('read-A')),
+ ]);
+ const stopping = lab.controller.stop();
+ const tail = Promise.allSettled([lab.store.setItem('#TV_SYMBOL-NATIVE', 3, callback('native'))]);
+ const outcomes = await accepted;
+ await stopping;
+ const resumed = await tail;
+ return {
+ statuses: outcomes.map(value => value.status), names: outcomes.map(value => value.reason.name),
+ resumed, callbacks, stats: lab.controller.getStats(), metrics: window.__STORAGE_NATIVE__.snapshot(),
+ persisted: await Promise.all(['A', 'B', 'NATIVE'].map(key => lab.native.getItem(`#TV_SYMBOL-${key}`))),
+ };
+ });
+
+ // Then each failed call is notified once and only the successful native continuation changes storage.
+ expect(result.statuses).toEqual(['rejected', 'rejected', 'rejected']);
+ expect(result.names).toEqual(['AbortError', 'AbortError', 'AbortError']);
+ expect(result.resumed).toEqual([{ status: 'fulfilled', value: 3 }]);
+ expect(result.callbacks).toEqual([
+ { id: 'write-A', name: 'AbortError', value: undefined },
+ { id: 'write-B', name: 'AbortError', value: undefined },
+ { id: 'read-A', name: 'AbortError', value: undefined },
+ { id: 'native', name: undefined, value: 3 },
+ ]);
+ expect(result.persisted).toEqual([1, 1, 3]);
+ expect(result.metrics).toMatchObject({ puts: 2, aborted: 1, pending: 0 });
+ expect(result.stats).toMatchObject({ acceptedOperations: 3, failedOperations: 3, pendingOperations: 0, pendingBytes: 0 });
+});
+
+test('user keeps native callbacks configuration and Blob storage through an existing facade after stop', async ({ page }) => {
+ // Given an unused managed facade survives stopping before it initializes a database.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.controller.stop();
+ });
+
+ // When the retained instance configures and uses the original native storage API.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const store = lab.store;
+ const configured = store.config({ description: 'Native storage after stop' });
+ const configuration = store.config();
+ let readyArgs;
+ await store.ready((...args) => { readyArgs = args; });
+ const blob = new Blob(['native chart payload'], { type: 'text/plain' });
+ let savedCallback;
+ const saved = await store.setItem('#TV_SYMBOL-BLOB', blob, (error, value) => {
+ savedCallback = { error: error === null, sameBlob: value === blob };
+ });
+ const restored = await store.getItem('#TV_SYMBOL-BLOB');
+ await store.setItem('native-number', 7);
+ const keys = await store.keys();
+ const length = await store.length();
+ const firstKey = await store.key(0);
+ const driver = await store.getDriver(store.driver());
+ const entries = [];
+ await store.iterate((value, key) => { entries.push({ key, blob: value instanceof Blob }); });
+ await store.removeItem('native-number');
+ const removed = await store.getItem('native-number');
+ await store.clear();
+ return {
+ configured, description: configuration.description, configIdentity: store.config() === configuration,
+ readyArgs, driver: store.driver(), driverMatches: driver._driver === lab.localforage.INDEXEDDB,
+ savedIsBlob: saved === blob, savedCallback,
+ restoredBlob: { type: restored.type, content: await restored.text(), isBlob: restored instanceof Blob },
+ keys, length, firstKey, entries, removed, remaining: await store.keys(), stats: lab.controller.getStats(),
+ };
+ });
+
+ // Then callbacks retain native signatures and unrestricted native values and methods remain usable.
+ expect(result.configured).toBe(true);
+ expect(result.description).toBe('Native storage after stop');
+ expect(result.configIdentity).toBe(true);
+ expect(result.readyArgs).toEqual([undefined]);
+ expect(result.driver).toBe('asyncStorage');
+ expect(result.driverMatches).toBe(true);
+ expect(result.savedIsBlob).toBe(true);
+ expect(result.savedCallback).toEqual({ error: true, sameBlob: true });
+ expect(result.restoredBlob).toEqual({ type: 'text/plain', content: 'native chart payload', isBlob: true });
+ expect(result.keys).toEqual(['#TV_SYMBOL-BLOB', 'native-number']);
+ expect(result.length).toBe(2);
+ expect(result.firstKey).toBe('#TV_SYMBOL-BLOB');
+ expect(result.entries).toEqual([{ key: '#TV_SYMBOL-BLOB', blob: true }, { key: 'native-number', blob: false }]);
+ expect(result.removed).toBe(null);
+ expect(result.remaining).toEqual([]);
+ expect(result.stats).toMatchObject({ acceptedOperations: 0, rejectedOperations: 0, transactions: 0 });
+});
+
+for (const placement of ['inherited', 'own']) {
+ test(`user restores the exact ${placement} factory descriptor and cannot reinstall until reload`, async ({ page }) => {
+ // Given the actual library has either its inherited factory or an explicit own descriptor.
+ await openStorageLab(page, { install: false });
+ await page.evaluate(placement => {
+ const lab = window.__STORAGE_LAB__;
+ if (placement === 'own') {
+ Object.defineProperty(lab.localforage, 'createInstance', {
+ value: lab.nativeCreateInstance, writable: true, enumerable: false, configurable: true,
+ });
+ }
+ lab.originalDescriptor = Object.getOwnPropertyDescriptor(lab.localforage, 'createInstance');
+ }, placement);
+
+ // When the prototype stops repeatedly and a caller attempts to install it again on the same export.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const { installChartStoragePrototype } = await import('/adapter.js');
+ const controller = installChartStoragePrototype(lab.localforage);
+ const retainedFactory = lab.localforage.createInstance;
+ const stopping = controller.stop();
+ const samePendingStop = controller.stop() === stopping;
+ await stopping;
+ const sameCompletedStop = controller.stop() === stopping;
+ const restoredDescriptor = Object.getOwnPropertyDescriptor(lab.localforage, 'createInstance');
+ const descriptorMatches = lab.originalDescriptor === undefined
+ ? restoredDescriptor === undefined
+ : Object.keys(lab.originalDescriptor).every(key => restoredDescriptor[key] === lab.originalDescriptor[key]);
+ const restoredFactory = lab.localforage.createInstance === lab.nativeCreateInstance;
+ let reinstallError;
+ try {
+ const reinstalled = installChartStoragePrototype(lab.localforage);
+ await reinstalled.stop();
+ } catch (error) {
+ reinstallError = error.message;
+ }
+ let retainedFactoryOutcome;
+ try {
+ const fresh = retainedFactory({ name: 'chart_futures', storeName: 'keyvaluepairs', driver: lab.localforage.INDEXEDDB });
+ retainedFactoryOutcome = { status: 'fulfilled', value: await fresh.setItem('#TV_SYMBOL-FACTORY', 5) };
+ } catch (error) {
+ retainedFactoryOutcome = { status: 'rejected', message: error.message };
+ }
+ return {
+ samePendingStop, sameCompletedStop, descriptorMatches, restoredFactory, reinstallError, retainedFactoryOutcome,
+ originallyOwn: lab.originalDescriptor !== undefined, restoredOwn: restoredDescriptor !== undefined,
+ };
+ });
+
+ // Then stop is idempotent, restores property ownership exactly, and retained factories stay native.
+ expect(result.samePendingStop).toBe(true);
+ expect(result.sameCompletedStop).toBe(true);
+ expect(result.descriptorMatches).toBe(true);
+ expect(result.restoredFactory).toBe(true);
+ expect(result.originallyOwn).toBe(placement === 'own');
+ expect(result.restoredOwn).toBe(placement === 'own');
+ expect(result.reinstallError).toMatch(/already|reload|install/i);
+ expect(result.retainedFactoryOutcome).toEqual({ status: 'fulfilled', value: 5 });
+ });
+}
diff --git a/e2e/binance-orderbook/specs/chart-storage-mirror-scoped.pw.js b/e2e/binance-orderbook/specs/chart-storage-mirror-scoped.pw.js
new file mode 100644
index 0000000..176960f
--- /dev/null
+++ b/e2e/binance-orderbook/specs/chart-storage-mirror-scoped.pw.js
@@ -0,0 +1,421 @@
+import { readFile } from 'node:fs/promises';
+import { createHash } from 'node:crypto';
+import { test, expect } from '../test.js';
+import { openChartMirrorScoped, openMirrorBootstrapLab } from '../helpers/chart-storage-mirror-scoped.js';
+import { STORAGE_LAB_ORIGIN } from '../helpers/chart-storage-lab.js';
+
+test('user receives a statically generated mirror factory with exactly one pinned expression replacement', async ({ page }) => {
+ // Given the generated candidate and its source provenance accompany the callback fixture.
+ const root = new URL('../../../test/fixtures/binance-chart-storage/', import.meta.url);
+ const manifest = JSON.parse(await readFile(new URL('mirror-scoped-manifest.json', root), 'utf8'));
+ await openMirrorBootstrapLab(page);
+
+ // When strict registration validation accepts the exact original and rejects a source mismatch.
+ const { original, changed, failure } = await page.evaluate(async () => {
+ const module = await import('/mirror-module.js');
+ const fixture = await import('/mirror-scoped-callback.js');
+ const native = fixture.createNativeMirrorFactory();
+ const replacement = module.replaceChartMirrorFactory(native, (_target, _entries, thunk) => thunk());
+ let failure;
+ try { module.replaceChartMirrorFactory(function mismatched() { return 1; }, () => []); }
+ catch (error) { failure = error.message; }
+ return { original: Function.prototype.toString.call(native), changed: Function.prototype.toString.call(replacement), failure };
+ });
+ const [start, end] = manifest.replacement.factory_utf16_range;
+ const inserted = changed.slice(start, changed.length - (original.length - end));
+
+ // Then reversing the single inserted dispatch reproduces every byte of the public factory.
+ expect(createHash('sha256').update(original).digest('hex')).toBe(manifest.original_factory.sha256);
+ expect(changed.slice(0, start) + original.slice(start, end) + changed.slice(start + inserted.length)).toBe(original);
+ expect(inserted).toBe(`__dispatchChartMirror(Sr,fr,()=>${original.slice(start, end)})`);
+ expect(failure).toBe('Mirror factory source does not match the pinned public module');
+});
+
+for (const mode of ['native', 'scoped']) {
+ test(`user retains native same-stack set get remove and clear results with ${mode}`, async ({ page }) => {
+ // Given a ready unmodified instance starts each operation matrix with one original value.
+ await openChartMirrorScoped(page, { mode });
+ await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ state.matrixStore = state.localforage.createInstance({ name: 'chart_futures' });
+ await state.matrixStore.ready();
+ state.matrixMethods = ['setItem', 'getItem', 'removeItem', 'clear'].map(name => state.matrixStore[name]);
+ });
+
+ // When each public operation sequence starts in one stack without waiting between its calls.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ const store = state.matrixStore;
+ const outcomes = [];
+ for (const operations of [['set', 'get'], ['set', 'remove', 'get'], ['set', 'clear', 'get'], ['get', 'remove', 'set', 'get']]) {
+ await store.clear();
+ await store.setItem('myTradingView', { revision: 'original' });
+ const callbacks = [];
+ const pending = operations.map((operation, index) => {
+ const callback = (error, value) => callbacks.push({ index, operation, error, value });
+ if (operation === 'set') return store.setItem('myTradingView', { revision: 'new' }, callback);
+ if (operation === 'get') return store.getItem('myTradingView', callback);
+ if (operation === 'remove') return store.removeItem('myTradingView', callback);
+ return store.clear(callback);
+ });
+ outcomes.push({ values: await Promise.all(pending), callbacks, finalValue: await store.getItem('myTradingView') });
+ }
+ return { outcomes, dispatches: state.dispatches,
+ sameMethods: ['setItem', 'getItem', 'removeItem', 'clear'].every((name, index) => store[name] === state.matrixMethods[index]),
+ sameFactory: state.localforage.createInstance === state.nativeCreateInstance };
+ });
+
+ // Then native delayed set preparation preserves concrete values and callback ordering in every matrix.
+ const before = { revision: 'original' };
+ const after = { revision: 'new' };
+ const callback = (index, operation, value) => ({ index, operation, error: null, value });
+ expect(result).toEqual({ outcomes: [
+ { values: [after, before], callbacks: [callback(1, 'get', before), callback(0, 'set', after)], finalValue: after },
+ { values: [after, undefined, null], callbacks: [callback(1, 'remove', undefined), callback(2, 'get', null), callback(0, 'set', after)], finalValue: after },
+ { values: [after, undefined, null], callbacks: [callback(1, 'clear', undefined), callback(2, 'get', null), callback(0, 'set', after)], finalValue: after },
+ { values: [before, undefined, after, null], callbacks: [callback(0, 'get', before), callback(1, 'remove', undefined), callback(3, 'get', null), callback(2, 'set', after)], finalValue: after },
+ ], dispatches: 0, sameMethods: true, sameFactory: true });
+ });
+
+ test(`user preserves ordinary storage API identity and native concurrent clear ordering with ${mode}`, async ({ page }) => {
+ // Given real localForage and Basic storage are ready before the mirror writer is used.
+ await openChartMirrorScoped(page, { mode });
+ await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ state.tv = window.__STORAGE_LAB__.create();
+ state.basic = window.__CHART_HOST__.basic.create({ namespace: 'chart_futures', symbol: 'BTCUSDT' });
+ await state.tv.setItem('myTradingView', { revision: 0 });
+ await state.basic.saveSettings({ background: 'initial' });
+ state.setItem = state.tv.setItem;
+ state.clear = state.tv.clear;
+ });
+
+ // When native writes and the original Basic clear are issued in the same stack.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ await Promise.all([
+ state.tv.setItem('#TV_SYMBOL-BEFORE', []), state.basic.saveSettings({ background: 'before-clear' }),
+ state.basic.clear(), state.tv.setItem('#TV_SYMBOL-AFTER', []),
+ ]);
+ return {
+ keys: (await state.tv.keys()).sort(), settings: await state.tv.getItem('candlestick-setting'),
+ snapshot: await state.tv.getItem('myTradingView'), dispatches: state.dispatches,
+ identity: state.localforage.createInstance === state.nativeCreateInstance && state.tv.setItem === state.setItem && state.tv.clear === state.clear,
+ };
+ });
+
+ // Then the same native ordering survives and no ordinary storage call enters the scoped writer.
+ expect(result).toEqual({
+ keys: ['#TV_SYMBOL-AFTER', '#TV_SYMBOL-BEFORE', 'candlestick-setting'], settings: { background: 'before-clear' },
+ snapshot: null, dispatches: 0, identity: true,
+ });
+ });
+
+ test(`user mirrors the original serial save chain while retaining null values and target-only keys with ${mode}`, async ({ page }) => {
+ // Given source and target hold independent Basic settings and an explicit null drawing.
+ await openChartMirrorScoped(page, { mode });
+ await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ state.source = state.localforage.createInstance({ name: 'chart_futures' });
+ state.target = state.localforage.createInstance({ name: 'chart_delivery' });
+ await state.source.setItem('#TV_SYMBOL-NULL', null);
+ await state.source.setItem('candlestick-setting', { theme: 'source' });
+ await state.target.setItem('candlestick-setting', { theme: 'target' });
+ await state.target.setItem('#TV_SYMBOL-TARGET-ONLY', []);
+ state.save = window.__CHART_HOST__.tradingView.createSaveCallback(state.source, 'myTradingView', {}, state.create('chart_futures'));
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When the unchanged original save callback awaits three successive mirror callbacks.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ for (const revision of [1, 1, 2]) await state.save({ layout: 's', revision, charts: [] });
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ return {
+ source: await state.source.getItem('myTradingView'), target: await state.target.getItem('myTradingView'),
+ targetKeys: (await state.target.keys()).sort(), targetNull: await state.target.getItem('#TV_SYMBOL-NULL'),
+ sourceBasic: await state.source.getItem('candlestick-setting'), targetBasic: await state.target.getItem('candlestick-setting'),
+ targetOnly: await state.target.getItem('#TV_SYMBOL-TARGET-ONLY'), metrics, dispatches: state.dispatches,
+ };
+ });
+
+ // Then snapshots match, null remains a stored key, and only target mirror writes use fewer transactions.
+ expect(result.source).toEqual({ layout: 's', revision: 2, charts: [] });
+ expect(result.target).toEqual(result.source);
+ expect(result.targetKeys).toEqual(['#TV_SYMBOL-NULL', '#TV_SYMBOL-TARGET-ONLY', 'candlestick-setting', 'myTradingView', 'myTradingView.layout']);
+ expect(result.targetNull).toBe(null);
+ expect(result.sourceBasic).toEqual({ theme: 'source' });
+ expect(result.targetBasic).toEqual({ theme: 'target' });
+ expect(result.targetOnly).toEqual([]);
+ expect(result.metrics.puts).toBe(mode === 'native' ? 15 : 10);
+ expect(result.metrics.transactions).toBe(mode === 'native' ? 30 : 24);
+ expect(result.metrics.pending).toBe(0);
+ expect(result.metrics.aborted).toBe(0);
+ expect(result.dispatches).toBe(mode === 'native' ? 0 : 3);
+ });
+
+ test(`user keeps native mirror guards and empty-source behavior with ${mode}`, async ({ page }) => {
+ // Given only an unrelated Basic key exists and both mirror control refs are available.
+ await openChartMirrorScoped(page, { mode });
+ await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ await window.__STORAGE_LAB__.native.setItem('candlestick-setting', { theme: 'source' });
+ state.blocked = state.create('chart_futures', { current: true }, { current: true });
+ state.disabled = state.create('chart_futures', { current: false }, { current: false });
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When guarded callbacks return before storage and an enabled callback sees zero mirror entries.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ const guarded = await Promise.all([state.blocked(), state.disabled()]);
+ const guards = window.__STORAGE_NATIVE__.snapshot();
+ await state.create('chart_futures')();
+ const empty = window.__STORAGE_NATIVE__.snapshot();
+ const target = state.localforage.createInstance({ name: 'chart_delivery' });
+ return { guarded, guards, empty, targetKeys: await target.keys(), dispatches: state.dispatches };
+ });
+
+ // Then neither guard performs IO and the empty mirror issues only its native source-key read.
+ expect(result.guarded).toEqual([undefined, undefined]);
+ expect(result.guards.transactions).toBe(0);
+ expect(result.empty.transactions).toBe(1);
+ expect(result.empty.puts).toBe(0);
+ expect(result.targetKeys).toEqual([]);
+ expect(result.dispatches).toBe(mode === 'native' ? 0 : 1);
+ });
+}
+
+for (const order of ['runtime-first', 'module-first']) {
+ test(`user replaces only the selected mirror module before first require with ${order}`, async ({ page }) => {
+ // Given the empty queue observer has an explicit synthetic 70940 replacement and an unrelated module.
+ await openMirrorBootstrapLab(page);
+ await page.evaluate(async () => {
+ const { observeChartStorageBootstrap } = await import('/bootstrap.js');
+ const state = { replacements: 0, originalExecutions: 0, patchedExecutions: 0 };
+ state.original = function (module) { state.originalExecutions += 1; module.exports = { value: 'original' }; };
+ state.patched = function (module) { state.patchedExecutions += 1; module.exports = { value: 'patched' }; };
+ state.observer = observeChartStorageBootstrap({ replaceMirrorFactory(original) {
+ if (original !== state.original) throw new Error('Unexpected synthetic factory');
+ state.replacements += 1;
+ return state.patched;
+ } });
+ state.chunk = [['mirror-scoped-synthetic'], { 70940: state.original, 43917(module) { module.exports = { value: 'untouched' }; } }, require => {
+ state.require = require;
+ state.first = require(70940);
+ state.other = require(43917);
+ }];
+ window.__MIRROR_ENGINE__ = state;
+ });
+
+ // When the actual Rspack runtime consumes the synthetic module in either registration order.
+ if (order === 'runtime-first') await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ await page.evaluate(() => self.webpackChunkfutures_trade_ui.push(window.__MIRROR_ENGINE__.chunk));
+ if (order === 'module-first') await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_ENGINE__;
+ const captured = await state.observer.captured;
+ state.observer.stop();
+ return {
+ first: state.first, other: state.other, sameExport: state.require(70940) === captured,
+ replacements: state.replacements, originalExecutions: state.originalExecutions, patchedExecutions: state.patchedExecutions,
+ restored: state.require.m[70940] === state.original,
+ queueRestored: Object.hasOwn(Object.getOwnPropertyDescriptor(self.webpackChunkfutures_trade_ui, 'push'), 'value'),
+ };
+ });
+
+ // Then the cached transformed export survives observer cleanup while future factory and queue slots are restored.
+ expect(result).toEqual({ first: { value: 'patched' }, other: { value: 'untouched' }, sameExport: true,
+ replacements: 1, originalExecutions: 0, patchedExecutions: 1, restored: true, queueRestored: true });
+ });
+}
+
+test('user keeps the native module executable when strict mirror registration validation rejects', async ({ page }) => {
+ // Given an early observer uses the real strict replacement against a mismatched synthetic factory.
+ await openMirrorBootstrapLab(page);
+ await page.evaluate(async () => {
+ const { observeChartStorageBootstrap } = await import('/bootstrap.js');
+ const { replaceChartMirrorFactory } = await import('/mirror-module.js');
+ const state = { executions: 0 };
+ state.original = function (module) { state.executions += 1; module.exports = { value: 'native' }; };
+ state.observer = observeChartStorageBootstrap({ replaceMirrorFactory: original => replaceChartMirrorFactory(original, (_target, _entries, thunk) => thunk()) });
+ state.failure = state.observer.captured.then(() => 'unexpected-success', error => error.message);
+ window.__MIRROR_ENGINE__ = state;
+ });
+
+ // When actual runtime registration rejects transformation before the native factory executes.
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_ENGINE__;
+ self.webpackChunkfutures_trade_ui.push([['mirror-scoped-rejected'], { 70940: state.original }, require => { state.require = require; state.first = require(70940); }]);
+ return { failure: await state.failure, first: state.first, sameExport: state.require(70940) === state.first,
+ executions: state.executions, restored: state.require.m[70940] === state.original };
+ });
+
+ // Then observer rejection cannot enter the runtime module-error cache or alter native exports.
+ expect(result).toEqual({ failure: 'Mirror factory source does not match the pinned public module', first: { value: 'native' },
+ sameExport: true, executions: 1, restored: true });
+});
+
+for (const runtimeStarted of [false, true]) {
+ test(`user retains the native factory when replacement synchronously stops observation with runtime started ${runtimeStarted}`, async ({ page }) => {
+ // Given the synchronous replacement callback stops its observer before returning a candidate factory.
+ await openMirrorBootstrapLab(page);
+ await page.evaluate(async () => {
+ const { observeChartStorageBootstrap } = await import('/bootstrap.js');
+ const state = { originalExecutions: 0, patchedExecutions: 0 };
+ state.original = function (module) { state.originalExecutions += 1; module.exports = { value: 'native' }; };
+ state.patched = function (module) { state.patchedExecutions += 1; module.exports = { value: 'patched' }; };
+ state.observer = observeChartStorageBootstrap({ replaceMirrorFactory() {
+ state.observer.stop();
+ return state.patched;
+ } });
+ state.failure = state.observer.captured.then(() => 'unexpected-success', error => error.message);
+ window.__MIRROR_ENGINE__ = state;
+ });
+
+ // When the real runtime consumes that registration before the host's first require.
+ if (runtimeStarted) await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ await page.evaluate(() => {
+ const state = window.__MIRROR_ENGINE__;
+ self.webpackChunkfutures_trade_ui.push([['mirror-scoped-stop-during-replacement'], { 70940: state.original }, require => {
+ state.require = require;
+ state.first = require(70940);
+ }]);
+ });
+ if (!runtimeStarted) await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_ENGINE__;
+ const queue = self.webpackChunkfutures_trade_ui;
+ return { failure: await state.failure, first: state.first, sameExport: state.require(70940) === state.first,
+ originalExecutions: state.originalExecutions, patchedExecutions: state.patchedExecutions,
+ originalFactory: state.require.m[70940] === state.original,
+ queuedOriginalFactory: queue.find(chunk => Object.hasOwn(chunk[1], '70940'))[1][70940] === state.original,
+ queueRestored: Object.hasOwn(Object.getOwnPropertyDescriptor(queue, 'push'), 'value') };
+ });
+
+ // Then stop rejects capture while the original factory executes once and owns the unchanged runtime cache.
+ expect(result).toEqual({ failure: 'Storage bootstrap stopped before module execution', first: { value: 'native' },
+ sameExport: true, originalExecutions: 1, patchedExecutions: 0, originalFactory: true, queuedOriginalFactory: true, queueRestored: true });
+ });
+
+ test(`user restores the original unexecuted mirror factory when stopped with runtime started ${runtimeStarted}`, async ({ page }) => {
+ // Given a replaced synthetic mirror factory has registered without being required.
+ await openMirrorBootstrapLab(page);
+ await page.evaluate(async () => {
+ const { observeChartStorageBootstrap } = await import('/bootstrap.js');
+ const state = { executions: 0 };
+ state.original = function (module) { state.executions += 1; module.exports = { value: 'native-after-stop' }; };
+ state.observer = observeChartStorageBootstrap({ replaceMirrorFactory: () => function () { throw new Error('Stopped replacement executed'); } });
+ state.failure = state.observer.captured.then(() => 'unexpected-success', error => error.message);
+ window.__MIRROR_ENGINE__ = state;
+ });
+ if (runtimeStarted) await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ await page.evaluate(() => self.webpackChunkfutures_trade_ui.push([['mirror-scoped-stopped'], { 70940: window.__MIRROR_ENGINE__.original }]));
+
+ // When stop restores the registered factory before the actual runtime requires it.
+ await page.evaluate(() => window.__MIRROR_ENGINE__.observer.stop());
+ if (!runtimeStarted) await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/webpack-runtime.js` });
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_ENGINE__;
+ self.webpackChunkfutures_trade_ui.push([['mirror-scoped-stopped-entry'], {}, require => { state.require = require; state.value = require(70940); }]);
+ return { value: state.value, executions: state.executions, restored: state.require.m[70940] === state.original,
+ failure: await state.failure, queueRestored: Object.hasOwn(Object.getOwnPropertyDescriptor(self.webpackChunkfutures_trade_ui, 'push'), 'value') };
+ });
+
+ // Then only the original factory executes and the observer reports its explicit pre-execution stop.
+ expect(result).toEqual({ value: { value: 'native-after-stop' }, executions: 1, restored: true,
+ failure: 'Storage bootstrap stopped before module execution', queueRestored: true });
+ });
+}
+
+test('user resumes the exact native per-key mirror map through retained callbacks after the scoped writer stops', async ({ page }) => {
+ // Given one retained transformed mirror callback points to a source with three mirror entries.
+ await openChartMirrorScoped(page);
+ await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ const source = state.localforage.createInstance({ name: 'chart_futures' });
+ await Promise.all([source.setItem('myTradingView', { revision: 1 }), source.setItem('myTradingView.layout', []), source.setItem('#TV_SYMBOL-NULL', null)]);
+ state.retained = state.create('chart_futures');
+ await state.writer.stop();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When the unchanged surrounding host callback calls dispatch after the writer has stopped.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ await state.retained();
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ const target = state.localforage.createInstance({ name: 'chart_delivery' });
+ return { metrics, keys: (await target.keys()).sort(), snapshot: await target.getItem('myTradingView'), dispatches: state.dispatches,
+ createInstanceUnchanged: state.localforage.createInstance === state.nativeCreateInstance };
+ });
+
+ // Then three native target transactions preserve the original map and native instance factory identity.
+ expect(result.metrics.transactions).toBe(7);
+ expect(result.metrics.puts).toBe(3);
+ expect(result.metrics.pending).toBe(0);
+ expect(result.keys).toEqual(['#TV_SYMBOL-NULL', 'myTradingView', 'myTradingView.layout']);
+ expect(result.snapshot).toEqual({ revision: 1 });
+ expect(result.dispatches).toBe(1);
+ expect(result.createInstanceUnchanged).toBe(true);
+});
+
+for (const mode of ['native', 'scoped']) {
+ test(`user preserves all historical mirror data through thirty-two real host save chains with ${mode}`, async ({ page }, testInfo) => {
+ // Given 290 historical drawing keys already exist in each independently initialized database.
+ test.setTimeout(30_000);
+ await openChartMirrorScoped(page, { mode });
+ await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ state.source = state.localforage.createInstance({ name: 'chart_futures' });
+ state.target = state.localforage.createInstance({ name: 'chart_delivery' });
+ state.history = Array.from({ length: 290 }, (_, index) => ({ key: `#TV_SYMBOL-LAB${index}`, value: [{ id: `history-${index}`, points: [{ time: 1000, price: index }] }] }));
+ await Promise.all(state.history.flatMap(({ key, value }) => [state.source.setItem(key, value), state.target.setItem(key, value)]));
+ state.initial = { layout: 's', revision: 0, charts: [{ chartId: '0', panes: [{ mainSourceId: 'main-BTCUSDT', sources: [
+ { id: 'main-BTCUSDT', type: 'MainSeries', state: { symbol: 'BTCUSDT' } },
+ { id: 'drawing-BTCUSDT', type: 'LineToolTrendLine', ownerSource: 'main-BTCUSDT', state: { symbol: 'BTCUSDT' }, points: [{ time: 1000, price: 20 }, { time: 2000, price: 25 }] },
+ ] }] }] };
+ state.save = window.__CHART_HOST__.tradingView.createSaveCallback(state.source, 'myTradingView', {}, state.create('chart_futures'));
+ await state.save(structuredClone(state.initial));
+ window.__STORAGE_NATIVE__.reset();
+ state.dispatches = 0;
+ });
+
+ // When eight revisions each complete four concurrent unchanged host save and mirror chains.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_SCOPED__;
+ let completed = 0;
+ const started = performance.now();
+ for (let revision = 1; revision <= 8; revision += 1) {
+ const snapshot = structuredClone(state.initial);
+ snapshot.revision = revision;
+ snapshot.charts[0].panes[0].sources[1].points[1].price = 25 + revision;
+ completed += (await Promise.all(Array.from({ length: 4 }, () => state.save(snapshot)))).length;
+ }
+ const elapsedMs = performance.now() - started;
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ const read = async storage => ({ keys: (await storage.keys()).sort(), history: await Promise.all(state.history.map(async ({ key }) => ({ key, value: await storage.getItem(key) }))),
+ loaded: await window.__CHART_HOST__.tradingView.load({ storage, key: 'myTradingView' }) });
+ return { completed, elapsedMs, metrics, dispatches: state.dispatches, source: await read(state.source), target: await read(state.target),
+ expectedHistory: state.history, createInstanceUnchanged: state.localforage.createInstance === state.nativeCreateInstance };
+ });
+ await testInfo.attach(`mirror-scoped-load-${mode}.json`, { body: JSON.stringify({ mode, completed: result.completed, elapsedMs: result.elapsedMs, metrics: result.metrics }, null, 2), contentType: 'application/json' });
+
+ // Then both full datasets retain every historical value and the final drawing revision without facade changes.
+ expect(result.completed).toBe(32);
+ expect(result.source).toEqual(result.target);
+ expect(result.source.history).toEqual(result.expectedHistory);
+ expect(result.source.keys).toEqual([...result.expectedHistory.map(({ key }) => key), '#TV_SYMBOL-BTCUSDT', 'myTradingView', 'myTradingView.layout'].sort());
+ expect(result.source.loaded.chartSave.revision).toBe(8);
+ expect(result.source.loaded.chartSave.charts[0].panes[0].sources[1].points[1].price).toBe(33);
+ expect(result.metrics.transactions).toBe((mode === 'native' ? 591 : 299) * 32);
+ expect(result.metrics.puts).toBe(mode === 'native' ? 296 * 32 : 112);
+ expect(result.metrics.pending).toBe(0);
+ expect(result.metrics.aborted).toBe(0);
+ expect(result.metrics.completed).toBe(result.metrics.transactions);
+ expect(result.dispatches).toBe(mode === 'native' ? 0 : 32);
+ expect(result.elapsedMs).toBeGreaterThan(0);
+ expect(result.createInstanceUnchanged).toBe(true);
+ });
+}
diff --git a/e2e/binance-orderbook/specs/chart-storage-mirror-writer.pw.js b/e2e/binance-orderbook/specs/chart-storage-mirror-writer.pw.js
new file mode 100644
index 0000000..455663e
--- /dev/null
+++ b/e2e/binance-orderbook/specs/chart-storage-mirror-writer.pw.js
@@ -0,0 +1,556 @@
+import { test, expect } from '../test.js';
+import { openStorageLab, STORAGE_LAB_ORIGIN } from '../helpers/chart-storage-lab.js';
+import { openMirrorWriterLab, holdMirrorWriteLock, releaseMirrorWriteLock } from '../helpers/chart-storage-mirror-writer.js';
+
+test('user mirrors changed and missing values atomically while preserving equal values native APIs and copied input', async ({ page }) => {
+ // Given a ready native target has equal, changed, explicit-null and target-only records.
+ await openMirrorWriterLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const state = window.__MIRROR_WRITER__;
+ await Promise.all([
+ lab.native.setItem('myTradingView.equal', { b: [2, null], a: 1 }),
+ lab.native.setItem('myTradingView.changed', { revision: 0 }),
+ lab.native.setItem('#TV_SYMBOL-NULL', null),
+ lab.native.setItem('#TV_SYMBOL-TARGET-ONLY', ['keep']),
+ lab.native.setItem('candlestick-setting', { theme: 'target' }),
+ ]);
+ state.methods = Object.fromEntries(['setItem', 'getItem', 'clear', 'ready', 'createInstance'].map(name => [name, lab.native[name]]));
+ state.factory = lab.localforage.createInstance;
+ state.beforeOwnKeys = Reflect.ownKeys(lab.native);
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When the caller mutates its input immediately after dispatch and repeats the null batch.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const state = window.__MIRROR_WRITER__;
+ const changed = { revision: 1, charts: [{ symbol: 'BTCUSDT' }] };
+ const entries = [
+ ['myTradingView.equal', { a: 1, b: [2, null] }],
+ ['myTradingView.changed', changed], ['#TV_SYMBOL-NULL', null], ['#TV_SYMBOL-MISSING', null],
+ ];
+ const before = JSON.stringify(entries);
+ const dispatched = state.writer.dispatch(lab.native, entries, state.nativeThunk);
+ const unchangedByDispatch = JSON.stringify(entries) === before;
+ changed.revision = 99;
+ changed.charts[0].symbol = 'ETHUSDT';
+ entries[0][0] = 'myTradingView.caller-mutated';
+ const outcomes = await Promise.allSettled(dispatched);
+ const first = window.__STORAGE_NATIVE__.snapshot();
+ const repeated = await Promise.allSettled(state.writer.dispatch(lab.native, [
+ ['#TV_SYMBOL-NULL', null], ['#TV_SYMBOL-MISSING', null],
+ ], state.nativeThunk));
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ const persisted = await Promise.all(['myTradingView.equal', 'myTradingView.changed', '#TV_SYMBOL-NULL', '#TV_SYMBOL-MISSING', '#TV_SYMBOL-TARGET-ONLY', 'candlestick-setting'].map(key => lab.native.getItem(key)));
+ return {
+ outcomes, repeated, first, metrics, persisted, keys: (await lab.native.keys()).sort(),
+ unchangedByDispatch, callerValue: changed, nativeCalls: state.nativeCalls, stats: state.writer.getStats(),
+ equalKeyOrder: Object.keys(persisted[0]),
+ identity: state.factory === lab.localforage.createInstance && Object.entries(state.methods).every(([name, method]) => lab.native[name] === method),
+ ownKeysUnchanged: JSON.stringify(Reflect.ownKeys(lab.native)) === JSON.stringify(state.beforeOwnKeys),
+ };
+ });
+
+ // Then one transaction per batch changes only two keys and keeps native identity and caller ownership.
+ expect(result.outcomes).toEqual([{ status: 'fulfilled', value: undefined }]);
+ expect(result.repeated).toEqual([{ status: 'fulfilled', value: undefined }]);
+ expect(result.first).toMatchObject({ transactions: 1, completed: 1, puts: 2, pending: 0 });
+ expect(result.metrics).toMatchObject({ transactions: 2, completed: 2, puts: 2, pending: 0 });
+ expect(result.persisted).toEqual([{ b: [2, null], a: 1 }, { revision: 1, charts: [{ symbol: 'BTCUSDT' }] }, null, null, ['keep'], { theme: 'target' }]);
+ expect(result.keys).toEqual(['#TV_SYMBOL-MISSING', '#TV_SYMBOL-NULL', '#TV_SYMBOL-TARGET-ONLY', 'candlestick-setting', 'myTradingView.changed', 'myTradingView.equal']);
+ expect(result.equalKeyOrder).toEqual(['b', 'a']);
+ expect(result.unchangedByDispatch).toBe(true);
+ expect(result.callerValue).toEqual({ revision: 99, charts: [{ symbol: 'ETHUSDT' }] });
+ expect(result.identity).toBe(true);
+ expect(result.ownKeysUnchanged).toBe(true);
+ expect(result.nativeCalls).toBe(0);
+ expect(result.stats).toMatchObject({ acceptedBatches: 2, committedWrites: 2, skippedWrites: 4, pendingBatches: 0, pendingBytes: 0 });
+});
+
+test('user receives one rejected bulk result and keeps every original record after a real second-put abort', async ({ page }) => {
+ // Given two committed records and a native observer that aborts after the second successful put.
+ await openMirrorWriterLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await lab.native.setItem('myTradingView.A', { revision: 0 });
+ await lab.native.setItem('myTradingView.B', { revision: 0 });
+ const nativePut = IDBObjectStore.prototype.put;
+ let puts = 0;
+ window.__MIRROR_WRITER__.restorePut = () => { IDBObjectStore.prototype.put = nativePut; };
+ IDBObjectStore.prototype.put = function (...args) {
+ const request = Reflect.apply(nativePut, this, args);
+ if (this.transaction.db.name === 'chart_futures' && ++puts === 2) {
+ request.addEventListener('success', () => this.transaction.abort(), { once: true });
+ }
+ return request;
+ };
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When both replacements have executed within one transaction before its real abort event.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const state = window.__MIRROR_WRITER__;
+ const outcomes = await Promise.allSettled(state.writer.dispatch(lab.native, [
+ ['myTradingView.A', { revision: 1 }], ['myTradingView.B', { revision: 1 }],
+ ], state.nativeThunk));
+ state.restorePut();
+ await state.writer.stop();
+ return {
+ outcomes: outcomes.map(outcome => ({ status: outcome.status, name: outcome.reason?.name })),
+ metrics: window.__STORAGE_NATIVE__.snapshot(), stats: state.writer.getStats(), nativeCalls: state.nativeCalls,
+ persisted: await Promise.all(['A', 'B'].map(key => lab.native.getItem(`myTradingView.${key}`))),
+ };
+ });
+
+ // Then the abort rolls back both successful puts and the original native expression is never retried.
+ expect(result.outcomes).toEqual([{ status: 'rejected', name: 'AbortError' }]);
+ expect(result.persisted).toEqual([{ revision: 0 }, { revision: 0 }]);
+ expect(result.metrics).toMatchObject({ transactions: 1, puts: 2, completed: 0, aborted: 1, pending: 0 });
+ expect(result.stats).toMatchObject({ acceptedBatches: 1, failedBatches: 1, committedWrites: 0, pendingBatches: 0, pendingBytes: 0 });
+ expect(result.nativeCalls).toBe(0);
+});
+
+test('user keeps the first replacement rolled back when the second record exceeds the real origin quota', async ({ page, context }) => {
+ // Given the isolated origin has a real quota before its first IndexedDB initialization.
+ await openMirrorWriterLab(page);
+ const session = await context.newCDPSession(page);
+ try {
+ await session.send('Storage.overrideQuotaForOrigin', { origin: STORAGE_LAB_ORIGIN, quotaSize: 64 * 1024 });
+ const quota = await session.send('Storage.getUsageAndQuota', { origin: STORAGE_LAB_ORIGIN });
+ expect(quota.overrideActive).toBe(true);
+ expect(quota.quota).toBe(64 * 1024);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await lab.native.setItem('myTradingView.small', { revision: 0 });
+ await lab.native.setItem('myTradingView.large', { revision: 0 });
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When an earlier small put shares a transaction with an incompressible one-MiB value.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const state = window.__MIRROR_WRITER__;
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
+ const large = Array.from({ length: 16 }, () => Array.from(crypto.getRandomValues(new Uint8Array(65536)), byte => alphabet[byte & 63]).join('')).join('');
+ const outcomes = await Promise.allSettled(state.writer.dispatch(lab.native, [
+ ['myTradingView.small', { revision: 1 }], ['myTradingView.large', large],
+ ], state.nativeThunk));
+ return {
+ outcomes: outcomes.map(outcome => ({ status: outcome.status, name: outcome.reason?.name })),
+ metrics: window.__STORAGE_NATIVE__.snapshot(), stats: state.writer.getStats(), nativeCalls: state.nativeCalls,
+ persisted: await Promise.all(['small', 'large'].map(key => lab.native.getItem(`myTradingView.${key}`))),
+ };
+ });
+
+ // Then Chromium reports its real quota failure and neither record commits or replays.
+ expect(result.outcomes).toEqual([{ status: 'rejected', name: 'QuotaExceededError' }]);
+ expect(result.persisted).toEqual([{ revision: 0 }, { revision: 0 }]);
+ expect(result.metrics).toMatchObject({ transactions: 1, puts: 2, completed: 0, aborted: 1, pending: 0 });
+ expect(result.stats).toMatchObject({ failedBatches: 1, committedWrites: 0, pendingBatches: 0, pendingBytes: 0 });
+ expect(result.nativeCalls).toBe(0);
+ } finally {
+ await session.send('Storage.overrideQuotaForOrigin', { origin: STORAGE_LAB_ORIGIN });
+ await session.detach();
+ }
+});
+
+test('user retains native clear transaction ordering while a mirror batch waits for the real write lock', async ({ page }) => {
+ // Given a native transaction holds the target lock and all later transaction creation is observed.
+ await openMirrorWriterLab(page);
+ await page.evaluate(async () => { await window.__STORAGE_LAB__.native.setItem('myTradingView.initial', 0); });
+ await holdMirrorWriteLock(page);
+ await page.evaluate(() => {
+ const state = window.__MIRROR_WRITER__;
+ const nativeTransaction = IDBDatabase.prototype.transaction;
+ const nativeClear = IDBObjectStore.prototype.clear;
+ state.created = [];
+ state.completed = [];
+ const ids = new WeakMap();
+ IDBDatabase.prototype.transaction = function (...args) {
+ const transaction = Reflect.apply(nativeTransaction, this, args);
+ if (this.name === 'chart_futures') {
+ const id = state.created.length + 1;
+ ids.set(transaction, id);
+ state.created.push({ id, mode: transaction.mode, clear: false });
+ transaction.addEventListener('complete', () => state.completed.push(id), { once: true });
+ }
+ return transaction;
+ };
+ IDBObjectStore.prototype.clear = function (...args) {
+ state.created.find(entry => entry.id === ids.get(this.transaction)).clear = true;
+ return Reflect.apply(nativeClear, this, args);
+ };
+ state.restoreObservation = () => {
+ IDBDatabase.prototype.transaction = nativeTransaction;
+ IDBObjectStore.prototype.clear = nativeClear;
+ };
+ window.__STORAGE_NATIVE__.reset();
+ state.first = Promise.all(state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.before', 1]], state.nativeThunk));
+ });
+ await expect.poll(() => page.evaluate(() => window.__MIRROR_WRITER__.created.length)).toBe(1);
+
+ // When native clear creates its own transaction before the lock releases and a later bulk is admitted.
+ let blocked;
+ try {
+ await page.evaluate(() => { window.__MIRROR_WRITER__.cleared = window.__STORAGE_LAB__.native.clear(); });
+ await expect.poll(() => page.evaluate(() => window.__MIRROR_WRITER__.created.length)).toBe(2);
+ await page.evaluate(() => {
+ const state = window.__MIRROR_WRITER__;
+ state.last = Promise.all(state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.after', 2]], state.nativeThunk));
+ });
+ await expect.poll(() => page.evaluate(() => window.__MIRROR_WRITER__.created.length)).toBe(3);
+ blocked = await page.evaluate(() => ({ created: window.__MIRROR_WRITER__.created, completed: window.__MIRROR_WRITER__.completed }));
+ } finally {
+ await releaseMirrorWriteLock(page);
+ }
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const outcomes = await Promise.all([state.first, state.cleared, state.last]);
+ state.restoreObservation();
+ return { outcomes, completed: state.completed, nativeCalls: state.nativeCalls, keys: await window.__STORAGE_LAB__.native.keys() };
+ });
+
+ // Then all three native transactions exist while blocked and commit in their creation order.
+ expect(blocked).toEqual({ created: [{ id: 1, mode: 'readwrite', clear: false }, { id: 2, mode: 'readwrite', clear: true }, { id: 3, mode: 'readwrite', clear: false }], completed: [] });
+ expect(result.completed).toEqual([1, 2, 3]);
+ expect(result.outcomes).toEqual([[undefined], undefined, [undefined]]);
+ expect(result.keys).toEqual(['myTradingView.after']);
+ expect(result.nativeCalls).toBe(0);
+});
+
+test('user compares current target data after another tab replaces an earlier mirrored value', async ({ page, context }) => {
+ // Given one writer has committed a value and another real page shares its IndexedDB origin.
+ await openMirrorWriterLab(page);
+ const other = await context.newPage();
+ try {
+ await openStorageLab(other, { install: false });
+ await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ await Promise.all(state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.shared', { revision: 1 }]], state.nativeThunk));
+ });
+
+ // When the other page changes storage and the first page requests its original value again.
+ await other.evaluate(async () => { await window.__STORAGE_LAB__.native.setItem('myTradingView.shared', { revision: 2 }); });
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ window.__STORAGE_NATIVE__.reset();
+ await Promise.all(state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.shared', { revision: 1 }]], state.nativeThunk));
+ return { metrics: window.__STORAGE_NATIVE__.snapshot(), nativeCalls: state.nativeCalls };
+ });
+ const persisted = await other.evaluate(() => window.__STORAGE_LAB__.native.getItem('myTradingView.shared'));
+
+ // Then the writer reads the shared target again and restores it with one actual put.
+ expect(result.metrics).toMatchObject({ transactions: 1, gets: 1, puts: 1, completed: 1 });
+ expect(persisted).toEqual({ revision: 1 });
+ expect(result.nativeCalls).toBe(0);
+ } finally {
+ await other.close();
+ }
+});
+
+test('user stops at a fixed accepted-work fence while native clear and later mirror thunks remain independent', async ({ page }) => {
+ // Given one accepted mirror waits behind an actual write lock.
+ await openMirrorWriterLab(page);
+ await page.evaluate(async () => { await window.__STORAGE_LAB__.native.ready(); });
+ await holdMirrorWriteLock(page);
+ await page.evaluate(() => {
+ const state = window.__MIRROR_WRITER__;
+ window.__STORAGE_NATIVE__.reset();
+ state.accepted = Promise.all(state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.accepted', 1]], state.nativeThunk));
+ });
+ await expect.poll(() => page.evaluate(() => window.__STORAGE_NATIVE__.snapshot().transactions)).toBe(1);
+
+ // When stop defers new thunks but a native clear still creates a transaction immediately.
+ let blocked;
+ try {
+ await page.evaluate(() => {
+ const state = window.__MIRROR_WRITER__;
+ state.events = [];
+ state.stopping = state.writer.stop();
+ state.sameStop = state.writer.stop() === state.stopping;
+ state.stopping.then(() => state.events.push('stopped'));
+ state.tail = Promise.all(state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.tail', 2]], () => {
+ state.events.push('native-thunk');
+ return [new Promise(resolve => { state.releaseTail = resolve; })];
+ })).then(value => { state.events.push('tail-complete'); return value; });
+ state.cleared = window.__STORAGE_LAB__.native.clear();
+ });
+ await expect.poll(() => page.evaluate(() => window.__STORAGE_NATIVE__.snapshot().transactions)).toBe(2);
+ blocked = await page.evaluate(() => ({ events: window.__MIRROR_WRITER__.events, stats: window.__MIRROR_WRITER__.writer.getStats(), metrics: window.__STORAGE_NATIVE__.snapshot() }));
+ } finally {
+ await releaseMirrorWriteLock(page);
+ }
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const accepted = await state.accepted;
+ await state.stopping;
+ await state.cleared;
+ const atFence = [...state.events];
+ const nativeResult = [Promise.resolve('native-result')];
+ let calls = 0;
+ const exact = state.writer.dispatch(null, null, () => { calls += 1; return nativeResult; });
+ state.releaseTail('tail-result');
+ return {
+ accepted, atFence, tail: await state.tail, events: state.events, sameStop: state.sameStop,
+ exactArray: exact === nativeResult, exactPromise: exact[0] === nativeResult[0], calls,
+ stoppedResult: await Promise.all(exact), stats: state.writer.getStats(), keys: await window.__STORAGE_LAB__.native.keys(),
+ };
+ });
+
+ // Then stop resolves without waiting for the tail and stopped dispatch returns the exact native array.
+ expect(blocked.events).toEqual([]);
+ expect(blocked.stats).toMatchObject({ acceptedBatches: 1, pendingBatches: 1 });
+ expect(blocked.metrics).toMatchObject({ transactions: 2, completed: 0, pending: 2 });
+ expect(result.accepted).toEqual([undefined]);
+ expect(result.atFence).toEqual(['stopped', 'native-thunk']);
+ expect(result.tail).toEqual([['tail-result']]);
+ expect(result.events).toEqual(['stopped', 'native-thunk', 'tail-complete']);
+ expect(result.sameStop).toBe(true);
+ expect(result.exactArray).toBe(true);
+ expect(result.exactPromise).toBe(true);
+ expect(result.calls).toBe(1);
+ expect(result.stoppedResult).toEqual(['native-result']);
+ expect(result.keys).toEqual([]);
+ expect(result.stats).toMatchObject({ acceptedBatches: 1, pendingBatches: 0, pendingBytes: 0 });
+});
+
+test('user drains a blocked database initialization while unrelated raw native writes stay available', async ({ page }) => {
+ // Given an external version-one connection blocks the target native version-two initialization.
+ await openMirrorWriterLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const state = window.__MIRROR_WRITER__;
+ await lab.native.ready();
+ state.blocker = await new Promise((resolve, reject) => {
+ const request = indexedDB.open('chart_delivery', 1);
+ request.onupgradeneeded = () => request.result.createObjectStore('keyvaluepairs');
+ request.onsuccess = () => resolve(request.result);
+ request.onerror = () => reject(request.error);
+ });
+ const nativeOpen = IDBFactory.prototype.open;
+ let resolveBlocked;
+ state.blocked = new Promise(resolve => { resolveBlocked = resolve; });
+ IDBFactory.prototype.open = function (...args) {
+ const request = Reflect.apply(nativeOpen, this, args);
+ if (args[0] === 'chart_delivery' && args[1] === 2) request.addEventListener('blocked', event => resolveBlocked({ oldVersion: event.oldVersion, newVersion: event.newVersion }), { once: true });
+ return request;
+ };
+ state.restoreOpen = () => { IDBFactory.prototype.open = nativeOpen; };
+ state.target = lab.create('chart_delivery', { version: 2 });
+ state.accepted = Promise.all(state.writer.dispatch(state.target, [['myTradingView.upgrade', 1]], state.nativeThunk));
+ });
+
+ // When stop waits for accepted initialization while a raw source write completes and a mirror tail waits.
+ let blocked;
+ try {
+ blocked = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const version = await state.blocked;
+ state.events = [];
+ state.stopping = state.writer.stop();
+ state.stopping.then(() => state.events.push('stopped'));
+ state.tail = Promise.all(state.writer.dispatch(state.target, [['myTradingView.upgrade', 2]], () => {
+ state.events.push('native-thunk');
+ return [state.target.setItem('myTradingView.upgrade', 2)];
+ }));
+ const raw = await window.__STORAGE_LAB__.native.setItem('myTradingView.raw', 3);
+ return { version, raw, events: [...state.events], stats: state.writer.getStats() };
+ });
+ } finally {
+ await page.evaluate(() => { const state = window.__MIRROR_WRITER__; state.restoreOpen(); state.blocker.close(); });
+ }
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const accepted = await state.accepted;
+ await state.stopping;
+ return { accepted, tail: await state.tail, events: state.events, stats: state.writer.getStats(), persisted: await state.target.getItem('myTradingView.upgrade') };
+ });
+
+ // Then independent native work succeeds before the drain and the accepted write precedes its native tail.
+ expect(blocked.version).toEqual({ oldVersion: 1, newVersion: 2 });
+ expect(blocked.raw).toBe(3);
+ expect(blocked.events).toEqual([]);
+ expect(blocked.stats).toMatchObject({ acceptedBatches: 1, pendingBatches: 1, transactions: 0 });
+ expect(result.accepted).toEqual([undefined]);
+ expect(result.tail).toEqual([[2]]);
+ expect(result.events).toEqual(['stopped', 'native-thunk']);
+ expect(result.persisted).toBe(2);
+ expect(result.stats).toMatchObject({ acceptedBatches: 1, pendingBatches: 0, pendingBytes: 0, committedWrites: 1 });
+});
+
+test('user dispatches an empty mirror without initializing the native target or opening a transaction', async ({ page }) => {
+ // Given the native instance has never needed a database connection.
+ await openMirrorWriterLab(page);
+ await page.evaluate(() => { window.__STORAGE_NATIVE__.reset(); });
+
+ // When active dispatch receives zero source entries and stop drains the empty writer.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const target = window.__STORAGE_LAB__.native;
+ const beforeReady = target._ready;
+ const dispatched = state.writer.dispatch(target, [], state.nativeThunk);
+ await state.writer.stop();
+ return { length: dispatched.length, unchangedReady: target._ready === beforeReady, hasDatabase: Boolean(target._dbInfo?.db), metrics: window.__STORAGE_NATIVE__.snapshot(), stats: state.writer.getStats(), nativeCalls: state.nativeCalls };
+ });
+
+ // Then the empty array costs no ready initialization, database open, transaction or accepted batch.
+ expect(result.length).toBe(0);
+ expect(result.unchangedReady).toBe(true);
+ expect(result.hasDatabase).toBe(false);
+ expect(result.metrics).toMatchObject({ opens: 0, transactions: 0, puts: 0, gets: 0 });
+ expect(result.stats).toMatchObject({ acceptedBatches: 0, rejectedBatches: 0, pendingBatches: 0, pendingBytes: 0 });
+ expect(result.nativeCalls).toBe(0);
+});
+
+for (const scenario of [
+ { name: 'entry count', limits: { maxEntries: 1 }, kind: 'entries', error: /entry limit/ },
+ { name: 'encoded batch bytes', limits: { maxBatchBytes: 32 }, kind: 'bytes', error: /batch byte limit/ },
+ { name: 'duplicate keys', limits: {}, kind: 'duplicate', error: /unique/ },
+ { name: 'non-chart keys', limits: {}, kind: 'key', error: /non-chart key/ },
+ { name: 'non-JSON values', limits: {}, kind: 'value', error: /plain JSON/ },
+ { name: 'invalid pair shape', limits: {}, kind: 'pair', error: /key\/value pair/ },
+]) {
+ test(`user rejects ${scenario.name} before any mirror entry is written`, async ({ page }) => {
+ // Given a committed first key would visibly change if admission wrote a partial batch.
+ await openMirrorWriterLab(page, { limits: scenario.limits });
+ await page.evaluate(async () => {
+ await window.__STORAGE_LAB__.native.setItem('myTradingView.first', 0);
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When a valid first entry precedes the specified invalid or oversized entry.
+ const result = await page.evaluate(async kind => {
+ const state = window.__MIRROR_WRITER__;
+ const first = ['myTradingView.first', 1];
+ const second = {
+ entries: ['myTradingView.second', 2], bytes: ['myTradingView.second', 'x'.repeat(64)],
+ duplicate: ['myTradingView.first', 2], key: ['candlestick-setting', 2],
+ value: ['myTradingView.second', new Date(0)], pair: ['myTradingView.second'],
+ }[kind];
+ const outcomes = await Promise.allSettled(state.writer.dispatch(window.__STORAGE_LAB__.native, [first, second], state.nativeThunk));
+ return {
+ outcomes: outcomes.map(outcome => ({ status: outcome.status, message: outcome.reason?.message })),
+ metrics: window.__STORAGE_NATIVE__.snapshot(), stats: state.writer.getStats(), nativeCalls: state.nativeCalls,
+ first: await window.__STORAGE_LAB__.native.getItem('myTradingView.first'), keys: await window.__STORAGE_LAB__.native.keys(),
+ };
+ }, scenario.kind);
+
+ // Then the one rejected result preserves original data without opening a transaction or retrying.
+ expect(result.outcomes).toHaveLength(1);
+ expect(result.outcomes[0].status).toBe('rejected');
+ expect(result.outcomes[0].message).toMatch(scenario.error);
+ expect(result.first).toBe(0);
+ expect(result.keys).toEqual(['myTradingView.first']);
+ expect(result.metrics).toMatchObject({ opens: 0, transactions: 0, puts: 0 });
+ expect(result.stats).toMatchObject({ acceptedBatches: 0, rejectedBatches: 1, pendingBatches: 0, pendingBytes: 0 });
+ expect(result.nativeCalls).toBe(0);
+ });
+}
+
+for (const kind of ['batches', 'bytes']) {
+ test(`user rejects pending ${kind} overflow without disturbing the accepted mirror`, async ({ page }) => {
+ // Given a real lock holds one batch inside the configured pending capacity.
+ const limits = kind === 'batches' ? { maxPendingBatches: 1 } : { maxPendingBytes: 48 };
+ await openMirrorWriterLab(page, { limits });
+ await page.evaluate(async () => { await window.__STORAGE_LAB__.native.ready(); });
+ await holdMirrorWriteLock(page);
+ await page.evaluate(() => {
+ const state = window.__MIRROR_WRITER__;
+ window.__STORAGE_NATIVE__.reset();
+ state.accepted = Promise.all(state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.first', 1]], state.nativeThunk));
+ });
+ await expect.poll(() => page.evaluate(() => window.__STORAGE_NATIVE__.snapshot().transactions)).toBe(1);
+
+ // When another valid batch exceeds the pending bound while the first transaction cannot finish.
+ let rejected;
+ try {
+ rejected = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const outcomes = await Promise.allSettled(state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.second', 2]], state.nativeThunk));
+ return { outcomes: outcomes.map(outcome => ({ status: outcome.status, message: outcome.reason?.message })), metrics: window.__STORAGE_NATIVE__.snapshot(), stats: state.writer.getStats() };
+ });
+ } finally {
+ await releaseMirrorWriteLock(page);
+ }
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ return { accepted: await state.accepted, keys: await window.__STORAGE_LAB__.native.keys(), stats: state.writer.getStats(), nativeCalls: state.nativeCalls };
+ });
+
+ // Then only the first batch owns a transaction and its successful commit releases all capacity.
+ expect(rejected.outcomes).toHaveLength(1);
+ expect(rejected.outcomes[0].status).toBe('rejected');
+ expect(rejected.outcomes[0].message).toMatch(kind === 'batches' ? /pending batch limit/ : /pending byte limit/);
+ expect(rejected.metrics).toMatchObject({ transactions: 1, puts: 0 });
+ expect(rejected.stats).toMatchObject({ acceptedBatches: 1, rejectedBatches: 1, pendingBatches: 1 });
+ expect(result.accepted).toEqual([undefined]);
+ expect(result.keys).toEqual(['myTradingView.first']);
+ expect(result.stats).toMatchObject({ acceptedBatches: 1, rejectedBatches: 1, pendingBatches: 0, pendingBytes: 0 });
+ expect(result.nativeCalls).toBe(0);
+ });
+}
+
+test('user rejects a Proxy value that stops admission during validation without replaying its native thunk', async ({ page }) => {
+ // Given no active work exists and the native target is already ready.
+ await openMirrorWriterLab(page);
+ await page.evaluate(async () => { await window.__STORAGE_LAB__.native.ready(); window.__STORAGE_NATIVE__.reset(); });
+
+ // When synchronous object inspection invokes stop before the batch can be admitted.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ let stopping;
+ let inspections = 0;
+ const value = new Proxy({ revision: 1 }, {
+ getPrototypeOf(target) {
+ inspections += 1;
+ stopping = state.writer.stop();
+ return Reflect.getPrototypeOf(target);
+ },
+ });
+ const outcomes = await Promise.allSettled(state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.reentrant', value]], state.nativeThunk));
+ await stopping;
+ return { outcomes: outcomes.map(outcome => ({ status: outcome.status, message: outcome.reason?.message })), inspections, metrics: window.__STORAGE_NATIVE__.snapshot(), stats: state.writer.getStats(), nativeCalls: state.nativeCalls, keys: await window.__STORAGE_LAB__.native.keys() };
+ });
+
+ // Then reentrant stop rejects admission before any put and no active failure replays natively.
+ expect(result.inspections).toBeGreaterThan(0);
+ expect(result.outcomes).toEqual([{ status: 'rejected', message: 'Chart mirror admission stopped during validation' }]);
+ expect(result.metrics).toMatchObject({ transactions: 0, puts: 0 });
+ expect(result.stats).toMatchObject({ acceptedBatches: 0, rejectedBatches: 1, pendingBatches: 0, pendingBytes: 0 });
+ expect(result.nativeCalls).toBe(0);
+ expect(result.keys).toEqual([]);
+});
+
+test('user rolls back earlier replacements when a later stored value exceeds the read budget', async ({ page }) => {
+ // Given native storage contains a small first value and a second value larger than the mirror read bound.
+ await openMirrorWriterLab(page, { limits: { maxReadBytes: 64 } });
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await lab.native.setItem('myTradingView.first', { revision: 0 });
+ await lab.native.setItem('myTradingView.large', 'x'.repeat(128));
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When the first replacement succeeds inside the transaction before the oversized value is read.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const state = window.__MIRROR_WRITER__;
+ const outcomes = await Promise.allSettled(state.writer.dispatch(lab.native, [
+ ['myTradingView.first', { revision: 1 }], ['myTradingView.large', 'small'],
+ ], state.nativeThunk));
+ return {
+ outcomes: outcomes.map(outcome => ({ status: outcome.status, message: outcome.reason?.message })),
+ metrics: window.__STORAGE_NATIVE__.snapshot(), stats: state.writer.getStats(), nativeCalls: state.nativeCalls,
+ persisted: await Promise.all(['first', 'large'].map(key => lab.native.getItem(`myTradingView.${key}`))),
+ };
+ });
+
+ // Then the explicit read-limit failure aborts all changes and preserves both original native values.
+ expect(result.outcomes).toEqual([{ status: 'rejected', message: 'Mirror stored-value byte limit exceeded' }]);
+ expect(result.persisted).toEqual([{ revision: 0 }, 'x'.repeat(128)]);
+ expect(result.metrics).toMatchObject({ transactions: 1, gets: 2, puts: 1, aborted: 1, completed: 0, pending: 0 });
+ expect(result.stats).toMatchObject({ acceptedBatches: 1, failedBatches: 1, committedWrites: 0, pendingBatches: 0, pendingBytes: 0 });
+ expect(result.nativeCalls).toBe(0);
+});
diff --git a/e2e/binance-orderbook/specs/chart-storage-orderbook-entry.pw.js b/e2e/binance-orderbook/specs/chart-storage-orderbook-entry.pw.js
new file mode 100644
index 0000000..8d265a7
--- /dev/null
+++ b/e2e/binance-orderbook/specs/chart-storage-orderbook-entry.pw.js
@@ -0,0 +1,207 @@
+import { readFile } from 'node:fs/promises';
+import { test, expect } from '../test.js';
+import { createCancelScenario } from '../scenarios/cancel-current-symbol.js';
+import { openUserscriptScenario, readFixtureState } from '../helpers/userscript-page.js';
+
+const PANEL = '#jh-binance-close-qty-multiplier-panel';
+const INPUT = '#jh-binance-close-qty-multiplier-input';
+
+test('user gets one working orderbook when document-start injection precedes the document root', async ({ page }) => {
+ // Given the real artifact runs once through the browser init-script boundary before any fixture HTML.
+ await page.route('**/*', route => route.abort('blockedbyclient'));
+ const errors = [];
+ page.on('pageerror', error => errors.push({ message: error.message, stack: error.stack }));
+ const source = await readFile(new URL('../../../scripts/binance-orderbook-trade.user.js', import.meta.url), 'utf8');
+ await page.addInitScript({ content: `
+ window.__EARLY_ORDERBOOK_ENTRY__ = {
+ documentRootPresent: Boolean(document.documentElement),
+ injections: 1,
+ fixtureScriptLoads: 0,
+ };
+ ${source}
+ ` });
+ let initial;
+
+ // When the normal host HTML arrives with its later copy of the artifact explicitly disabled.
+ const host = await openUserscriptScenario(page, createCancelScenario(), {
+ beforeOrderbook: 'window.__EARLY_ORDERBOOK_ENTRY__.fixtureScriptLoads += 1; if (false) {',
+ afterOrderbook: '}',
+ afterNavigation: async () => {
+ initial = await page.evaluate(() => ({
+ entry: window.__EARLY_ORDERBOOK_ENTRY__,
+ storageInstalled: Object.hasOwn(self, '__BINANCE_CHART_STORAGE__'),
+ queueIntercepted: typeof Object.getOwnPropertyDescriptor(self.webpackChunkfutures_trade_ui, 'push').get === 'function',
+ }));
+ expect(errors, 'The initial artifact must complete without a later reinjection repairing it').toEqual([]);
+ },
+ });
+ const disabledStyle = await page.locator('#jh-binance-close-qty-multiplier-dec').evaluate(element => {
+ const style = getComputedStyle(element);
+ return { disabled: element.disabled, background: style.backgroundColor, cursor: style.cursor };
+ });
+ await page.locator(INPUT).fill('3');
+ await page.locator(INPUT).blur();
+
+ // Then the first injection owns storage and initializes exactly one usable panel after the root appears.
+ expect(initial).toEqual({ entry: { documentRootPresent: false, injections: 1, fixtureScriptLoads: 1 }, storageInstalled: true, queueIntercepted: true });
+ expect(await page.evaluate(() => typeof window.__TM_CLOSE_LONG_DEBUG__)).toBe('object');
+ await expect(page.locator('style#jh-disabled-control-style')).toHaveCount(1);
+ expect(disabledStyle).toEqual({ disabled: true, background: 'rgb(245, 245, 245)', cursor: 'not-allowed' });
+ await expect(page.locator(PANEL)).toHaveCount(1);
+ await expect(page.locator(PANEL)).toBeVisible();
+ await expect(page.locator(INPUT)).toHaveValue('3');
+ await expect(page.locator('#jh-binance-close-qty-final')).toHaveText('0.21');
+ expect(errors).toEqual([]);
+ expect(host.errors).toEqual([]);
+ expect((await readFixtureState(page)).events.filter(({ type }) => ['order-submitted', 'cancel-requested'].includes(type))).toEqual([]);
+});
+
+test('user installs chart storage before the generated orderbook starts its native network observation', async ({ page }) => {
+ // Given every request is isolated and a native constructor read records the first business initialization.
+ await page.route('**/*', route => route.abort('blockedbyclient'));
+ const observeStartup = `
+ {
+ const descriptor = Object.getOwnPropertyDescriptor(window, 'WebSocket');
+ let value = window.WebSocket;
+ window.__CHART_ENTRY_OBSERVATIONS__ = [];
+ window.__RESTORE_ENTRY_OBSERVATION__ = () => Object.defineProperty(window, 'WebSocket', { ...descriptor, value });
+ Object.defineProperty(window, 'WebSocket', {
+ configurable: true,
+ get() {
+ const queue = self.webpackChunkfutures_trade_ui;
+ const push = queue && Object.getOwnPropertyDescriptor(queue, 'push');
+ window.__CHART_ENTRY_OBSERVATIONS__.push({
+ storageInstalled: Object.hasOwn(self, '__BINANCE_CHART_STORAGE__'),
+ queueIntercepted: Boolean(push && typeof push.get === 'function'),
+ bookDebug: typeof window.__TM_CLOSE_LONG_DEBUG__,
+ readyState: document.readyState,
+ });
+ return value;
+ },
+ set(next) { value = next; },
+ });
+ }
+ `;
+
+ // When the unchanged generated install artifact starts on the existing full orderbook host fixture.
+ const host = await openUserscriptScenario(page, createCancelScenario(), {
+ beforeOrderbook: observeStartup,
+ afterOrderbook: 'window.__RESTORE_ENTRY_OBSERVATION__();',
+ });
+ const first = await page.evaluate(() => window.__CHART_ENTRY_OBSERVATIONS__[0]);
+
+ // Then queue interception precedes the first business network observer and the normal panel still mounts.
+ expect(first).toEqual({ storageInstalled: true, queueIntercepted: true, bookDebug: 'undefined', readyState: 'loading' });
+ await expect(page.locator(PANEL)).toBeVisible();
+ await expect(page.locator(INPUT)).toHaveValue('1');
+ expect(host.errors).toEqual([]);
+});
+
+for (const kind of ['source-mismatch', 'late-bootstrap']) {
+ test(`user can edit the orderbook multiplier when chart storage retires after ${kind}`, async ({ page }) => {
+ // Given the fixture supplies either an unsupported host module or an already populated native queue.
+ await page.route('**/*', route => route.abort('blockedbyclient'));
+ const beforeOrderbook = `
+ self.__CHART_ENTRY_NATIVE_FACTORY__ = function nativeHost(module) { module.exports = 'native-chart-module'; };
+ self.__CHART_ENTRY_NATIVE_CHUNK__ = [['existing-chart'], { 70940: self.__CHART_ENTRY_NATIVE_FACTORY__ }];
+ self.webpackChunkfutures_trade_ui = ${kind === 'late-bootstrap' ? '[self.__CHART_ENTRY_NATIVE_CHUNK__]' : '[]'};
+ `;
+
+ // When the generated orderbook loads and the user edits its real quantity control after storage retires.
+ const host = await openUserscriptScenario(page, createCancelScenario(), {
+ beforeOrderbook,
+ afterOrderbook: kind === 'source-mismatch'
+ ? 'self.webpackChunkfutures_trade_ui.push(self.__CHART_ENTRY_NATIVE_CHUNK__);' : '',
+ });
+ await page.locator(INPUT).fill('3');
+ await page.locator(INPUT).blur();
+ const storage = await page.evaluate(async () => {
+ const snapshot = await self.__BINANCE_CHART_STORAGE__.stop();
+ const chunk = self.webpackChunkfutures_trade_ui[0];
+ const module = { exports: {} };
+ chunk[1][70940](module);
+ return {
+ snapshot,
+ sameChunk: chunk === self.__CHART_ENTRY_NATIVE_CHUNK__,
+ sameFactory: chunk[1][70940] === self.__CHART_ENTRY_NATIVE_FACTORY__,
+ nativePush: self.webpackChunkfutures_trade_ui.push === Array.prototype.push,
+ nativeValue: module.exports,
+ };
+ });
+
+ // Then native chart execution remains intact and the normal orderbook calculation works without trading.
+ expect(storage.snapshot).toMatchObject({ status: 'native', phase: 'stopped',
+ reason: kind === 'source-mismatch' ? 'source_mismatch' : 'bootstrap_unavailable',
+ attempts: kind === 'source-mismatch' ? 1 : 0, matches: 0, executions: 0,
+ writer: { acceptedBatches: 0, transactions: 0 },
+ });
+ expect(storage).toMatchObject({ sameChunk: true, sameFactory: true, nativePush: true, nativeValue: 'native-chart-module' });
+ await expect(page.locator(PANEL)).toBeVisible();
+ await expect(page.locator(INPUT)).toHaveValue('3');
+ await expect(page.locator('#jh-binance-close-qty-final')).toHaveText('0.21');
+ expect((await readFixtureState(page)).events.filter(({ type }) => ['order-submitted', 'cancel-requested'].includes(type))).toEqual([]);
+ expect(host.errors).toEqual([]);
+ });
+}
+
+test('user keeps storage and the trading panel absent on futures home before native SPA entry', async ({ page }) => {
+ // Given the generated artifact starts on the futures homepage despite a stale trading host DOM.
+ await page.route('**/*', route => route.abort('blockedbyclient'));
+ let homepage;
+
+ // When the existing fixture navigates from home to its supported contract without reinjecting the script.
+ const host = await openUserscriptScenario(page, createCancelScenario(), {
+ beforeOrderbook: "history.replaceState({}, '', '/zh-CN/futures/home');",
+ afterNavigation: async () => {
+ homepage = await page.evaluate(() => ({
+ pathname: location.pathname,
+ storageInstalled: Object.hasOwn(self, '__BINANCE_CHART_STORAGE__'),
+ queueCreated: Object.hasOwn(self, 'webpackChunkfutures_trade_ui'),
+ bookDebug: typeof window.__TM_CLOSE_LONG_DEBUG__,
+ panels: document.querySelectorAll('#jh-binance-close-qty-multiplier-panel').length,
+ }));
+ await page.evaluate(() => history.pushState({}, '', '/zh-CN/futures/HYPEUSDT'));
+ },
+ });
+ await page.locator(INPUT).fill('3');
+
+ // Then home performed no storage or trading initialization and the unchanged orderbook SPA path still works.
+ expect(homepage).toEqual({ pathname: '/zh-CN/futures/home', storageInstalled: false, queueCreated: false, bookDebug: 'undefined', panels: 0 });
+ expect(await page.evaluate(() => Object.hasOwn(self, '__BINANCE_CHART_STORAGE__'))).toBe(false);
+ await expect(page.locator(PANEL)).toBeVisible();
+ await expect(page.locator('#jh-binance-close-qty-final')).toHaveText('0.21');
+ expect((await readFixtureState(page)).events.filter(({ type }) => ['order-submitted', 'cancel-requested'].includes(type))).toEqual([]);
+ expect(host.errors).toEqual([]);
+});
+
+test('user retains the existing orderbook behavior in a child frame while storage remains top-frame only', async ({ page }) => {
+ // Given the generated artifact has mounted on a top-level fixture that owns every child-frame request.
+ await page.route('**/*', route => route.abort('blockedbyclient'));
+ const host = await openUserscriptScenario(page, createCancelScenario());
+
+ // When a real same-origin child trading page loads the same artifact and edits its own multiplier.
+ await page.evaluate(() => {
+ const frame = document.createElement('iframe');
+ frame.name = 'chart-storage-entry-child';
+ frame.src = '/zh-CN/futures/HYPEUSDT?chart-storage-entry-child=1';
+ frame.style.cssText = 'width:1440px;height:1000px;border:0';
+ document.body.appendChild(frame);
+ });
+ const frame = page.frameLocator('iframe[name="chart-storage-entry-child"]');
+ await expect(frame.locator(PANEL)).toBeVisible();
+ await frame.locator(INPUT).fill('3');
+ const state = await frame.locator('body').evaluate(() => ({
+ isTop: self === top,
+ storageInstalled: Object.hasOwn(self, '__BINANCE_CHART_STORAGE__'),
+ queueCreated: Object.hasOwn(self, 'webpackChunkfutures_trade_ui'),
+ bookDebug: typeof window.__TM_CLOSE_LONG_DEBUG__,
+ actions: window.__BINANCE_FIXTURE__.snapshot().events.filter(({ type }) => ['order-submitted', 'cancel-requested'].includes(type)),
+ }));
+
+ // Then only storage is frame-restricted while the original orderbook panel and quantity calculation remain usable.
+ expect(state).toEqual({ isTop: false, storageInstalled: false, queueCreated: false, bookDebug: 'object', actions: [] });
+ expect(await page.evaluate(() => Object.hasOwn(self, '__BINANCE_CHART_STORAGE__'))).toBe(true);
+ await expect(frame.locator(INPUT)).toHaveValue('3');
+ await expect(frame.locator('#jh-binance-close-qty-final')).toHaveText('0.21');
+ expect(host.errors).toEqual([]);
+});
diff --git a/e2e/binance-orderbook/specs/chart-storage-production-writer.pw.js b/e2e/binance-orderbook/specs/chart-storage-production-writer.pw.js
new file mode 100644
index 0000000..e88aaa0
--- /dev/null
+++ b/e2e/binance-orderbook/specs/chart-storage-production-writer.pw.js
@@ -0,0 +1,348 @@
+import { test, expect } from '../test.js';
+import { openProductionMirrorWriterLab } from '../helpers/chart-storage-production-writer.js';
+import { holdMirrorWriteLock, releaseMirrorWriteLock } from '../helpers/chart-storage-mirror-writer.js';
+
+test('user persists signed zero and property order changes without rewriting equal snapshots', async ({ page }) => {
+ // Given native records differ only by signed zero or insertion order.
+ await openProductionMirrorWriterLab(page);
+ await page.evaluate(async () => {
+ const target = window.__STORAGE_LAB__.native;
+ await Promise.all([
+ target.setItem('myTradingView.zero', { value: -0 }),
+ target.setItem('myTradingView.order', { a: 1, b: 2 }),
+ target.setItem('myTradingView.equal', { same: [1, null] }),
+ ]);
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When the native mirror receives the next ordered snapshot.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ await Promise.all(state.dispatch([
+ ['myTradingView.zero', { value: 0 }],
+ ['myTradingView.order', { b: 2, a: 1 }],
+ ['myTradingView.equal', { same: [1, null] }],
+ ]));
+ const target = window.__STORAGE_LAB__.native;
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ return { metrics, zero: Object.is((await target.getItem('myTradingView.zero')).value, 0),
+ keys: Object.keys(await target.getItem('myTradingView.order')), stats: state.writer.getStats(), nativeCalls: state.nativeCalls };
+ });
+
+ // Then exactly the observable changes are committed in one transaction.
+ expect(result.zero).toBe(true);
+ expect(result.keys).toEqual(['b', 'a']);
+ expect(result.metrics).toMatchObject({ transactions: 1, puts: 2, aborted: 0, completed: 1 });
+ expect(result.stats).toMatchObject({ committedWrites: 2, skippedWrites: 1, failedBatches: 0 });
+ expect(result.nativeCalls).toBe(0);
+});
+
+for (const kind of ['shared reference', 'cycle', 'date', 'oversized input']) {
+ test(`user preserves native storage semantics for ${kind} before a batch transaction exists`, async ({ page }) => {
+ // Given the supported optimization limits exclude the next structured clone value.
+ await openProductionMirrorWriterLab(page, { limits: kind === 'oversized input' ? { maxBatchBytes: 40 } : {} });
+ await page.evaluate(async () => { await window.__STORAGE_LAB__.native.ready(); window.__STORAGE_NATIVE__.reset(); });
+
+ // When the original native map receives the unsupported snapshot once.
+ const result = await page.evaluate(async kind => {
+ const state = window.__MIRROR_WRITER__;
+ const shared = { revision: 1 };
+ const value = kind === 'shared reference' ? { a: shared, b: shared }
+ : kind === 'cycle' ? { revision: 1 } : kind === 'date' ? new Date(0) : 'x'.repeat(100);
+ if (kind === 'cycle') value.self = value;
+ const outcomes = await Promise.allSettled(state.dispatch([['myTradingView.native', value]]));
+ await state.writer.stop();
+ const persisted = await window.__STORAGE_LAB__.native.getItem('myTradingView.native');
+ return { status: outcomes.map(outcome => outcome.status), nativeCalls: state.nativeCalls, stats: state.writer.getStats(),
+ preserved: kind === 'shared reference' ? persisted.a === persisted.b
+ : kind === 'cycle' ? persisted.self === persisted : kind === 'date' ? persisted instanceof Date && persisted.getTime() === 0 : persisted === value };
+ }, kind);
+
+ // Then native structured cloning retains the value without splitting aliases or dropping a save.
+ expect(result.status).toEqual(['fulfilled']);
+ expect(result.preserved).toBe(true);
+ expect(result.nativeCalls).toBe(1);
+ expect(result.stats).toMatchObject({ acceptedBatches: 0, rejectedBatches: 1, nativePassthroughBatches: 1,
+ transactions: 0, failedBatches: 0, pendingBatches: 0, pendingBytes: 0 });
+ });
+}
+
+for (const kind of ['date', 'read budget']) {
+ test(`user replaces a stored ${kind} inside the original transaction`, async ({ page }) => {
+ // Given the second old value cannot be safely compared within the optimization budget.
+ await openProductionMirrorWriterLab(page, { limits: { maxReadBytes: 64 } });
+ await page.evaluate(async kind => {
+ const target = window.__STORAGE_LAB__.native;
+ await target.setItem('myTradingView.first', { revision: 0 });
+ await target.setItem('myTradingView.second', kind === 'date' ? new Date(0) : 'x'.repeat(128));
+ window.__STORAGE_NATIVE__.reset();
+ }, kind);
+
+ // When both records receive fresh JSON snapshots.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const outcomes = await Promise.allSettled(state.dispatch([
+ ['myTradingView.first', { revision: 1 }], ['myTradingView.second', { revision: 2 }],
+ ]));
+ return { status: outcomes.map(outcome => outcome.status), nativeCalls: state.nativeCalls, metrics: window.__STORAGE_NATIVE__.snapshot(),
+ stats: state.writer.getStats(), values: await Promise.all(['first', 'second'].map(key => window.__STORAGE_LAB__.native.getItem(`myTradingView.${key}`))) };
+ });
+
+ // Then the transaction commits both replacements without aborting or native replay.
+ expect(result.status).toEqual(['fulfilled']);
+ expect(result.values).toEqual([{ revision: 1 }, { revision: 2 }]);
+ expect(result.nativeCalls).toBe(0);
+ expect(result.metrics).toMatchObject({ transactions: 1, puts: 2, completed: 1, aborted: 0 });
+ expect(result.stats).toMatchObject({ acceptedBatches: 1, failedBatches: 0, committedWrites: 2, pendingBytes: 0 });
+ });
+}
+
+test('user accepts plain snapshots from the chart iframe without changing native methods', async ({ page }) => {
+ // Given the chart creates ordinary objects in a different JavaScript realm.
+ await openProductionMirrorWriterLab(page);
+ await page.evaluate(async () => { await window.__STORAGE_LAB__.native.ready(); window.__STORAGE_NATIVE__.reset(); });
+
+ // When dispatch copies the iframe snapshot before its owner mutates it.
+ const result = await page.evaluate(async () => {
+ const iframe = document.createElement('iframe');
+ document.body.append(iframe);
+ const value = new iframe.contentWindow.Object();
+ value.revision = 1;
+ const target = window.__STORAGE_LAB__.native;
+ const method = target.setItem;
+ const state = window.__MIRROR_WRITER__;
+ const work = state.dispatch([['myTradingView.realm', value]]);
+ value.revision = 2;
+ await Promise.all(work);
+ iframe.remove();
+ return { value: await target.getItem('myTradingView.realm'), sameMethod: method === target.setItem, stats: state.writer.getStats(), nativeCalls: state.nativeCalls };
+ });
+
+ // Then the accepted snapshot is isolated from later caller changes.
+ expect(result.value).toEqual({ revision: 1 });
+ expect(result.sameMethod).toBe(true);
+ expect(result.nativeCalls).toBe(0);
+ expect(result.stats).toMatchObject({ acceptedBatches: 1, committedWrites: 1 });
+});
+
+test('user resumes native writes exactly once after a closed IndexedDB connection', async ({ page }) => {
+ // Given localForage owns a connection that has really been closed.
+ await openProductionMirrorWriterLab(page);
+ await page.evaluate(async () => {
+ await window.__STORAGE_LAB__.native.ready();
+ window.__STORAGE_LAB__.native._dbInfo.db.close();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When transaction creation fails before any mirror transaction was accepted.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const outcomes = await Promise.allSettled(state.dispatch([['myTradingView.reopened', 42]]));
+ await state.writer.stop();
+ return { status: outcomes.map(outcome => outcome.status), value: await window.__STORAGE_LAB__.native.getItem('myTradingView.reopened'),
+ stats: state.writer.getStats(), nativeCalls: state.nativeCalls };
+ });
+
+ // Then the real library reopens its connection and commits the original native map once.
+ expect(result.status).toEqual(['fulfilled']);
+ expect(result.value).toBe(42);
+ expect(result.nativeCalls).toBe(1);
+ expect(result.stats).toMatchObject({ transactions: 0, failedBatches: 0, nativePassthroughBatches: 1, pendingBatches: 0 });
+});
+
+for (const kind of ['driver', 'database configuration', 'ready rejection']) {
+ test(`user preserves the native result after a ${kind} mismatch before transaction creation`, async ({ page }) => {
+ // Given a real localForage instance does not satisfy the mirror destination contract.
+ await openProductionMirrorWriterLab(page);
+
+ // When the writer encounters the actual configured driver or readiness failure.
+ const result = await page.evaluate(async kind => {
+ const lab = window.__STORAGE_LAB__;
+ const state = window.__MIRROR_WRITER__;
+ const target = kind === 'driver' ? lab.create('chart_futures', { driver: lab.localforage.LOCALSTORAGE })
+ : kind === 'database configuration' ? lab.create('other_chart') : lab.create('chart_futures', { driver: 'not-an-installed-driver' });
+ const outcomes = await Promise.allSettled(state.dispatch([['myTradingView.native', 42]], target));
+ await state.writer.stop();
+ return { outcomes: outcomes.map(outcome => ({ status: outcome.status, message: outcome.reason?.message })),
+ value: kind === 'ready rejection' ? null : await target.getItem('myTradingView.native'),
+ stats: state.writer.getStats(), nativeCalls: state.nativeCalls };
+ }, kind);
+
+ // Then only the original native expression determines the saved value or rejection.
+ expect(result.nativeCalls).toBe(1);
+ expect(result.stats).toMatchObject({ transactions: 0, nativePassthroughBatches: 1, pendingBatches: 0, pendingBytes: 0 });
+ if (kind === 'ready rejection') {
+ expect(result.outcomes).toEqual([{ status: 'rejected', message: 'No available storage method found.' }]);
+ } else {
+ expect(result.outcomes).toEqual([{ status: 'fulfilled', message: undefined }]);
+ expect(result.value).toBe(42);
+ }
+ });
+}
+
+test('user keeps transaction aborts atomic and routes later calls through the untouched native API', async ({ page }) => {
+ // Given the actual IndexedDB transaction is aborted after its first successful put.
+ await openProductionMirrorWriterLab(page);
+ await page.evaluate(async () => {
+ await window.__STORAGE_LAB__.native.setItem('myTradingView.first', 0);
+ window.__STORAGE_NATIVE__.reset();
+ window.__STORAGE_NATIVE__.abortNextPut();
+ });
+
+ // When the admitted batch aborts and a later mirror is submitted.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const outcomes = await Promise.allSettled(state.dispatch([['myTradingView.first', 1], ['myTradingView.second', 2]]));
+ const first = await window.__STORAGE_LAB__.native.getItem('myTradingView.first');
+ const before = state.writer.getStats();
+ const nativeResult = [window.__STORAGE_LAB__.native.setItem('myTradingView.later', 3)];
+ let nativeCalls = 0;
+ const returned = state.writer.dispatch(window.__STORAGE_LAB__.native, [['myTradingView.later', 3]], () => { nativeCalls += 1; return nativeResult; });
+ await Promise.all(returned);
+ return { status: outcomes.map(outcome => outcome.status), first, noReplay: state.nativeCalls, nativeCalls,
+ originalReturn: returned === nativeResult, before, after: state.writer.getStats(), keys: (await window.__STORAGE_LAB__.native.keys()).sort() };
+ });
+
+ // Then rollback is reported once and the stopped writer no longer changes optimization statistics.
+ expect(result.status).toEqual(['rejected']);
+ expect(result.first).toBe(0);
+ expect(result.noReplay).toBe(0);
+ expect(result.nativeCalls).toBe(1);
+ expect(result.originalReturn).toBe(true);
+ expect(result.keys).toEqual(['myTradingView.first', 'myTradingView.later']);
+ expect(result.before).toMatchObject({ failedBatches: 1, abortedTransactions: 1, committedWrites: 0, pendingBatches: 0 });
+ expect(result.after).toEqual(result.before);
+});
+
+for (const kind of ['batch count', 'snapshot bytes']) {
+ test(`user drains native work over pending ${kind} capacity before stop releases later writes`, async ({ page }) => {
+ // Given a real write lock holds the only allowed optimization batch.
+ await openProductionMirrorWriterLab(page, { limits: kind === 'batch count' ? { maxPendingBatches: 1 } : { maxPendingBytes: 40 } });
+ await page.evaluate(async () => { await window.__STORAGE_LAB__.native.ready(); });
+ await holdMirrorWriteLock(page);
+ await page.evaluate(() => {
+ const state = window.__MIRROR_WRITER__;
+ window.__STORAGE_NATIVE__.reset();
+ state.first = Promise.all(state.dispatch([['myTradingView.first', 1]]));
+ });
+ await expect.poll(() => page.evaluate(() => window.__STORAGE_NATIVE__.snapshot().transactions)).toBe(1);
+
+ // When another batch uses native storage and stop is followed by one final mirror.
+ const during = await page.evaluate(() => {
+ const state = window.__MIRROR_WRITER__;
+ state.second = Promise.all(state.dispatch([['myTradingView.second', 2]]));
+ state.stopped = false;
+ state.stopping = state.writer.stop().then(() => { state.stopped = true; });
+ state.later = Promise.all(state.dispatch([['myTradingView.later', 3]]));
+ return { stats: state.writer.getStats(), nativeCalls: state.nativeCalls, stopped: state.stopped };
+ });
+ await releaseMirrorWriteLock(page);
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ await Promise.all([state.first, state.second, state.stopping, state.later]);
+ return { stats: state.writer.getStats(), nativeCalls: state.nativeCalls, stopped: state.stopped,
+ values: await Promise.all(['first', 'second', 'later'].map(key => window.__STORAGE_LAB__.native.getItem(`myTradingView.${key}`))) };
+ });
+
+ // Then no save is discarded and the fence includes the native work accepted before stop.
+ expect(during.stopped).toBe(false);
+ expect(during.nativeCalls).toBe(1);
+ expect(during.stats).toMatchObject({ acceptedBatches: 1, rejectedBatches: 1, pendingBatches: 2, nativePassthroughBatches: 1 });
+ expect(result.values).toEqual([1, 2, 3]);
+ expect(result.stopped).toBe(true);
+ expect(result.nativeCalls).toBe(2);
+ expect(result.stats).toMatchObject({ pendingBatches: 0, pendingBytes: 0, nativePassthroughBatches: 1, failedBatches: 0 });
+ });
+}
+
+test('user preserves missing and explicit null chart keys without disturbing target-only records', async ({ page }) => {
+ // Given native storage contains an explicit null and records outside this mirror.
+ await openProductionMirrorWriterLab(page);
+ await page.evaluate(async () => {
+ const target = window.__STORAGE_LAB__.native;
+ await Promise.all([target.setItem('myTradingView.present', null), target.setItem('myTradingView.targetOnly', 7), target.setItem('candlestick-setting', 8)]);
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When a missing null key is mirrored twice and an empty batch follows.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const entries = [['myTradingView.present', null], ['myTradingView.missing', null]];
+ await Promise.all(state.dispatch(entries));
+ await Promise.all(state.dispatch(entries));
+ const before = state.writer.getStats();
+ const empty = state.dispatch([]);
+ return { before, after: state.writer.getStats(), empty, nativeCalls: state.nativeCalls,
+ metrics: window.__STORAGE_NATIVE__.snapshot(), keys: (await window.__STORAGE_LAB__.native.keys()).sort(),
+ values: await Promise.all(['myTradingView.present', 'myTradingView.missing', 'myTradingView.targetOnly', 'candlestick-setting'].map(key => window.__STORAGE_LAB__.native.getItem(key))) };
+ });
+
+ // Then one new null record is committed while repeats and the empty batch make no writes.
+ expect(result.values).toEqual([null, null, 7, 8]);
+ expect(result.keys).toEqual(['candlestick-setting', 'myTradingView.missing', 'myTradingView.present', 'myTradingView.targetOnly']);
+ expect(result.metrics).toMatchObject({ transactions: 2, puts: 1, completed: 2, aborted: 0 });
+ expect(result.before).toMatchObject({ committedWrites: 1, skippedWrites: 3, pendingBatches: 0 });
+ expect(result.after).toEqual(result.before);
+ expect(result.empty).toEqual([]);
+ expect(result.nativeCalls).toBe(0);
+});
+
+test('user drains accepted work when input inspection synchronously requests stop', async ({ page }) => {
+ // Given input inspection can synchronously call the exposed stop hook.
+ await openProductionMirrorWriterLab(page);
+ await page.evaluate(async () => { await window.__STORAGE_LAB__.native.ready(); });
+
+ // When inspecting the snapshot triggers stop during the original dispatch.
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ let stop;
+ const value = new Proxy({ revision: 1 }, { getPrototypeOf(target) { stop = state.writer.stop(); return Reflect.getPrototypeOf(target); } });
+ const outcomes = await Promise.allSettled(state.dispatch([['myTradingView.reentrant', value]]));
+ await stop;
+ return { status: outcomes.map(outcome => outcome.status), nativeCalls: state.nativeCalls,
+ value: await window.__STORAGE_LAB__.native.getItem('myTradingView.reentrant'), stats: state.writer.getStats() };
+ });
+
+ // Then the work accepted before inspection completes and stop does not discard its snapshot.
+ expect(result.status).toEqual(['fulfilled']);
+ expect(result.value).toEqual({ revision: 1 });
+ expect(result.nativeCalls).toBe(0);
+ expect(result.stats).toMatchObject({ acceptedBatches: 1, committedWrites: 1, pendingBatches: 0 });
+});
+
+test('user drains every native map member after one member rejects', async ({ page }) => {
+ // Given a real write lock delays a valid native save while another native value cannot clone.
+ await openProductionMirrorWriterLab(page, { limits: { maxEntries: 1 } });
+ await page.evaluate(async () => { await window.__STORAGE_LAB__.native.ready(); });
+ await holdMirrorWriteLock(page);
+
+ // When a rejected native member settles before the other pending member and stop is requested.
+ const during = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ state.results = Promise.allSettled(state.dispatch([['myTradingView.invalid', () => 1], ['myTradingView.valid', 2]]));
+ state.results.then(outcomes => { state.earlyOutcome = outcomes[0].status; });
+ state.stopped = false;
+ state.stopping = state.writer.stop().then(() => { state.stopped = true; });
+ await window.__MIRROR_WRITE_LOCK__.progress();
+ return { stopped: state.stopped, earlyOutcome: state.earlyOutcome, stats: state.writer.getStats(), nativeCalls: state.nativeCalls };
+ });
+ await releaseMirrorWriteLock(page);
+ const result = await page.evaluate(async () => {
+ const state = window.__MIRROR_WRITER__;
+ const outcomes = await state.results;
+ await state.stopping;
+ return { status: outcomes.map(outcome => outcome.status), stopped: state.stopped, nativeCalls: state.nativeCalls,
+ value: await window.__STORAGE_LAB__.native.getItem('myTradingView.valid'), stats: state.writer.getStats() };
+ });
+
+ // Then stop waits for the successful member and neither native promise is replayed.
+ expect(during.stopped).toBe(false);
+ expect(during.earlyOutcome).toBe('rejected');
+ expect(during.nativeCalls).toBe(1);
+ expect(during.stats.pendingBatches).toBe(1);
+ expect(result.status).toEqual(['rejected']);
+ expect(result.value).toBe(2);
+ expect(result.stopped).toBe(true);
+ expect(result.nativeCalls).toBe(1);
+ expect(result.stats).toMatchObject({ pendingBatches: 0, pendingBytes: 0 });
+});
diff --git a/e2e/binance-orderbook/specs/chart-storage-prototype.pw.js b/e2e/binance-orderbook/specs/chart-storage-prototype.pw.js
new file mode 100644
index 0000000..07cea9e
--- /dev/null
+++ b/e2e/binance-orderbook/specs/chart-storage-prototype.pw.js
@@ -0,0 +1,1132 @@
+import { test, expect } from '../test.js';
+import { writeFile } from 'node:fs/promises';
+import { openStorageLab, openStorageRuntime, initializeStorageLab, displayStorageResult, STORAGE_LAB_ORIGIN } from '../helpers/chart-storage-lab.js';
+
+test('user repeats 290 historical chart saves with bounded transactions and no unchanged writes', async ({ page }, testInfo) => {
+ // Given the actual fixture library has persisted 290 synthetic historical chart records.
+ await openStorageLab(page, { install: process.env.STORAGE_PROTOTYPE_BASELINE !== '1' });
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.records = Array.from({ length: 290 }, (_, index) => ({
+ key: `#TV_SYMBOL-LAB${index}USDT`,
+ value: { symbol: `LAB${index}USDT`, interval: '15', studies: [{ name: 'volume', inputs: [20, true] }] },
+ }));
+ await Promise.all(lab.records.map(({ key, value }) => lab.native.setItem(key, value)));
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When eight complete save rounds read and write the same chart state through Promise and callback APIs.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const callbacks = [];
+ const values = [];
+ for (let round = 0; round < 8; round += 1) {
+ values.push(await Promise.all(lab.records.map(async ({ key, value }, index) => {
+ const callback = kind => (error, actual) => {
+ callbacks.push({ id: `${round}:${index}:${kind}`, error: error === null, value: actual });
+ };
+ const read = await lab.store.getItem(key, callback('get'));
+ const written = await lab.store.setItem(key, read, callback('set'));
+ return { readMatches: JSON.stringify(read) === JSON.stringify(value), writeMatches: JSON.stringify(written) === JSON.stringify(value) };
+ })));
+ }
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ const stats = lab.controller ? lab.controller.getStats() : null;
+ const persisted = await Promise.all(lab.records.map(({ key }) => lab.native.getItem(key)));
+ return {
+ metrics, stats, identity: lab.cacheIdentity(),
+ valuesMatch: values.flat().every(value => value.readMatches && value.writeMatches),
+ callbackCount: callbacks.length,
+ uniqueCallbacks: new Set(callbacks.map(({ id }) => id)).size,
+ callbackValuesMatch: callbacks.every(({ id, error, value }) => error
+ && JSON.stringify(value) === JSON.stringify(lab.records[Number(id.split(':')[1])].value)),
+ persistedMatches: persisted.every((value, index) => JSON.stringify(value) === JSON.stringify(lab.records[index].value)),
+ };
+ });
+ await displayStorageResult(page, result);
+ const measurementPath = testInfo.outputPath('storage-measurement.json');
+ await writeFile(measurementPath, JSON.stringify(result, null, 2));
+ await testInfo.attach('storage-measurement.json', { path: measurementPath, contentType: 'application/json' });
+ const screenshotPath = testInfo.outputPath('storage-lab.png');
+ await page.screenshot({ path: screenshotPath, fullPage: true });
+ await testInfo.attach('storage-lab.png', { path: screenshotPath, contentType: 'image/png' });
+
+ // Then every value and callback is preserved while actual database work remains bounded.
+ expect(result.identity).toEqual({ sameExport: true, factoryExecutions: 1 });
+ expect(result.valuesMatch).toBe(true);
+ expect(result.persistedMatches).toBe(true);
+ expect(result.callbackCount).toBe(290 * 8 * 2);
+ expect(result.uniqueCallbacks).toBe(290 * 8 * 2);
+ expect(result.callbackValuesMatch).toBe(true);
+ expect(result.metrics.transactions).toBeLessThanOrEqual(32);
+ expect(result.metrics.puts).toBe(0);
+});
+
+test('user captures the first registered storage export without executing its factory twice', async ({ page }) => {
+ // Given the observer is installed between the real runtime and first module registration.
+ await openStorageLab(page, { capture: true });
+
+ // When the host requires the module and the observer delivers the captured export.
+ const result = await page.evaluate(async () => ({
+ capturedSameExport: await window.__STORAGE_CAPTURE__.captured === window.__STORAGE_LAB__.localforage,
+ pushRestored: self.webpackChunkfutures_trade_ui.push === window.__STORAGE_CAPTURE_ORIGINAL_PUSH__,
+ identity: window.__STORAGE_LAB__.cacheIdentity(),
+ }));
+
+ // Then capture shares the host's exact export and real runtime cache identity.
+ expect(result).toEqual({ capturedSameExport: true, pushRestored: true, identity: { sameExport: true, factoryExecutions: 1 } });
+});
+
+test('user receives an explicit refusal when module capture starts after registration', async ({ page }) => {
+ // Given the real storage module is already registered and required by the host.
+ await openStorageLab(page);
+
+ // When a new observer attempts to capture the existing module.
+ const result = await page.evaluate(async () => {
+ const { observeChartStorageModule } = await import('/capture.js');
+ try {
+ const observer = observeChartStorageModule();
+ await observer.captured;
+ return { rejected: false };
+ } catch (error) {
+ return { rejected: true, message: error.message, identity: window.__STORAGE_LAB__.cacheIdentity() };
+ }
+ });
+
+ // Then the unavailable capture fails explicitly without re-executing the host factory.
+ expect(result.rejected).toBe(true);
+ expect(result.message).toMatch(/already|late|registered/i);
+ expect(result.identity).toEqual({ sameExport: true, factoryExecutions: 1 });
+});
+
+test('user preserves cross-instance call order before readiness and native non-target storage', async ({ page }) => {
+ // Given fresh futures and delivery instances have not initialized their real databases.
+ await openStorageLab(page);
+
+ // When callers issue interleaved operations before ready and another database stores a native Blob.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const first = lab.create();
+ const second = first.createInstance({ name: 'chart_futures', storeName: 'keyvaluepairs', driver: lab.localforage.INDEXEDDB });
+ const setter = first.setItem;
+ const delivery = lab.create('chart_delivery');
+ const key = '#TV_SYMBOL-ORDER';
+ const sequence = await Promise.all([
+ first.setItem(key, { value: 'first' }),
+ second.getItem(key),
+ second.setItem(key, { value: 'second' }),
+ first.getItem(key),
+ delivery.setItem(key, { value: 'delivery' }),
+ ]);
+ await first.ready();
+ const other = lab.create('chart_other');
+ const blob = new Blob(['native-binary'], { type: 'text/plain' });
+ await other.setItem(key, blob);
+ const binary = await other.getItem(key);
+ return {
+ sequence, wrapperPreserved: setter === first.setItem,
+ delivery: await delivery.getItem(key), nativeBlobText: await binary.text(), nativeBlobType: binary.type,
+ };
+ });
+
+ // Then shared-store operations retain issue order while delivery and native databases stay independent.
+ expect(result).toEqual({
+ sequence: [{ value: 'first' }, { value: 'first' }, { value: 'second' }, { value: 'second' }, { value: 'delivery' }],
+ wrapperPreserved: true, delivery: { value: 'delivery' }, nativeBlobText: 'native-binary', nativeBlobType: 'text/plain',
+ });
+});
+
+test('user observes every A to B to A transition and ordered native barriers', async ({ page }) => {
+ // Given the real database initially contains chart state A.
+ await openStorageLab(page);
+ await page.evaluate(() => window.__STORAGE_LAB__.native.setItem('#TV_SYMBOL-ABA', 'A'));
+
+ // When one caller interleaves state changes, reads, enumeration, deletion, and an unmanaged key.
+ const result = await page.evaluate(async () => {
+ const store = window.__STORAGE_LAB__.create();
+ return Promise.all([
+ store.getItem('#TV_SYMBOL-ABA'),
+ store.setItem('#TV_SYMBOL-ABA', 'B'),
+ store.getItem('#TV_SYMBOL-ABA'),
+ store.setItem('#TV_SYMBOL-ABA', 'A'),
+ store.getItem('#TV_SYMBOL-ABA'),
+ store.keys(),
+ store.removeItem('#TV_SYMBOL-ABA'),
+ store.getItem('#TV_SYMBOL-ABA'),
+ store.setItem('unmanaged-setting', { enabled: true }),
+ store.getItem('unmanaged-setting'),
+ store.clear(),
+ store.keys(),
+ ]);
+ });
+
+ // Then each intermediate result follows issue order and clear leaves no stored keys.
+ expect(result).toEqual([
+ 'A', 'B', 'B', 'A', 'A', ['#TV_SYMBOL-ABA'], undefined, null,
+ { enabled: true }, { enabled: true }, undefined, [],
+ ]);
+});
+
+test('user creates a present null record and suppresses only its repeated null write', async ({ page }) => {
+ // Given the chart store is empty and its native connection is ready.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ window.__STORAGE_LAB__.store = window.__STORAGE_LAB__.create();
+ await window.__STORAGE_LAB__.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When a missing chart key receives two explicit null saves.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const values = await Promise.all([
+ lab.store.getItem('myTradingView-null'),
+ lab.store.setItem('myTradingView-null', null),
+ lab.store.setItem('myTradingView-null', null),
+ ]);
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ return { values, metrics, keys: await lab.native.keys(), stats: lab.controller.getStats() };
+ });
+
+ // Then null is a persisted present key and only the unchanged second save is skipped.
+ expect(result.values).toEqual([null, null, null]);
+ expect(result.keys).toEqual(['myTradingView-null']);
+ expect(result.metrics.puts).toBe(1);
+ expect(result.stats.skippedWrites).toBe(1);
+});
+
+test('user reads completed writes from another page and retains them after reload', async ({ page, context }) => {
+ // Given two isolated host pages share only the real same-origin IndexedDB database.
+ await openStorageLab(page);
+ const second = await context.newPage();
+ await openStorageLab(second);
+
+ // When the first page commits A and the second page reads A before committing B.
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.sharedStore = lab.create();
+ await lab.sharedStore.setItem('#TV_SYMBOL-SHARED', { revision: 'A' });
+ await lab.sharedStore.getItem('#TV_SYMBOL-SHARED');
+ });
+ const secondRead = await second.evaluate(async () => {
+ const store = window.__STORAGE_LAB__.create();
+ const prior = await store.getItem('#TV_SYMBOL-SHARED');
+ await store.setItem('#TV_SYMBOL-SHARED', { revision: 'B' });
+ return prior;
+ });
+ const firstRead = await page.evaluate(() => window.__STORAGE_LAB__.sharedStore.getItem('#TV_SYMBOL-SHARED'));
+ await page.reload();
+ await initializeStorageLab(page);
+ const reloaded = await page.evaluate(() => window.__STORAGE_LAB__.create().getItem('#TV_SYMBOL-SHARED'));
+
+ // Then the later completed write is visible across pages and survives a real document reload.
+ expect(secondRead).toEqual({ revision: 'A' });
+ expect(firstRead).toEqual({ revision: 'B' });
+ expect(reloaded).toEqual({ revision: 'B' });
+ await second.close();
+});
+
+test('user receives batch failures after a real transaction abort with every prior value restored', async ({ page }) => {
+ // Given two committed records exist and the next native put will trigger a real IDB transaction abort.
+ await openStorageLab(page);
+ const pageErrors = [];
+ page.on('pageerror', error => pageErrors.push(error.message));
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await Promise.all(['A', 'B'].map(key => lab.native.setItem(`#TV_SYMBOL-${key}`, { revision: 1 })));
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ window.__STORAGE_NATIVE__.abortNextPut();
+ });
+
+ // When two writes and a read are accepted into the aborting batch.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const callbacks = [];
+ const callback = (error, value) => callbacks.push({ error: error instanceof Error || error instanceof DOMException, value });
+ const outcomes = await Promise.allSettled([
+ lab.store.setItem('#TV_SYMBOL-A', { revision: 2 }, callback),
+ lab.store.setItem('#TV_SYMBOL-B', { revision: 2 }, callback),
+ lab.store.getItem('#TV_SYMBOL-A', callback),
+ ]);
+ await lab.controller.drain();
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ return {
+ statuses: outcomes.map(value => value.status), names: outcomes.map(value => value.reason?.name), callbacks, metrics,
+ persisted: await Promise.all(['A', 'B'].map(key => lab.native.getItem(`#TV_SYMBOL-${key}`))),
+ };
+ });
+
+ // Then every accepted request rejects once and the actual database has rolled the entire batch back.
+ expect(result.statuses).toEqual(['rejected', 'rejected', 'rejected']);
+ expect(result.names).toEqual(['AbortError', 'AbortError', 'AbortError']);
+ expect(result.callbacks).toEqual([
+ { error: true, value: undefined }, { error: true, value: undefined }, { error: true, value: undefined },
+ ]);
+ expect(result.persisted).toEqual([{ revision: 1 }, { revision: 1 }]);
+ expect(result.metrics.aborted).toBe(1);
+ expect(result.metrics.pending).toBe(0);
+ expect(pageErrors).toEqual([]);
+});
+
+test('user receives success only after all accepted writes commit to the real database', async ({ page }) => {
+ // Given the chart store is ready and native transaction completion is being observed.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When a batch is saved and each returned Promise records the real transaction state at resolution.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const completions = await Promise.all(Array.from({ length: 20 }, (_, index) => (
+ lab.store.setItem(`#TV_SYMBOL-COMMIT${index}`, { index }).then(value => ({
+ value, native: window.__STORAGE_NATIVE__.snapshot(),
+ }))
+ )));
+ return { completions, keys: await lab.native.keys() };
+ });
+
+ // Then all twenty results arrive after native completion and all twenty keys are durable.
+ expect(result.completions.map(({ value }) => value)).toEqual(Array.from({ length: 20 }, (_, index) => ({ index })));
+ expect(result.completions.every(({ native }) => native.completed >= 1 && native.pending === 0)).toBe(true);
+ expect(result.keys).toHaveLength(20);
+});
+
+test('user receives explicit capacity rejection without losing any accepted save', async ({ page }) => {
+ // Given the isolated adapter accepts at most two outstanding chart operations.
+ await openStorageLab(page, { options: { maxPendingOperations: 2 } });
+
+ // When three writes arrive synchronously before database readiness can finish.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const store = lab.create();
+ const outcomes = await Promise.allSettled([0, 1, 2].map(index => store.setItem(`#TV_SYMBOL-LIMIT${index}`, index)));
+ await lab.controller.drain();
+ return {
+ outcomes: outcomes.map(outcome => ({ status: outcome.status, value: outcome.value, message: outcome.reason?.message })),
+ persisted: await Promise.all([0, 1, 2].map(index => lab.native.getItem(`#TV_SYMBOL-LIMIT${index}`))),
+ keys: await lab.native.keys(), stats: lab.controller.getStats(),
+ };
+ });
+
+ // Then the first two writes remain durable and the third fails explicitly instead of disappearing.
+ expect(result.outcomes.map(({ status }) => status)).toEqual(['fulfilled', 'fulfilled', 'rejected']);
+ expect(result.outcomes[2].message).toMatch(/capacity|limit|pending|queue/i);
+ expect(result.persisted).toEqual([0, 1, null]);
+ expect(result.keys.sort()).toEqual(['#TV_SYMBOL-LIMIT0', '#TV_SYMBOL-LIMIT1']);
+ expect(result.stats.pendingOperations).toBe(0);
+ expect(result.stats.pendingBytes).toBe(0);
+ expect(result.stats.peakPendingOperations).toBe(2);
+ expect(result.stats.rejectedOperations).toBe(1);
+});
+
+test('user rejects oversized pending data while a subsequent bounded save remains durable', async ({ page }) => {
+ // Given pending JSON input is limited to sixty-four bytes.
+ await openStorageLab(page, { options: { maxPendingBytes: 64 } });
+
+ // When an oversized chart string is followed by a small valid chart number.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const store = lab.create();
+ const outcomes = await Promise.allSettled([
+ store.setItem('#TV_SYMBOL-BIG', 'x'.repeat(1024)),
+ store.setItem('#TV_SYMBOL-SMALL', 7),
+ ]);
+ await lab.controller.drain();
+ return {
+ statuses: outcomes.map(outcome => outcome.status),
+ keys: await lab.native.keys(), value: await lab.native.getItem('#TV_SYMBOL-SMALL'), stats: lab.controller.getStats(),
+ };
+ });
+
+ // Then only the bounded save exists and every pending accounting reservation is released.
+ expect(result.statuses).toEqual(['rejected', 'fulfilled']);
+ expect(result.keys).toEqual(['#TV_SYMBOL-SMALL']);
+ expect(result.value).toBe(7);
+ expect(result.stats.pendingBytes).toBe(0);
+ expect(result.stats.pendingOperations).toBe(0);
+});
+
+test('user drains accepted work during stop and retained facades continue with native storage', async ({ page }) => {
+ // Given two operations are accepted before the new chart database becomes ready.
+ await openStorageLab(page);
+
+ // When stop is requested while the accepted write and read are still queued.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const store = lab.create();
+ const accepted = [store.setItem('#TV_SYMBOL-STOP', { saved: true }), store.getItem('#TV_SYMBOL-STOP')];
+ const stopping = lab.controller.stop();
+ await stopping;
+ const values = await Promise.all(accepted);
+ const retained = await Promise.allSettled([
+ store.getItem('#TV_SYMBOL-STOP'), store.setItem('#TV_SYMBOL-LATE', 1), store.keys(),
+ ]);
+ const restored = lab.localforage.createInstance === lab.nativeCreateInstance;
+ const fresh = lab.create();
+ await fresh.setItem('#TV_SYMBOL-NATIVE', 2);
+ return {
+ values, restored, statuses: retained.map(outcome => outcome.status),
+ keys: await lab.native.keys(), stats: lab.controller.getStats(),
+ saved: await lab.native.getItem('#TV_SYMBOL-STOP'), nativeSaved: await lab.native.getItem('#TV_SYMBOL-NATIVE'),
+ };
+ });
+
+ // Then stop preserves queued work, allows retained-facade calls, and restores native factory behavior.
+ expect(result.values).toEqual([{ saved: true }, { saved: true }]);
+ expect(result.restored).toBe(true);
+ expect(result.statuses).toEqual(['fulfilled', 'fulfilled', 'fulfilled']);
+ expect(result.keys.sort()).toEqual(['#TV_SYMBOL-LATE', '#TV_SYMBOL-NATIVE', '#TV_SYMBOL-STOP']);
+ expect(result.saved).toEqual({ saved: true });
+ expect(result.nativeSaved).toBe(2);
+ expect(result.stats.pendingOperations).toBe(0);
+ expect(result.stats.pendingBytes).toBe(0);
+});
+
+test('user cannot mutate managed configuration or submit values outside the JSON contract', async ({ page }) => {
+ // Given a managed store exposes a configuration snapshot for inspection.
+ await openStorageLab(page);
+
+ // When callers mutate a snapshot and attempt configuration, driver, database, and non-JSON changes.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const store = lab.create();
+ const snapshot = store.config();
+ snapshot.name = 'changed-outside';
+ const cycle = {};
+ cycle.self = cycle;
+ const actions = [
+ () => store.config({ name: 'changed-inside' }),
+ () => store.setDriver(lab.localforage.LOCALSTORAGE),
+ () => store.dropInstance(),
+ ...[undefined, NaN, Infinity, new Date(0), new Blob(['binary']), cycle]
+ .map(value => () => store.setItem('#TV_SYMBOL-INVALID', value)),
+ ];
+ const outcomes = await Promise.allSettled(actions.map(action => Promise.resolve().then(action)));
+ return {
+ name: store.config().name,
+ statuses: outcomes.map(outcome => outcome.status),
+ messages: outcomes.map(outcome => outcome.reason?.message),
+ keys: await lab.native.keys(),
+ };
+ });
+
+ // Then invalid operations fail explicitly without changing database identity or creating a key.
+ expect(result.name).toBe('chart_futures');
+ expect(result.statuses).toEqual(Array(9).fill('rejected'));
+ expect(result.messages.every(message => typeof message === 'string' && message.length > 0)).toBe(true);
+ expect(result.keys).toEqual([]);
+});
+
+test('user receives a closed-connection failure without an automatic reopen or retry', async ({ page }) => {
+ // Given the real connection used by a completed managed save is deliberately closed.
+ await openStorageLab(page);
+ const closed = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.setItem('#TV_SYMBOL-BEFORE-CLOSE', 1);
+ const count = window.__STORAGE_NATIVE__.closeTarget('chart_futures');
+ window.__STORAGE_NATIVE__.reset();
+ return count;
+ });
+
+ // When the retained facade attempts another chart save through that closed connection.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const outcomes = await Promise.allSettled([lab.store.setItem('#TV_SYMBOL-AFTER-CLOSE', 2)]);
+ await lab.controller.drain();
+ return {
+ status: outcomes[0].status, name: outcomes[0].reason?.name, message: outcomes[0].reason?.message,
+ metrics: window.__STORAGE_NATIVE__.snapshot(), stats: lab.controller.getStats(),
+ };
+ });
+
+ // Then the storage error is explicit and no hidden database open retries the failed write.
+ expect(closed).toBeGreaterThanOrEqual(1);
+ expect(result.status).toBe('rejected');
+ expect(result.message).toMatch(/clos|connection|transaction|invalid/i);
+ expect(result.metrics.opens).toBe(0);
+ expect(result.metrics.puts).toBe(0);
+ expect(result.stats.pendingOperations).toBe(0);
+});
+
+test('user splits accepted work at the configured batch size without losing a result', async ({ page }) => {
+ // Given a ready real chart store limits each batch to two operations.
+ await openStorageLab(page, { options: { maxBatchOperations: 2 } });
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When five saves are submitted together through the public storage facade.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const values = await Promise.all(Array.from({ length: 5 }, (_, index) => (
+ lab.store.setItem(`#TV_SYMBOL-BATCH${index}`, index)
+ )));
+ return { values, metrics: window.__STORAGE_NATIVE__.snapshot(), stats: lab.controller.getStats() };
+ });
+
+ // Then all results complete through exactly three real committed transactions.
+ expect(result.values).toEqual([0, 1, 2, 3, 4]);
+ expect(result.metrics.transactions).toBe(3);
+ expect(result.metrics.completed).toBe(3);
+ expect(result.metrics.puts).toBe(5);
+ expect(result.stats.pendingOperations).toBe(0);
+});
+
+test('user receives an explicit oversized-result failure and can still read a bounded value', async ({ page }) => {
+ // Given native storage contains a large value beyond the adapter's result-size contract.
+ await openStorageLab(page, { options: { maxResultBytes: 32 } });
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await lab.native.setItem('#TV_SYMBOL-LARGE-RESULT', 'x'.repeat(1024));
+ await lab.native.setItem('#TV_SYMBOL-SMALL-RESULT', 3);
+ lab.store = lab.create();
+ });
+
+ // When the caller reads the oversized record before reading the independent bounded record.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const outcome = await Promise.allSettled([lab.store.getItem('#TV_SYMBOL-LARGE-RESULT')]);
+ return {
+ status: outcome[0].status, message: outcome[0].reason?.message,
+ small: await lab.store.getItem('#TV_SYMBOL-SMALL-RESULT'),
+ originalLength: (await lab.native.getItem('#TV_SYMBOL-LARGE-RESULT')).length,
+ stats: lab.controller.getStats(),
+ };
+ });
+
+ // Then the oversized read fails without corrupting storage or blocking later valid work.
+ expect(result.status).toBe('rejected');
+ expect(result.message).toMatch(/result|size|byte|limit/i);
+ expect(result.small).toBe(3);
+ expect(result.originalLength).toBe(1024);
+ expect(result.stats.pendingOperations).toBe(0);
+});
+
+test('user saves an admission-time JSON snapshot without sharing mutable result objects', async ({ page }) => {
+ // Given a caller owns a nested JSON chart object.
+ await openStorageLab(page);
+
+ // When that object and later returned objects are mutated around asynchronous storage calls.
+ const result = await page.evaluate(async () => {
+ const store = window.__STORAGE_LAB__.create();
+ const input = { nested: [1] };
+ const saving = store.setItem('#TV_SYMBOL-SNAPSHOT', input);
+ input.nested[0] = 2;
+ const written = await saving;
+ const savedResult = structuredClone(written);
+ written.nested[0] = 3;
+ const read = await store.getItem('#TV_SYMBOL-SNAPSHOT');
+ const firstRead = structuredClone(read);
+ read.nested[0] = 4;
+ return { savedResult, firstRead, secondRead: await store.getItem('#TV_SYMBOL-SNAPSHOT') };
+ });
+
+ // Then persistence and every result retain the original snapshot instead of caller mutations.
+ expect(result).toEqual({ savedResult: { nested: [1] }, firstRead: { nested: [1] }, secondRead: { nested: [1] } });
+});
+
+test('user completes an external version upgrade and recovers storage only after a fresh page lifecycle', async ({ page }) => {
+ // Given a managed facade has committed through the library's shared native connection.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.setItem('#TV_SYMBOL-UPGRADE', 1);
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When an external native connection upgrades the database and the old facade attempts another save.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const previousVersion = window.__STORAGE_NATIVE__.version('chart_futures');
+ const upgrade = await new Promise((resolve, reject) => {
+ const request = indexedDB.open('chart_futures', previousVersion + 1);
+ let upgraded = false;
+ request.onupgradeneeded = () => { upgraded = true; };
+ request.onerror = () => reject(request.error);
+ request.onblocked = () => reject(new Error('The external version upgrade was blocked'));
+ request.onsuccess = () => {
+ const version = request.result.version;
+ request.result.close();
+ resolve({ upgraded, version });
+ };
+ });
+ const nativeAfterUpgrade = window.__STORAGE_NATIVE__.snapshot();
+ window.__STORAGE_NATIVE__.reset();
+ const outcome = await Promise.allSettled([lab.store.setItem('#TV_SYMBOL-UPGRADE', 2)]);
+ await lab.controller.stop();
+ return {
+ previousVersion, upgrade, nativeAfterUpgrade, status: outcome[0].status,
+ afterFailure: window.__STORAGE_NATIVE__.snapshot(),
+ factoryRestored: lab.localforage.createInstance === lab.nativeCreateInstance,
+ };
+ });
+
+ // Then the actual upgrade completes, the stale facade fails without reopening, and stop restores the factory.
+ expect(result.upgrade).toEqual({ upgraded: true, version: result.previousVersion + 1 });
+ expect(result.nativeAfterUpgrade.versionChanges).toBeGreaterThanOrEqual(1);
+ expect(result.status).toBe('rejected');
+ expect(result.afterFailure.opens).toBe(0);
+ expect(result.afterFailure.puts).toBe(0);
+ expect(result.factoryRestored).toBe(true);
+
+ // When a real reload creates a fresh library lifecycle against the upgraded database.
+ await page.reload();
+ await initializeStorageLab(page);
+ const recovered = await page.evaluate(async () => {
+ const store = window.__STORAGE_LAB__.create();
+ const before = await store.getItem('#TV_SYMBOL-UPGRADE');
+ await store.setItem('#TV_SYMBOL-UPGRADE', 3);
+ return { before, after: await store.getItem('#TV_SYMBOL-UPGRADE') };
+ });
+
+ // Then the last committed value survives and a fresh lifecycle can save again.
+ expect(recovered).toEqual({ before: 1, after: 3 });
+});
+
+test('user installs storage synchronously before the host creates its first instance', async ({ page }) => {
+ // Given capture and its synchronous adapter callback precede module registration and execution.
+ await openStorageLab(page, { capture: true, captureInstall: true, initialize: false });
+
+ // When the real host requires storage and immediately creates and uses its first instance.
+ const result = await page.evaluate(async () => {
+ let hostExport;
+ let store;
+ let firstSave;
+ let pendingBeforeRequireReturns;
+ let factoryExecutions = 0;
+ self.webpackChunkfutures_trade_ui.push([
+ ['codex-chart-storage-sync-host'], {}, require => {
+ const original = require.m[43917];
+ require.m[43917] = function (...args) {
+ factoryExecutions += 1;
+ return original.apply(this, args);
+ };
+ hostExport = require(43917);
+ store = hostExport.createInstance({
+ name: 'chart_futures', storeName: 'keyvaluepairs', driver: hostExport.INDEXEDDB,
+ });
+ firstSave = store.setItem('#TV_SYMBOL-SYNC-CAPTURE', { saved: true });
+ pendingBeforeRequireReturns = window.__STORAGE_CAPTURE_CONTROLLER__.getStats().pendingOperations;
+ },
+ ]);
+ await firstSave;
+ const capturedSameExport = await window.__STORAGE_CAPTURE__.captured === hostExport;
+ window.__STORAGE_NATIVE__.reset();
+ const repeated = await Promise.all(Array.from({ length: 12 }, () => (
+ store.setItem('#TV_SYMBOL-SYNC-CAPTURE', { saved: true })
+ )));
+ return {
+ pendingBeforeRequireReturns, factoryExecutions, capturedSameExport, repeated,
+ metrics: window.__STORAGE_NATIVE__.snapshot(),
+ saved: await store.getItem('#TV_SYMBOL-SYNC-CAPTURE'),
+ };
+ });
+
+ // Then the first host instance performs real batching and unchanged-write suppression.
+ expect(result.pendingBeforeRequireReturns).toBe(1);
+ expect(result.factoryExecutions).toBe(1);
+ expect(result.capturedSameExport).toBe(true);
+ expect(result.repeated).toEqual(Array.from({ length: 12 }, () => ({ saved: true })));
+ expect(result.metrics.transactions).toBe(1);
+ expect(result.metrics.puts).toBe(0);
+ expect(result.saved).toEqual({ saved: true });
+});
+
+test('user keeps every accepted save when one callback throws a caller error', async ({ page }) => {
+ // Given a ready managed store has an event listener for the deliberately thrown caller error.
+ await openStorageLab(page);
+ const reportedError = page.waitForEvent('pageerror', { predicate: error => error.message === 'Storage lab callback failure' });
+
+ // When one callback throws while three valid saves are completing.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const store = lab.create();
+ const callbacks = [];
+ const values = await Promise.all([0, 1, 2].map(index => store.setItem(`#TV_SYMBOL-CALLBACK${index}`, index, (error, value) => {
+ callbacks.push({ index, success: error === null, value });
+ if (index === 0) throw new Error('Storage lab callback failure');
+ })));
+ return {
+ values, callbacks,
+ persisted: await Promise.all([0, 1, 2].map(index => lab.native.getItem(`#TV_SYMBOL-CALLBACK${index}`))),
+ pending: lab.controller.getStats().pendingOperations,
+ };
+ });
+ const error = await reportedError;
+
+ // Then the error is reported separately and all three callbacks, promises, and committed values survive.
+ expect(error.message).toBe('Storage lab callback failure');
+ expect(result.values).toEqual([0, 1, 2]);
+ expect(result.callbacks).toEqual([
+ { index: 0, success: true, value: 0 }, { index: 1, success: true, value: 1 }, { index: 2, success: true, value: 2 },
+ ]);
+ expect(result.persisted).toEqual([0, 1, 2]);
+ expect(result.pending).toBe(0);
+});
+
+test('user splits chart saves at the configured byte limit while retaining every value', async ({ page }) => {
+ // Given each individual JSON save fits a hundred-byte batch but two saves do not.
+ await openStorageLab(page, { options: { maxBatchBytes: 100 } });
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When three sixty-character records are accepted together.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const values = await Promise.all([0, 1, 2].map(index => lab.store.setItem(`#TV_SYMBOL-BYTES${index}`, 'x'.repeat(60))));
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ return { values, metrics, persisted: await Promise.all([0, 1, 2].map(index => lab.native.getItem(`#TV_SYMBOL-BYTES${index}`))) };
+ });
+
+ // Then three bounded transactions preserve all saved and returned values.
+ expect(result.values).toEqual(Array(3).fill('x'.repeat(60)));
+ expect(result.persisted).toEqual(Array(3).fill('x'.repeat(60)));
+ expect(result.metrics.transactions).toBe(3);
+ expect(result.metrics.completed).toBe(3);
+ expect(result.metrics.puts).toBe(3);
+});
+
+test('user counts native barriers against pending capacity and preserves their place in order', async ({ page }) => {
+ // Given only two pending operations may be accepted before fresh database initialization.
+ await openStorageLab(page, { options: { maxPendingOperations: 2 } });
+
+ // When a save and keys barrier occupy capacity before a third save arrives.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const store = lab.create();
+ const outcomes = await Promise.allSettled([
+ store.setItem('#TV_SYMBOL-BARRIER-FIRST', 1), store.keys(), store.setItem('#TV_SYMBOL-BARRIER-LATE', 2),
+ ]);
+ await lab.controller.drain();
+ return {
+ statuses: outcomes.map(outcome => outcome.status),
+ barrierKeys: outcomes[1].value, persistedKeys: await lab.native.keys(), stats: lab.controller.getStats(),
+ };
+ });
+
+ // Then the barrier sees the first committed key and the over-capacity save explicitly fails.
+ expect(result.statuses).toEqual(['fulfilled', 'fulfilled', 'rejected']);
+ expect(result.barrierKeys).toEqual(['#TV_SYMBOL-BARRIER-FIRST']);
+ expect(result.persistedKeys).toEqual(['#TV_SYMBOL-BARRIER-FIRST']);
+ expect(result.stats.peakPendingOperations).toBe(2);
+ expect(result.stats.pendingOperations).toBe(0);
+});
+
+test('user stops capture after registration and restores the unexecuted original factory', async ({ page }) => {
+ // Given module registration has completed but the host has not required the storage factory.
+ await openStorageLab(page, { capture: true, initialize: false });
+
+ // When capture stops before the first host require.
+ const stopped = await page.evaluate(async () => {
+ const capture = window.__STORAGE_CAPTURE__;
+ const completion = capture.captured.then(() => ({ status: 'fulfilled' }), error => ({ status: 'rejected', message: error.message }));
+ capture.stop();
+ let restoredFactory;
+ self.webpackChunkfutures_trade_ui.push([
+ ['codex-chart-storage-stopped-host'], {}, require => {
+ restoredFactory = require.m[43917] === window.__STORAGE_ORIGINAL_MODULE_FACTORY__;
+ },
+ ]);
+ return {
+ completion: await completion,
+ restoredFactory,
+ restoredPush: self.webpackChunkfutures_trade_ui.push === window.__STORAGE_CAPTURE_ORIGINAL_PUSH__,
+ };
+ });
+ await initializeStorageLab(page, { install: false });
+ const identity = await page.evaluate(() => window.__STORAGE_LAB__.cacheIdentity());
+
+ // Then capture rejects explicitly and the host can execute the restored native factory exactly once.
+ expect(stopped.completion.status).toBe('rejected');
+ expect(stopped.completion.message).toMatch(/stop.*before.*execution/i);
+ expect(stopped.restoredFactory).toBe(true);
+ expect(stopped.restoredPush).toBe(true);
+ expect(identity).toEqual({ sameExport: true, factoryExecutions: 1 });
+});
+
+test('user receives the original factory error and capture restores every owned runtime hook', async ({ page }) => {
+ // Given the real runtime will execute an explicitly failing test-owned module factory.
+ await openStorageRuntime(page);
+
+ // When the host requires that factory through the capture observer.
+ const result = await page.evaluate(async () => {
+ const { observeChartStorageModule } = await import('/capture.js');
+ const queue = self.webpackChunkfutures_trade_ui;
+ const originalPush = queue.push;
+ let executions = 0;
+ const originalFactory = () => {
+ executions += 1;
+ throw new Error('Storage lab original factory failed');
+ };
+ const capture = observeChartStorageModule();
+ const captured = capture.captured.then(() => ({ status: 'fulfilled' }), error => ({ status: 'rejected', message: error.message }));
+ let runtimeRequire;
+ queue.push([['codex-chart-storage-failing-module'], { 43917: originalFactory }, require => { runtimeRequire = require; }]);
+ let hostError;
+ try { runtimeRequire(43917); } catch (error) { hostError = error.message; }
+ return {
+ hostError, captured: await captured, executions,
+ factoryRestored: runtimeRequire.m[43917] === originalFactory, pushRestored: queue.push === originalPush,
+ };
+ });
+
+ // Then both consumers receive the original error and no interception hook remains installed.
+ expect(result).toEqual({
+ hostError: 'Storage lab original factory failed',
+ captured: { status: 'rejected', message: 'Storage lab original factory failed' },
+ executions: 1, factoryRestored: true, pushRestored: true,
+ });
+});
+
+test('user receives a capture callback error and the real storage factory remains restored', async ({ page }) => {
+ // Given a capture callback deliberately fails after the actual localForage factory returns.
+ await openStorageRuntime(page);
+ await page.evaluate(async () => {
+ const { observeChartStorageModule } = await import('/capture.js');
+ const queue = self.webpackChunkfutures_trade_ui;
+ window.__CAPTURE_ERROR_ORIGINAL_PUSH__ = queue.push;
+ const capture = observeChartStorageModule(queue, { onCapture() { throw new Error('Storage lab onCapture failed'); } });
+ window.__CAPTURE_ERROR_RESULT__ = capture.captured.then(() => ({ status: 'fulfilled' }), error => ({ status: 'rejected', message: error.message }));
+ const observedPush = queue.push;
+ window.__CAPTURE_ERROR_OBSERVED_PUSH__ = observedPush;
+ queue.push = function (chunk) {
+ if (Object.hasOwn(chunk[1], '43917')) window.__CAPTURE_ERROR_ORIGINAL_FACTORY__ = chunk[1][43917];
+ return observedPush.call(this, chunk);
+ };
+ });
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/localforage-module.js` });
+
+ // When the host first requires the real module and handles the callback error explicitly.
+ const result = await page.evaluate(async () => {
+ const queue = self.webpackChunkfutures_trade_ui;
+ queue.push = window.__CAPTURE_ERROR_OBSERVED_PUSH__;
+ let runtimeRequire;
+ queue.push([['codex-chart-storage-capture-error'], {}, require => { runtimeRequire = require; }]);
+ let hostError;
+ try { runtimeRequire(43917); } catch (error) { hostError = error.message; }
+ return {
+ hostError, captured: await window.__CAPTURE_ERROR_RESULT__,
+ factoryRestored: runtimeRequire.m[43917] === window.__CAPTURE_ERROR_ORIGINAL_FACTORY__,
+ pushRestored: queue.push === window.__CAPTURE_ERROR_ORIGINAL_PUSH__,
+ };
+ });
+
+ // Then the callback failure rejects capture and leaves the real runtime's factory and queue restored.
+ expect(result).toEqual({
+ hostError: 'Storage lab onCapture failed',
+ captured: { status: 'rejected', message: 'Storage lab onCapture failed' },
+ factoryRestored: true, pushRestored: true,
+ });
+});
+
+test('user rejects an oversized unmanaged save before its native barrier can persist data', async ({ page }) => {
+ // Given a managed store limits any result to thirty-two bytes.
+ await openStorageLab(page, { options: { maxResultBytes: 32 } });
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When an unmanaged key attempts a native-barrier save whose returned value exceeds the limit.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const outcome = await Promise.allSettled([lab.store.setItem('unmanaged-large-value', 'x'.repeat(100))]);
+ return {
+ status: outcome[0].status, message: outcome[0].reason?.message,
+ metrics: window.__STORAGE_NATIVE__.snapshot(), keys: await lab.native.keys(),
+ value: await lab.native.getItem('unmanaged-large-value'),
+ };
+ });
+
+ // Then explicit rejection precedes every native put and no rejected value is stored.
+ expect(result.status).toBe('rejected');
+ expect(result.message).toMatch(/result|size|byte|limit/i);
+ expect(result.metrics.puts).toBe(0);
+ expect(result.keys).toEqual([]);
+ expect(result.value).toBe(null);
+});
+
+test('user completes eight simultaneous 290-key save rounds with every callback and bounded native work', async ({ page }, testInfo) => {
+ // Given the native library has committed 290 historical chart snapshots before observation begins.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.concurrentRecords = Array.from({ length: 290 }, (_, index) => ({
+ key: `#TV_SYMBOL-CONCURRENT${index}`, value: { symbol: `CONCURRENT${index}`, drawings: [index, { visible: true }] },
+ }));
+ await Promise.all(lab.concurrentRecords.map(({ key, value }) => lab.native.setItem(key, value)));
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When all eight rounds begin together and each read is followed by its corresponding unchanged save.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const callbacks = [];
+ const rounds = Array.from({ length: 8 }, (_, round) => Promise.all(lab.concurrentRecords.map(async ({ key, value }, index) => {
+ const callback = kind => (error, actual) => callbacks.push({
+ id: `${round}:${index}:${kind}`, success: error === null, matches: JSON.stringify(actual) === JSON.stringify(value),
+ });
+ const read = await lab.store.getItem(key, callback('get'));
+ const saved = await lab.store.setItem(key, read, callback('set'));
+ return JSON.stringify(read) === JSON.stringify(value) && JSON.stringify(saved) === JSON.stringify(value);
+ })));
+ const matches = await Promise.all(rounds);
+ const metrics = window.__STORAGE_NATIVE__.snapshot();
+ const stats = lab.controller.getStats();
+ const persisted = await Promise.all(lab.concurrentRecords.map(({ key }) => lab.native.getItem(key)));
+ return {
+ matches: matches.flat().every(Boolean), metrics, stats,
+ callbacks: callbacks.length, uniqueCallbacks: new Set(callbacks.map(({ id }) => id)).size,
+ callbackValuesMatch: callbacks.every(({ success, matches }) => success && matches),
+ persistedMatches: persisted.every((value, index) => JSON.stringify(value) === JSON.stringify(lab.concurrentRecords[index].value)),
+ };
+ });
+ await displayStorageResult(page, result);
+ const measurementPath = testInfo.outputPath('concurrent-storage-measurement.json');
+ await writeFile(measurementPath, JSON.stringify(result, null, 2));
+ await testInfo.attach('concurrent-storage-measurement.json', { path: measurementPath, contentType: 'application/json' });
+
+ // Then all 4640 distinct callbacks and results match while concurrent work remains batched and unchanged writes vanish.
+ expect(result.matches).toBe(true);
+ expect(result.callbackValuesMatch).toBe(true);
+ expect(result.persistedMatches).toBe(true);
+ expect(result.callbacks).toBe(4640);
+ expect(result.uniqueCallbacks).toBe(4640);
+ expect(result.metrics.transactions).toBeLessThanOrEqual(32);
+ expect(result.metrics.puts).toBe(0);
+ expect(result.stats.pendingOperations).toBe(0);
+ expect(result.stats.pendingBytes).toBe(0);
+});
+
+test('user retains upstream native connection recovery for an unmanaged barrier save', async ({ page }) => {
+ // Given the real connection closes after a managed chart save has committed.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.setItem('#TV_SYMBOL-BEFORE-BARRIER', 1);
+ window.__STORAGE_NATIVE__.closeTarget('chart_futures');
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When an unmanaged key uses localForage's native barrier path through the closed connection.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const value = await lab.store.setItem('unmanaged-recovery', { recovered: true });
+ return {
+ value, metrics: window.__STORAGE_NATIVE__.snapshot(),
+ persisted: await lab.native.getItem('unmanaged-recovery'),
+ previous: await lab.native.getItem('#TV_SYMBOL-BEFORE-BARRIER'),
+ };
+ });
+
+ // Then native localForage reopens and commits the barrier while preserving the earlier chart value.
+ expect(result.value).toEqual({ recovered: true });
+ expect(result.persisted).toEqual({ recovered: true });
+ expect(result.previous).toBe(1);
+ expect(result.metrics.opens).toBeGreaterThanOrEqual(1);
+ expect(result.metrics.puts).toBe(1);
+});
+
+test('user retains native barrier rejection even when its abort reason is null', async ({ page }) => {
+ // Given an unmanaged native record exists and its next put will abort a real transaction.
+ await openStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await lab.native.setItem('unmanaged-abort', 1);
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ window.__STORAGE_NATIVE__.abortNextPut();
+ });
+
+ // When the native barrier attempts to replace that record and localForage rejects with a null reason.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const outcome = await Promise.allSettled([lab.store.setItem('unmanaged-abort', 2)]);
+ return {
+ status: outcome[0].status, reason: outcome[0].reason,
+ persisted: await lab.native.getItem('unmanaged-abort'), metrics: window.__STORAGE_NATIVE__.snapshot(),
+ };
+ });
+
+ // Then a null rejection reason cannot become success and rollback preserves the original value.
+ expect(result.status).toBe('rejected');
+ expect(result.reason).toBe(null);
+ expect(result.persisted).toBe(1);
+ expect(result.metrics.aborted).toBe(1);
+});
+
+test('user receives real quota failures for every batched request while prior values remain durable', async ({ page, context }) => {
+ // Given the isolated browser has two committed records and a real sixty-four-KiB origin quota.
+ await openStorageRuntime(page);
+ const pageErrors = [];
+ page.on('pageerror', error => pageErrors.push(error.message));
+ const session = await context.newCDPSession(page);
+ try {
+ // Chromium caches bucket space from its first writes, so override quota before initialization.
+ await session.send('Storage.overrideQuotaForOrigin', { origin: STORAGE_LAB_ORIGIN, quotaSize: 64 * 1024 });
+ const quota = await session.send('Storage.getUsageAndQuota', { origin: STORAGE_LAB_ORIGIN });
+ expect(quota.overrideActive).toBe(true);
+ expect(quota.quota).toBe(64 * 1024);
+ await page.addScriptTag({ url: `${STORAGE_LAB_ORIGIN}/localforage-module.js` });
+ await initializeStorageLab(page);
+ await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ await Promise.all([
+ lab.native.setItem('#TV_SYMBOL-QUOTA-A', { revision: 1 }),
+ lab.native.setItem('#TV_SYMBOL-QUOTA-B', { revision: 1 }),
+ ]);
+ lab.store = lab.create();
+ await lab.store.ready();
+ window.__STORAGE_NATIVE__.reset();
+ });
+
+ // When a one-MiB JSON string and two related requests enter the same real transaction.
+ const result = await page.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
+ const chunks = Array.from({ length: 16 }, () => {
+ const bytes = crypto.getRandomValues(new Uint8Array(65536));
+ return Array.from(bytes, byte => alphabet[byte & 63]).join('');
+ });
+ const large = chunks.join('');
+ const callbacks = [];
+ const callback = id => (error, value) => callbacks.push({ id, name: error?.name, value });
+ const outcomes = await Promise.allSettled([
+ lab.store.setItem('#TV_SYMBOL-QUOTA-A', large, callback('large')),
+ lab.store.setItem('#TV_SYMBOL-QUOTA-B', { revision: 2 }, callback('small')),
+ lab.store.getItem('#TV_SYMBOL-QUOTA-A', callback('read')),
+ ]);
+ await lab.controller.drain();
+ return {
+ inputBytes: new TextEncoder().encode(JSON.stringify(large)).byteLength,
+ statuses: outcomes.map(outcome => outcome.status), names: outcomes.map(outcome => outcome.reason?.name),
+ callbacks, metrics: window.__STORAGE_NATIVE__.snapshot(), stats: lab.controller.getStats(),
+ persisted: await Promise.all([
+ lab.native.getItem('#TV_SYMBOL-QUOTA-A'), lab.native.getItem('#TV_SYMBOL-QUOTA-B'),
+ ]),
+ };
+ });
+
+ // Then real QuotaExceededError rejects every request once and rollback preserves both original records.
+ expect(result.inputBytes).toBe(1024 * 1024 + 2);
+ expect(result.statuses).toEqual(['rejected', 'rejected', 'rejected']);
+ expect(result.names).toEqual(['QuotaExceededError', 'QuotaExceededError', 'QuotaExceededError']);
+ expect(result.callbacks).toEqual([
+ { id: 'large', name: 'QuotaExceededError', value: undefined },
+ { id: 'small', name: 'QuotaExceededError', value: undefined },
+ { id: 'read', name: 'QuotaExceededError', value: undefined },
+ ]);
+ expect(result.persisted).toEqual([{ revision: 1 }, { revision: 1 }]);
+ expect(result.metrics.transactions).toBe(1);
+ expect(result.metrics.aborted).toBe(1);
+ expect(result.stats.pendingOperations).toBe(0);
+ expect(result.stats.pendingBytes).toBe(0);
+ expect(pageErrors).toEqual([]);
+ } finally {
+ try {
+ await session.send('Storage.overrideQuotaForOrigin', { origin: STORAGE_LAB_ORIGIN });
+ } finally {
+ await session.detach();
+ }
+ }
+});
+
+test('user competing across two pages writes the same new chart state only once', async ({ page, context }) => {
+ // Given both pages are ready and a native readwrite transaction holds their shared chart store lock.
+ await openStorageLab(page);
+ const second = await context.newPage();
+ await openStorageLab(second);
+ await page.evaluate(() => window.__STORAGE_LAB__.native.setItem('#TV_SYMBOL-CONTENDED', 'A'));
+ await Promise.all([page, second].map(target => target.evaluate(async () => {
+ const lab = window.__STORAGE_LAB__;
+ lab.store = lab.create();
+ await lab.store.ready();
+ })));
+ await page.evaluate(() => {
+ const database = window.__STORAGE_LAB__.native._dbInfo.db;
+ const transaction = database.transaction('keyvaluepairs', 'readwrite');
+ const store = transaction.objectStore('keyvaluepairs');
+ const control = { release: false, reads: 0 };
+ const completion = new Promise((resolve, reject) => {
+ transaction.addEventListener('complete', () => resolve('complete'), { once: true });
+ transaction.addEventListener('abort', () => reject(transaction.error), { once: true });
+ });
+ function continueHolding() {
+ const request = store.get('#TV_SYMBOL-CONTENDED');
+ request.onsuccess = () => {
+ control.reads += 1;
+ if (!control.release) continueHolding();
+ };
+ }
+ window.__STORAGE_CONTENTION_LOCK__ = { control, completion };
+ continueHolding();
+ });
+ try {
+ await expect.poll(() => page.evaluate(() => window.__STORAGE_CONTENTION_LOCK__.control.reads)).toBeGreaterThan(0);
+ await Promise.all([page, second].map(target => target.evaluate(() => window.__STORAGE_NATIVE__.reset())));
+
+ // When both pages create pending write transactions for B before the lock is explicitly released.
+ await Promise.all([page, second].map(target => target.evaluate(() => {
+ const lab = window.__STORAGE_LAB__;
+ lab.contendingSave = lab.store.setItem('#TV_SYMBOL-CONTENDED', 'B').then(
+ value => ({ status: 'fulfilled', value }), error => ({ status: 'rejected', name: error?.name }),
+ );
+ })));
+ await expect.poll(() => Promise.all([page, second].map(target => target.evaluate(() => (
+ window.__STORAGE_NATIVE__.snapshot().pending
+ ))))).toEqual([1, 1]);
+ await page.evaluate(async () => {
+ const lock = window.__STORAGE_CONTENTION_LOCK__;
+ lock.control.release = true;
+ await lock.completion;
+ });
+ const results = await Promise.all([page, second].map(target => target.evaluate(async () => ({
+ outcome: await window.__STORAGE_LAB__.contendingSave,
+ metrics: window.__STORAGE_NATIVE__.snapshot(),
+ }))));
+ const persisted = await page.evaluate(() => window.__STORAGE_LAB__.native.getItem('#TV_SYMBOL-CONTENDED'));
+
+ // Then IndexedDB serializes both comparisons and only one physical put is needed for both successful results.
+ expect(results.map(({ outcome }) => outcome)).toEqual([
+ { status: 'fulfilled', value: 'B' }, { status: 'fulfilled', value: 'B' },
+ ]);
+ expect(results.map(({ metrics }) => metrics.transactions)).toEqual([1, 1]);
+ expect(results.map(({ metrics }) => metrics.completed)).toEqual([1, 1]);
+ expect(results.reduce((sum, { metrics }) => sum + metrics.puts, 0)).toBe(1);
+ expect(results.map(({ metrics }) => metrics.pending)).toEqual([0, 0]);
+ expect(persisted).toBe('B');
+ } finally {
+ try {
+ await page.evaluate(async () => {
+ const lock = window.__STORAGE_CONTENTION_LOCK__;
+ lock.control.release = true;
+ await lock.completion;
+ });
+ } finally {
+ await second.close();
+ }
+ }
+});
diff --git a/experiments/binance-chart-storage/README.md b/experiments/binance-chart-storage/README.md
new file mode 100644
index 0000000..b09b754
--- /dev/null
+++ b/experiments/binance-chart-storage/README.md
@@ -0,0 +1,632 @@
+# Binance chart-storage experiment
+
+The reviewed production successor is now maintained independently in
+[`src/binance-orderbook-trade/chart-storage/`](../../src/binance-orderbook-trade/chart-storage/), with its
+[current contract](../../docs/binance-chart-storage-development.md). The files
+below retain the historical experiments and native baselines; their former
+production restrictions and remaining-work lists describe those experiments.
+
+This directory contains isolated experiments and an unpublished native-only
+preflight review artifact. No production userscript imports this code. The current
+candidate changes only a pinned mirror-write expression in module `70940`.
+The earlier global localForage facade is retained as historical research: it
+changes native mixed-operation ordering and is not the production candidate.
+The mirror writer defaults to `scope: 'lab'`, which rejects every origin except
+`https://chart-storage.test`. Its explicit `isolated-ususdt-probe` scope permits
+only the exact top-level `https://www.binance.com/zh-CN/futures/USUSDT` page.
+That guard cannot establish an anonymous profile: the separate probe harness
+must own a fresh Chrome process/profile with no login or extensions. The
+historical adapter and original capture remain lab-only. Bootstrap and passive
+preflight also permit the exact top-level USUSDT page; neither enables a writer
+by itself. No production entrypoint imports this experiment.
+
+## Current candidate: one mirror destination batch
+
+The global facade orders calls before waiting for localForage initialization.
+The captured native `setItem` prepares its value in an additional Promise stage,
+so an immediately following `getItem`, `removeItem` or `clear` can create its
+transaction first. The facade's stronger issue order changes returned values,
+callback order and final records. The original Basic clear workflow reproduces
+this difference. That is a compatibility failure for a transparent storage
+replacement, even when both implementations preserve their own intended order.
+
+`generate-mirror-module.js` instead generates a static replacement for the exact
+public `70940` method in `TradingView.99bc5074.js`. Acorn identifies exactly one
+mirror destination expression, and generation verifies the complete source hash,
+factory hash, UTF-16 ranges and absence of a colliding dispatch identifier. The
+only change replaces the original `Wr=fr.map(...)` RHS with:
+
+```js
+__dispatchChartMirror(Sr,fr,()=>ORIGINAL_FR_MAP_EXPRESSION)
+```
+
+The complete original method occupies `[6665,36819)` and has SHA-256
+`37d249d1ceec4a41b54a959c1ab2c4b52b3061e504cf8cd81a824962af8c6c88`.
+The replaced RHS occupies `[33783,34025)`. Reversing that insertion must reproduce
+the complete original method byte for byte. There is no `eval` or `new Function`.
+`replaceChartMirrorFactory` synchronously compares the full original
+`Function.prototype.toString` result before returning the static candidate.
+See the [scoped fixture provenance](../../test/fixtures/binance-chart-storage/mirror-scoped.md).
+
+`bootstrap.js` selects `70940` only when an explicit `replaceMirrorFactory`
+callback is supplied; its default `43917` capture remains unchanged. Rejection
+leaves the original module executable and does not poison its cache. A callback
+that synchronously stops observation must also leave the original registration
+untouched. Both loading orders first failed that regression; rechecking the
+active state immediately after the callback now makes them pass. Cleanup restores
+queue and factory slots. Already executed exports remain cached normally.
+
+The unchanged host code still owns its guards and catch, source `keys()` and
+per-key `getItem()` calls, target namespace exclusion, ordinary TradingView save,
+Basic settings and reset/clear paths. The writer never replaces localForage's
+`createInstance` or instance methods. It reuses only the JSON utility exports
+from `adapter.js`; importing those exports does not install the historical facade.
+
+`mirror-writer.js` accepts one explicit destination batch and compares current
+values inside one IndexedDB read/write transaction, putting only changed or
+missing records. Stored null is distinguished from a missing key. Empty entries
+perform no initialization or transaction. Values are copied as validated JSON
+before admission. Defaults bound each batch to 512 entries and 4 MiB of input,
+with a separate 4 MiB stored-read budget; pending work is limited to 16 batches
+and 64 MiB. Count/input limits reject before any entry is written; a read-budget
+failure aborts the whole destination transaction.
+The explicit page-probe scope changes only origin/path/frame admission; it does
+not broaden the database, driver, key, JSON or resource-limit contracts.
+
+Stop closes admission and drains the finite set of accepted batches. A later
+mirror dispatch during drain waits for that fence, then evaluates its retained
+original async per-key map; after stop it invokes that native thunk directly.
+Later calls do not extend the optimized queue. Ordinary native save, Basic and
+clear calls are not intercepted or placed behind this mirror fence. Accepted
+failures are not replayed, and neither cleanup nor stop closes shared library
+connections.
+
+This is deliberately not native mirror transaction equivalence. The original
+per-key writes can partially commit; the candidate commits or aborts the entire
+destination batch and changes interleaving with concurrent native transactions.
+Limits introduce explicit rejection conditions. The outer native catch remains
+unchanged. Source reads are still a non-atomic sequence, and two databases cannot
+share this transaction. Neither lost-update protection nor blank-page recovery
+follows from this change.
+
+## Current scoped evidence
+
+The scoped suite contains 19 scenarios and the dedicated writer suite contains
+18. They cover exact source identity and inverse replacement, the original save
+and mirror callbacks, guards, empty/null records, native API identity and the
+same-stack set/get/remove/clear matrix, strict registration refusal, both runtime
+orders, stop and callback reentry, real abort/quota rollback, limits, blocked
+initialization, concurrent native clear and another tab's changed target values.
+The scoped and existing startup-integration suites first passed a targeted
+33-scenario run after the reentrant-stop fix. Before the explicit page-probe
+scope was added, the combined experiment run passed all 105 Chromium scenarios,
+all eight Node origin/path/frame cases and the complete test-policy lint. The browser report is
+`/tmp/chart-storage-final-20261006.json`.
+The scope change then expanded the Node suite to 18 cases: three new cases first
+failed, then all 18 passed. Both mirror browser suites passed all 37 scenarios,
+and the full test-policy lint passed again. Default production-origin refusal
+and the explicit scope's exact origin/path/top-frame boundaries are separate
+assertions.
+The reusable anonymous-probe contract and native-only preflight then added four
+and 18 Node scenarios respectively. Together with the 18 scope/origin cases,
+all 40 Node cases passed; the 37 mirror browser scenarios also passed. The
+preflight build and its dependency boundary were independently reviewed.
+
+For the current 32-chain workload, each database starts with 290 historical
+drawing records. Eight sequential rounds each complete four concurrent original
+save/mirror chains with the same changed revision. Both databases retain all
+293 expected keys, historical values and the final chart/drawing data.
+
+| Current 32-chain workload | Transactions | Puts | Gets |
+| --- | ---: | ---: | ---: |
+| Native localForage | 18,912 | 9,472 | 9,408 |
+| Scoped mirror writer | 9,568 | 112 | 18,784 |
+
+Setup and final verification I/O are excluded. The scoped path retains the
+source save/read work: its 112 puts comprise 96 ordinary source writes and 16
+changed destination writes. Target comparisons add 9,376 gets. The two measured
+workloads ended with zero pending or aborted transactions and all 32 chains
+complete. The final extracted metrics are
+`/tmp/chart-storage-mirror-measurements-20261006.json`; a separate fresh two-case
+measurement in `/tmp/chart-storage-mirror-scoped-doc-metrics.json` confirmed the
+same counts. These counts do not measure
+sustained production CPU, latency, competing revisions or blank-page recovery.
+
+The offline evidence separates full-module static identity, exact callback
+execution with original dependencies and real IndexedDB, and synthetic module
+lifecycle in the actual captured runtime. Offline callback fixtures do not
+execute the complete public React module or establish rendered chart readiness.
+
+A separate anonymous Chrome `154.0.8037.98` run then injected the complete static
+`70940` candidate before registration in a temporary profile with no login or
+extensions. The dispatch service was native-only: it would evaluate the exact
+original thunk, and the lab writer was not installed. Strict validation attempted
+once, matched once and executed the replaced module once. Capture occurred at
+5,975.4 ms. At 10,015.6 ms, `activeChart().dataReady()` was true, and the inspected
+screenshot showed USUSDT candles, moving averages, volume and a populated orderbook.
+The observer stopped, no page errors were reported, and the browser was closed.
+
+That run recorded `dispatches: 0`: anonymous startup did not trigger the mirror.
+It proves full-module registration/execution and rendered startup with the
+native-only service, not execution of the injected mirror branch, real batched
+writes, production compatibility or blank-page recovery. The result, injection
+and screenshot are in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-mirror-native-preflight-HeCAar/`.
+The earlier anonymous `43917` observation below remains a separate historical
+result.
+
+A later, independent anonymous Chrome `154.0.8037.98` shape probe exercised the
+real mirror using only its original native thunk. In the bounded interval-change
+stage, switching the native interval from `60` to `15` advanced completed mirrors
+from 6 to 7. Earlier activity had already produced mirrors; the final total of
+seven is not attributed to that one action. All seven completed destinations
+were `chart_delivery` using IndexedDB, with zero mirror or JSON-shape failures.
+Observed maxima were four entries, 49,003 encoded batch bytes and 24,504 bytes
+for one record. These values establish this anonymous sample's compatibility
+with the JSON and size contracts, not every saved chart's compatibility.
+
+The native chart remained data-ready at resolution `15`, and the inspected
+screenshot showed the chart and populated orderbook. Observation stopped and
+the temporary browser closed. Evidence is in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-mirror-shape-probe-yDfadK/`.
+This supplies real native mirror invocation and shape evidence beyond the
+earlier zero-dispatch startup. It does not establish real-page bulk execution.
+
+## Anonymous bulk execution: 2026-10-07 Beijing time
+
+An independently launched anonymous Chrome `154.0.8037.98` then enabled the real
+batch writer through the explicit USUSDT probe scope. The retained report starts
+at `2026-10-06T16:24:24.572Z`, which is October 7 in Beijing. The exact factory
+matched and executed once. Five accepted batches committed in five destination
+transactions, with eight writes and eleven unchanged writes skipped. Rejected,
+failed, aborted and pending counts were zero; peak admission was two batches and
+98,006 bytes.
+
+| Verified stage | Completed mirror calls | Bulk commits | Readback |
+| --- | ---: | ---: | --- |
+| One-hour chart interval stage | 0 → 4 | 4 | Four captured entries matched |
+| One hour → fifteen minutes | 4 → 5 | 5 total | Four captured entries matched |
+| After writer stop, four-hour interval | 5 → 6 | Still 5 | Four captured entries matched |
+
+The first stage's four calls include the activity observed during that interval
+stage and are not four saves attributed to one click. Each readback compares
+destination records with the corresponding captured dispatch snapshot. After
+`writer.stop()`, the four-hour stage increased `nativeCalls` from zero to one;
+the complete writer statistics remained unchanged. This verifies native thunk
+continuation through an already mounted transformed callback.
+
+The runner required native chart data readiness for every completed interval
+stage. The inspected final screenshot showed four-hour USUSDT candles, moving
+averages, volume and a populated orderbook. There were no page errors, observation
+stopped, and the temporary browser closed. The report and screenshot are in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-mirror-bulk-probe-3YcdXL/`.
+This is actual batch execution in an independent anonymous browser. It is not
+enablement in the user's logged-in profile or proof of long-running stability,
+Tampermonkey delivery or blank-page recovery.
+
+Three earlier attempts stopped at the anonymous-UI check before any factory
+match or mirror dispatch. The registration text matched both a button and a
+link; selecting the unique button by its accessible role fixed that diagnostic
+premise. Those attempts are not storage failures.
+
+The independently runnable `run-anonymous-probe.js`, `anonymous-probe-entry.js`
+and `anonymous-probe-contract.js` retain this bounded workflow. The runner owns
+the fresh Chrome context, rejects navigation away from the fixed URL, checks
+the anonymous page, records failure counts and closes its browser. The entry
+also checks the exact runtime URL and top frame when capturing each batch;
+diagnostic rejection is counted even when the host's original catch resolves.
+For an authorized isolated live probe, run:
+
+```sh
+node experiments/binance-chart-storage/run-anonymous-probe.js
+```
+
+## Tampermonkey acceptance conditions
+
+Tampermonkey's official [Content Script API documentation](https://www.tampermonkey.net/documentation.php?q=content_script_api)
+says the default **Content Script** mode has no real `document-start` support.
+On Chrome, ordinary **UserScripts API** mode also retrieves scripts through
+background messaging and has no real `document-start` support. **UserScripts API
+Dynamic** injects the wrapper and script together and supports `document-start`.
+The ordinary UserScripts API statement differs on Firefox; this experiment
+targets Chrome.
+
+The official [`@sandbox` documentation](https://www.tampermonkey.net/documentation.php?q=sandbox)
+states that `raw` requests page `MAIN_WORLD` access, but injection can move to
+another enabled sandbox when MAIN_WORLD injection is unavailable, for example
+because of CSP. Therefore `@run-at document-start` and `@sandbox raw` metadata
+alone do not prove timely access to the actual page queue.
+
+A future Tampermonkey entrypoint must first pass a separate native-only probe on
+an authorized test page: verify the effective injection mode, actual MAIN_WORLD
+execution, exact source pin before first module execution, real native mirror
+completion and clean stop. Unverified timing/world/source must refuse
+replacement. Chrome `addInitScript` evidence does not satisfy this extension
+gate. The anonymous runs changed no installed script or extension setting.
+
+`native-preflight-core.js`, `native-preflight-entry.user.js` and
+`build-native-preflight.js` now produce the self-contained
+`native-preflight.user.js` review artifact. Its verified build input graph contains
+only the bootstrap, pinned module, native-only core and entry; no writer or
+runtime loader is included. The core keeps bounded in-memory statistics for
+30 seconds and forwards the original thunk synchronously, preserving its returned
+array and thrown error identities without inspecting stored values or adding
+storage calls. Manual stop, pagehide, scope change and the deadline retire
+observation. The entry exposes only snapshot and stop. Its metadata alone does
+not establish Tampermonkey injection timing or MAIN_WORLD behavior. It remains
+unpublished; the authorized local installation is recorded below.
+
+```sh
+node experiments/binance-chart-storage/build-native-preflight.js
+```
+
+On October 7 Beijing time, the generated artifact itself was then executed with
+`addInitScript` in a fresh anonymous Chrome `154.0.8037.98` context. The report
+started at `2026-10-06T16:30:27.651Z`: attempts, source matches and factory
+executions were each one; factory capture completed, and four dispatches counted
+15 entries. There was no diagnostic failure or page error. Manual stop changed
+`active` to false, and the temporary browser closed. The inspected screenshot
+showed candles, moving averages, volume and the populated orderbook. Evidence is
+in `/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-native-artifact-probe-JSBjWC/`.
+Here `completed: true` means module capture completed; `dispatches` counts native
+thunk invocation, not asynchronous write commits. This validates the built
+artifact through Chrome initialization-script injection, not Tampermonkey
+delivery. The artifact was still unpublished and uninstalled at that stage.
+
+The subsequent authorized Tampermonkey test began at
+`2026-10-06T18:39:55.733Z` (October 7 Beijing time) in a new USUSDT tab. Installed
+source matched all 86,589 artifact bytes; the loaded extension wrapper contained
+one identical copy in the top-level Binance default execution world. The
+preflight observed one exact factory match and execution, then three native
+dispatches counting 885 entries. At page ages 45.1 and 94.2 seconds it was retired
+with the same counters, `stopReason: 'deadline'` and no diagnostic failure.
+The queue accessor was restored. Native chart data readiness and the inspected
+candle crop confirmed a rendered 15-minute chart.
+
+Only the statistics script was installed. It remains restricted to the exact
+USUSDT page and starts a new observation on a fresh matching load. The test and
+installer tabs closed; the target-local leverage-endpoint block was disposed
+with the test tab. Existing user tabs and extension settings were unchanged.
+No bulk writer was enabled or published. Native asynchronous commit completion,
+repeatable injection timing, long-running concurrency and blank-page recovery
+remain unverified. See the [diagnosis record](../../docs/binance-futures-blank-page-diagnosis.md#tampermonkey-native-only-acceptance-2026-10-07-beijing)
+for the sampling limits and `/tmp/binance-tampermonkey-native-preflight-hlIxL9/`
+for the report, loaded source and inspected chart crop.
+
+## Historical global-facade research
+
+The following sections preserve earlier results for `adapter.js`, including its
+different scheduling contract. Their transaction counts, installation lifecycle
+and API restrictions do not describe the current scoped mirror candidate.
+
+### Original question and acceptance
+
+Can the actual Binance localForage export be observed at first module execution,
+and can a narrowly scoped adapter reduce the transaction and unchanged-write
+amplification without dropping accepted calls?
+
+The browser tests use a fresh Chromium context, intercepted `.test` routes, the
+captured Binance runtime and localForage module, and real IndexedDB. No Binance
+login, existing profile, production database or trading action is used. See the
+[fixture provenance](../../test/fixtures/binance-chart-storage/README.md).
+
+Acceptance covers 290 historical keys and repeated saves, callback delivery,
+initialization, ordered mixed operations, null versus missing records, barriers,
+two pages, persistence after reload, transaction rollback, limits and cleanup.
+The native workflow must first fail the optimized transaction/write assertions.
+
+### Historical facade restrictions
+
+- Only newly wrapped instances for `chart_futures` and `chart_delivery`, using
+ `keyvaluepairs`, are managed. Only `#TV_SYMBOL-` and `myTradingView` keys are
+ optimized. Other operations pass through ordered native barriers.
+- Managed values are JSON values. They are validated and copied when accepted.
+ Invalid data and exceeded limits are explicit errors, not silent truncation.
+- Queued and running operations share count and byte budgets. A batch has its
+ own operation and byte limits. Read results have a separate maximum size.
+- Operations are ordered before waiting for localForage initialization. A
+ transaction advances only through native request events. Its promises settle
+ after commit or abort, and equal values are compared inside the transaction.
+- Optimized batch failure rolls back the whole batch. Reads also wait for commit.
+ Optimized batches do not retry connection errors. Native barriers retain
+ localForage's independent transactions and one-time connection recovery; they
+ are not covered by optimized-batch rollback or transaction counters.
+- Connections are obtained from the initialized instance for every batch. The
+ experiment never closes the library's shared connections during cleanup.
+- Stop closes optimized admission, drains accepted work and then restores the
+ exact original factory descriptor. Retained facades and factories resume native
+ behavior. Calls arriving during drain wait behind its finite completion fence;
+ they cannot overtake accepted writes or extend the optimized drain queue.
+- Each export permits one installation per page lifecycle. After stop, reload is
+ required before another installation can own a new queue.
+- While active, `config()` returns a copy; configuration changes, driver changes, database
+ removal and iteration are explicitly unsupported on managed facades. Managed
+ non-chart values are also restricted to JSON, even when passed to a barrier.
+ After stop, the original native API and value types apply. Synchronous config
+ changes remain prohibited while accepted work drains.
+
+### Historical facade evidence boundaries
+
+The original capture entrypoint's first-registration interception and cache
+identity are tested only in isolation. The later bootstrap also has the anonymous
+startup observation described below. The runtime does not expose its module
+cache. Requiring a registered module is
+not proof that its factory was already executed. Empty chunk-ID arrays do not
+execute a runtime callback in this captured runtime; the host uses a unique
+test-owned chunk ID.
+
+`observeChartStorageModule(queue, { onCapture })` observes module `43917` and
+calls `onCapture` synchronously after its factory finishes, before the host's
+`require` returns. A Promise-only consumer is too late to intercept instances
+created in that same host call stack. Both success and failure restore the
+observer's factory and chunk-queue changes. Starting after registration is an
+explicit error; it never probes the private cache by executing the factory.
+If another owner replaces the queue hook, capture rejects and preserves that
+owner's hook while restoring its own module registration. It does not claim
+that an external wrapper containing the observer can be removed safely.
+
+The adapter is not a transparent replacement for the complete localForage API.
+Batch atomicity, read completion timing, capacity errors and connection failure
+behavior differ. Tests do not establish safety for production installation.
+Two-page tests do not establish atomic synchronization across two databases or
+fix an application's separate `getItem` then `setItem` lost-update race.
+
+No real trading page adapter is installed by this experiment. Existing queued
+transactions are not drained, cancelled or repaired by running these tests.
+
+### Historical validation
+
+Use the repository Node version and run:
+
+```sh
+npm run test:ui -- e2e/binance-orderbook/specs/chart-storage-*.pw.js
+node --test test/unit/binance-chart-storage-origin.test.js
+npm run lint:tests
+```
+
+Earlier-stage validation on 2026-10-06: all 68 real Chromium scenarios and all eight
+origin/path/frame-guard Node tests passed. The complete test-policy lint, experiment syntax
+checks and `git diff --check` passed. Fixture file hashes match their manifest.
+The native baseline was rerun in a separate output directory and failed the
+bounded-transaction assertion as intended (`4,640 > 32`).
+
+The quota scenario sets a 64-KiB CDP quota before the first library execution or
+database write. A one-MiB incompressible JSON value triggers the real
+`QuotaExceededError`; both writes and the read in its batch reject, all callbacks
+complete, and prior values remain unchanged. Setting the override after seed
+writes did not trigger this failure: the matching
+[Chromium bucket implementation](https://github.com/chromium/chromium/blob/151.0.7922.34/content/browser/indexed_db/instance/bucket_context.cc#L463-L523)
+can use an already allotted space budget before asking the quota manager again.
+An active reported override alone does not prove write-time enforcement.
+
+### Historical synthetic transaction work
+
+The controlled workload prepopulates 290 synthetic JSON chart records, then
+performs eight rounds of `getItem` followed by an unchanged `setItem`. Setup and
+final verification reads are excluded from the counters. All 4,640 Promise
+results and all 4,640 distinct callbacks are verified against expected values.
+
+| Workload | Native transactions | Native puts | Native gets |
+| --- | ---: | ---: | ---: |
+| Native localForage, eight sequential rounds | 4,640 | 2,320 | 2,320 |
+| Prototype, eight sequential rounds | 16 | 0 | 4,640 |
+| Prototype, eight simultaneous rounds | 10 | 0 | 4,640 |
+
+The native baseline fails the same bounded-transaction assertion as intended.
+The sequential comparison reduces transaction creation by about 99.66% and
+skips all 2,320 unchanged writes. It adds comparison reads inside transactions;
+it does not reduce the number of read requests. The concurrent test has at most
+2,320 outstanding operations and finishes with zero pending operations/bytes.
+These are work counts, not a browser CPU, latency or blank-page recovery
+benchmark. Changed values are still written and checked by separate scenarios.
+
+The browser runtime was Playwright Chromium `151.0.7922.34`, verified by launching
+the same bundled browser and reading `browser.version()`. This is not the
+user's Chrome `154.0.8037.98` profile. The rendered lab report was inspected;
+the later anonymous Chrome observation is described separately below.
+
+### Historical integration follow-up
+
+Two additional regressions were reproduced before the integration changes:
+
+- Installing the original capture before the real runtime produced one factory
+ execution but zero capture callbacks. Rspack saves the prior `push` as a parent
+ append callback, invoked after registration and synchronous host execution.
+- A real same-origin iframe's `JSON.parse` chart object was rejected by the
+ adapter's same-realm prototype identity check. The native TradingView iframe
+ produces its save callback's JSON in precisely that separate realm.
+
+`bootstrap.js` observes the runtime's first `push` assignment. The pre-runtime
+parent callback becomes permanently append-only after takeover or stop. Queue,
+factory and cache behavior are tested across both script orders, late entry,
+stopping, diagnostic exceptions and host registrations that the observer refuses.
+An in-flight append cannot lose its newly created global queue during cleanup.
+Diagnostic callback failure rejects observation without poisoning a successful
+host module's cached export. Original factory failures keep their host behavior.
+
+The JSON validator now recognizes native plain-object prototypes across realms
+by constructor and constructor.prototype identity. It still rejects class
+instances, Date, Map, forged/null prototypes, symbols and accessor properties.
+Snapshots retain nested data and own `__proto__` keys without mutating the input.
+
+`preflight.js` wraps only the actual export's `createInstance`, returning each
+original instance. It records up to sixteen chart-instance creation timestamps
+and fixed DB/default-store metadata. It never wraps instance methods, calls
+ready/config/get/set, records keys/values or starts the adapter. Stop, pagehide
+and a 30-second deadline restore the original factory descriptor; an inherited
+factory is restored by removing the observer's own property. Retained wrappers
+then forward without observation. Tests establish zero added opens, transactions,
+gets or puts during passive observation. Dedicated scenarios verify manual stop
+and the deadline; they do not separately exercise pagehide, changed factory
+ownership, metadata failure or the sixteen-entry recording limit.
+
+The current user-page module factory was read without requiring it. Its SHA-256
+was `f33d0f78c185ff592f1f0781e21baab63e7db937054866c37afdfda028e9e647`, matching the
+fixture's complete module property. The existing page was not refreshed or
+modified by the observation.
+
+An independently launched, anonymous Chrome `154.0.8037.98` with a temporary
+profile, no extensions and no login loaded the real USUSDT page. Synchronous
+`addInitScript` injection captured the actual module at about 3,236.9 ms and the
+first `chart_futures` / `keyvaluepairs` instance at 4,400.5 ms. The observer was
+stopped (`active=false`, no failure), and the temporary browser was closed.
+This first capture stopped before the business UI mounted and establishes only
+the ordering. Its evidence is in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-storage-preflight-b4mWbr/`.
+
+Further anonymous observations separated module capture from rendered readiness:
+
+- Waiting for a TradingView iframe on the default Basic chart timed out. The
+ inspected screenshot showed a working Basic chart and populated orderbook;
+ it was not a blank-page reproduction.
+- The first mode-selection attempt used `TradingView`, but the native label is
+ `Trading View`. That selector timeout did not click the control or establish
+ a chart failure.
+- After using the correct label, a visible iframe and seven canvases appeared
+ at about 6.8 seconds. The screenshot still showed the loading animation. That
+ run's raw `outcome: ui_ready` field is an inadequate machine criterion and
+ must not be interpreted as rendered chart readiness. Its evidence is in
+ `/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-storage-preflight-tv-verified-CrQy6W/`.
+- The final observation waited for the native `activeChart().dataReady()` and
+ then two animation frames. The inspected screenshot showed USUSDT candles,
+ moving averages, volume bars and the orderbook. The captured library defines
+ `dataReady()` without a callback as a read-only check for a model and nonempty
+ main-series data; the screenshot supplies the separate rendering evidence.
+
+In that final Chrome `154.0.8037.98` run, capture occurred at 2,184.1 ms and the
+first chart instance at 3,008.7 ms. At 7,429.2 ms the page had a populated
+orderbook, one TradingView iframe, twelve canvases and `nativeChartDataReady:
+true`. Twenty `chart_futures` instances were observed (only the first sixteen
+timestamps retained), with no `chart_delivery` instance and no page errors.
+The observer was stopped (`active=false`, `failure=null`) and the temporary
+browser was closed. Structured results and the inspected screenshot are in
+`/var/folders/99/d7dxx5j94_x8n09snmknq0x80000gn/T/binance-storage-preflight-chart-ready-1nRAMB/`.
+All anonymous probes used separate temporary profiles and closed their browsers.
+They did not enable the adapter. Successful `addInitScript` observation does not
+validate Tampermonkey document-start/page-world injection or the affected user's
+logged-in profile, and does not establish blank-page recovery.
+
+The actual source contract is broader than the initial historical-key workload:
+module `76535` in [chunk 36648](https://bin.bnbstatic.com/static/chunks/36648.dd8ae7dc.js)
+creates instances during component rendering and retains them. Its main save,
+layout read/modify/write, drawing saves and cross-database mirror are separate
+stages. Module `15426` in
+[chunk 28928](https://bin.bnbstatic.com/static/chunks/28928.9aeec892.js) also uses
+the same databases for Basic candlestick settings, including `clear()`. Those
+non-TradingView calls are affected by the prototype's native-barrier restrictions.
+Those retained references motivated the stop lifecycle correction described below;
+restoring only the factory cannot update an already mounted component's instance.
+
+### Historical facade continuation after stop
+
+The initial stop implementation rejected all retained-facade requests and exposed
+the native factory before accepted work drained. Six new regressions reproduced
+these failures before the lifecycle change. The adapter now moves from active to
+draining to stopped. While draining, its factory still returns managed facades;
+their later calls wait for the finite set of accepted operations to complete.
+Afterwards it restores the original descriptor and forwards retained calls with
+their complete arguments and native receiver. Native callbacks retain their
+method-specific signatures. Failed accepted transactions are reported once and
+are not retried; they do not prevent a later native operation after stop.
+
+The nine lifecycle scenarios cover a real write lock, a real blocked database
+version upgrade, callback reentry, abort delivery, native Blob/API usage, factory
+ownership changes, exact descriptor restoration and repeated stop/install calls.
+JSON validation can execute Proxy traps; a separate failing regression showed a
+trap could stop the adapter before the request was accepted. Admission now checks
+the lifecycle again after validation, so that request rejects without entering a
+queue or starting a transaction. An external factory replacement during drain
+causes stop to reject explicitly and preserves the replacement.
+
+Concurrent native `setItem` and `getItem` do not guarantee read-your-write. The
+captured driver's set operation prepares the value in an additional Promise
+stage before creating its transaction. An independent native baseline therefore
+owns the post-stop ordering assertions; reading the prior value before that
+concurrent write commits is not data loss. The final persisted value is checked
+separately. Stop does not promise to complete while accepted native storage work
+itself remains blocked, and its drain statistics do not include subsequent native
+calls. These are isolated lifecycle proofs, not a live stop of mounted Binance UI.
+
+### Historical original-host persistence workflows
+
+The [host fixture](../../test/fixtures/binance-chart-storage/host-persistence.md)
+preserves 273 exact source fragments from seven public bundles, including 203
+original dependency factories. It executes the actual TradingView save/load
+functions and mirror callback, and the actual Basic storage functions and old-key
+migration. Source byte hashes and UTF-16 ranges were independently checked.
+Only the documented storage, widget and enabled-state references are supplied by
+the harness; the business algorithms are not reimplemented.
+
+The same scenarios run through native localForage and the adapter. They check
+layout retention, drawing restoration and deletion, cross-database mirror contents,
+all six Basic storage categories, original key migration and shared-store clear.
+The Basic React subscriptions, debounce and upstream JSON serialization are not
+executed; their storage boundary receives synthetic JSON values. Default `{name}`
+instances select the IndexedDB driver in these browser tests.
+
+Four original save/mirror calls with revisions `[1, 1, 2, 2]` produced 64 native
+transactions and 36 puts, compared with 28 transactions and 11 puts through the
+adapter. Both retained the expected source and destination records. Setup and
+final verification are excluded. This small workload includes changed values;
+its reduction is different from the unchanged 290-record benchmark above.
+
+A bounded load scenario prepopulates both databases with the same 290 historical
+drawing records, then completes eight rounds of four concurrent original save
+and mirror callbacks. Every round changes the current snapshot and drawing.
+All 32 callbacks, both databases' 293 keys, all 580 historical values and final
+snapshot/layout/drawing/load results are checked. No failures, rejections,
+aborted transactions or pending operations remained.
+
+| Thirty-two original save/mirror chains | Transactions | Puts | Gets |
+| --- | ---: | ---: | ---: |
+| Native localForage | 18,912 | 9,472 | 9,408 |
+| Adapter | 112 | 32 | 18,880 |
+
+The adapter count comprises 80 optimized transactions and 32 native barriers;
+9,440 unchanged writes were skipped. Setup and final verification I/O are
+excluded. Controller workload counters use before/after differences; peak gauges
+are separately labeled as installation-lifetime measurements. The measured peak
+was 1,172 pending operations and 181,767 pending bytes, within configured limits.
+Recorded elapsed times are individual lab samples, not a sustained production
+latency or CPU benchmark. The increased reads are the cost of comparing values
+inside transactions, and this bounded workload does not prove long-term stability.
+The four concurrent chains within each round share one revision, with rounds
+awaited in sequence; competing different revisions are not covered. The retained
+JSON report and extracted measurements are `/tmp/chart-storage-load-retained-20261006.json`
+and `/tmp/chart-storage-load-measurements-20261006.json`.
+
+An additional native/adapter comparison exposes a real ordering difference:
+concurrent native sets can create their transactions after a later-invoked Basic
+`clear()`, whereas the adapter's barriers enforce issue order and clear those
+earlier writes. Both delete records that were already committed. The separate
+shared-clear conformance scenario awaits completed saves before clearing; the
+concurrent difference has its own explicit assertions. Stronger issue order is
+not transparent emulation of native scheduling and remains a production contract
+decision, alongside whole-batch rollback and delayed read completion.
+
+## Current remaining production boundary
+
+- Validate Tampermonkey delivery under the conditions above. Keep the earlier
+ zero-dispatch startup, later 6-to-7 native mirror and October 7 actual anonymous
+ bulk/stop-continuation evidence distinct; none proves extension delivery.
+ Exact source mismatch must refuse
+ replacement and preserve native operation; the captured build is not a stable API.
+- Validate the real mirror's saved JSON shapes, destination driver/configuration,
+ limits and the user's logged-in mounted caller lifecycle. The anonymous probe
+ demonstrated one mounted stop continuation only. Default scope still rejects production
+ origins; the explicit exact-page scope depends on the harness for profile
+ isolation. Ordinary native APIs remain outside its control.
+- Decide whether destination-wide rollback, read-comparison cost, capacity or
+ connection failures and changed interleaving with native clear/other tabs are
+ acceptable. A stopped writer restores the retained native mirror expression;
+ stopping the bootstrap alone does not alter an already cached module export.
+- Measure sustained changed snapshots and concurrent different revisions. Source
+ reads and layout read/modify/write remain native and non-atomic across calls;
+ cross-database atomicity and previously queued transaction repair remain absent.
+- Validate actual blank-page recovery and long-running stability on the affected
+ Chrome version. Passing this experiment does not establish a deployed fix.
+
+Neither historical storage optimization is imported by production entrypoints.
+The native-only preflight was installed for the separate acceptance above and
+subsequently replaced locally by the production successor. The historical global
+facade is not the production candidate.
diff --git a/experiments/binance-chart-storage/adapter.js b/experiments/binance-chart-storage/adapter.js
new file mode 100644
index 0000000..ebaf516
--- /dev/null
+++ b/experiments/binance-chart-storage/adapter.js
@@ -0,0 +1,380 @@
+const LAB_ORIGIN = 'https://chart-storage.test';
+const DATABASES = new Set(['chart_futures', 'chart_delivery']);
+const DEFAULT_LIMITS = Object.freeze({
+ maxBatchOperations: 512,
+ maxBatchBytes: 4 * 1024 * 1024,
+ maxPendingOperations: 8192,
+ maxPendingBytes: 64 * 1024 * 1024,
+ maxResultBytes: 4 * 1024 * 1024,
+});
+const installations = new WeakSet();
+const encoder = new TextEncoder();
+const nativeObjectSource = Function.prototype.toString.call(Object);
+
+export { snapshotJson, byteLength, equalJson };
+
+function assert(condition, message) {
+ if (!condition) throw new Error(message);
+}
+
+/** TradingView creates snapshots in an iframe, whose Object.prototype differs. */
+function isPlainJsonObject(value) {
+ const prototype = Object.getPrototypeOf(value);
+ if (prototype === null || Object.getPrototypeOf(prototype) !== null) return false;
+ const constructor = Object.getOwnPropertyDescriptor(prototype, 'constructor');
+ if (!constructor || !Object.hasOwn(constructor, 'value') || typeof constructor.value !== 'function') return false;
+ if (Function.prototype.toString.call(constructor.value) !== nativeObjectSource) return false;
+ const constructorPrototype = Object.getOwnPropertyDescriptor(constructor.value, 'prototype');
+ return !!constructorPrototype && Object.hasOwn(constructorPrototype, 'value') && constructorPrototype.value === prototype;
+}
+
+/** Reject values whose JSON encoding would silently change their meaning. */
+function snapshotJson(value, ancestors = new Set()) {
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
+ if (typeof value === 'number') {
+ assert(Number.isFinite(value), 'Chart JSON numbers must be finite');
+ return value;
+ }
+ assert(typeof value === 'object', 'Chart values must be JSON');
+ assert(!ancestors.has(value), 'Chart JSON must not contain cycles');
+ const array = Array.isArray(value);
+ assert(array || isPlainJsonObject(value), 'Chart values must be plain JSON');
+ const descriptors = Object.getOwnPropertyDescriptors(value);
+ assert(Object.getOwnPropertySymbols(value).length === 0, 'Chart JSON must not contain symbol properties');
+ const keys = Object.keys(descriptors).filter(key => !(array && key === 'length'));
+ if (array) assert(keys.length === value.length && keys.every((key, index) => key === String(index)), 'Chart JSON arrays must be dense');
+ ancestors.add(value);
+ const result = array ? [] : {};
+ for (const key of keys) {
+ const descriptor = descriptors[key];
+ assert(descriptor.enumerable && Object.hasOwn(descriptor, 'value'), 'Chart JSON requires enumerable data properties');
+ Object.defineProperty(result, key, {
+ value: snapshotJson(descriptor.value, ancestors), enumerable: true, writable: true, configurable: true,
+ });
+ }
+ ancestors.delete(value);
+ return result;
+}
+
+function byteLength(value) {
+ return encoder.encode(JSON.stringify(value)).byteLength;
+}
+
+/** Compare JSON structure without changing the property order of stored snapshots. */
+function equalJson(first, second) {
+ if (first === second) return true;
+ if (first === null || second === null || typeof first !== 'object' || typeof second !== 'object') return false;
+ if (Array.isArray(first) !== Array.isArray(second)) return false;
+ const keys = Object.keys(first);
+ if (keys.length !== Object.keys(second).length) return false;
+ return keys.every(key => Object.hasOwn(second, key) && equalJson(first[key], second[key]));
+}
+
+function isChartKey(key) {
+ return key.startsWith('#TV_SYMBOL-') || key.startsWith('myTradingView');
+}
+
+/** Callback exceptions are caller errors; they cannot abandon other accepted calls. */
+function withCallback(promise, callback) {
+ if (callback !== undefined) {
+ assert(typeof callback === 'function', 'Storage callback must be a function');
+ const invoke = (error, value) => {
+ try {
+ callback(error, value);
+ } catch (callbackError) {
+ queueMicrotask(() => { throw callbackError; });
+ }
+ };
+ promise.then(value => invoke(null, value), error => invoke(error, undefined));
+ }
+ return promise;
+}
+
+/**
+ * Isolated facade for the captured localForage export. The library owns its
+ * connections; this experiment owns only accepted operations and their batches.
+ * Batch atomicity and connection-failure behavior deliberately differ from native.
+ */
+export function installChartStoragePrototype(localforage, options = {}) {
+ assert(location.origin === LAB_ORIGIN, 'Chart storage prototype is restricted to the isolated lab origin');
+ assert(typeof localforage.createInstance === 'function', 'Expected the actual localForage export');
+ assert(!installations.has(localforage), 'Chart storage prototype is already installed');
+ const limits = { ...DEFAULT_LIMITS, ...options };
+ for (const [name, value] of Object.entries(limits)) {
+ assert(Object.hasOwn(DEFAULT_LIMITS, name), `Unknown chart storage limit: ${name}`);
+ assert(Number.isSafeInteger(value) && value > 0, `${name} must be a positive safe integer`);
+ }
+ const nativeCreateInstance = localforage.createInstance;
+ const originalFactoryDescriptor = Object.getOwnPropertyDescriptor(localforage, 'createInstance');
+ const coordinators = new Map();
+ const waiters = [];
+ let phase = 'active';
+ let stopping;
+ const stats = {
+ acceptedOperations: 0, rejectedOperations: 0, failedOperations: 0,
+ pendingOperations: 0, pendingBytes: 0, peakPendingOperations: 0, peakPendingBytes: 0,
+ transactions: 0, committedTransactions: 0, abortedTransactions: 0,
+ committedWrites: 0, skippedWrites: 0, barriers: 0,
+ };
+
+ function finish(operation, succeeded, value) {
+ stats.pendingOperations -= 1;
+ stats.pendingBytes -= operation.bytes;
+ if (!succeeded) {
+ stats.failedOperations += 1;
+ operation.reject(value);
+ } else {
+ operation.resolve(value);
+ }
+ }
+
+ function notifyDrained() {
+ if (stats.pendingOperations === 0) {
+ for (const resolve of waiters.splice(0)) resolve();
+ }
+ }
+
+ function drain() {
+ return stats.pendingOperations === 0 ? Promise.resolve() : new Promise(resolve => waiters.push(resolve));
+ }
+
+ /** Native request events keep the transaction active; no Promise is awaited inside it. */
+ function transact(database, batch) {
+ return new Promise((resolve, reject) => {
+ const transaction = database.transaction('keyvaluepairs', batch.some(op => op.method === 'setItem') ? 'readwrite' : 'readonly');
+ stats.transactions += 1;
+ const store = transaction.objectStore('keyvaluepairs');
+ const results = [];
+ let index = 0;
+ let resultBytes = 0;
+ let writes = 0;
+ let skipped = 0;
+ let failure;
+ transaction.oncomplete = () => {
+ stats.committedTransactions += 1;
+ stats.committedWrites += writes;
+ stats.skippedWrites += skipped;
+ resolve(results);
+ };
+ transaction.onabort = () => {
+ stats.abortedTransactions += 1;
+ reject(failure || transaction.error || new DOMException('Chart storage transaction aborted', 'AbortError'));
+ };
+ transaction.onerror = event => { failure = event.target.error; };
+
+ function guarded(action) {
+ try {
+ action();
+ } catch (error) {
+ try {
+ transaction.abort();
+ } catch (abortError) {
+ // Another native listener may already have aborted this transaction.
+ // Its abort event owns the error; do not replace it with a late request failure.
+ if (abortError instanceof DOMException && abortError.name === 'InvalidStateError') return;
+ throw abortError;
+ }
+ failure = error;
+ }
+ }
+
+ function completeOperation(value) {
+ const snapshot = snapshotJson(value);
+ resultBytes += byteLength(snapshot);
+ assert(resultBytes <= limits.maxResultBytes, 'Chart batch result byte limit exceeded');
+ results.push(snapshot);
+ index += 1;
+ advance();
+ }
+
+ function advance() {
+ if (index === batch.length) return;
+ const operation = batch[index];
+ const request = store.get(operation.key);
+ request.onsuccess = () => guarded(() => {
+ if (operation.method === 'getItem') {
+ completeOperation(request.result === undefined ? null : request.result);
+ return;
+ }
+ function compareAndWrite(present) {
+ const current = request.result === undefined ? null : snapshotJson(request.result);
+ if (present && equalJson(current, operation.value)) {
+ skipped += 1;
+ completeOperation(operation.value);
+ } else {
+ const put = store.put(operation.value === null ? undefined : operation.value, operation.key);
+ writes += 1;
+ put.onsuccess = () => guarded(() => completeOperation(operation.value));
+ }
+ }
+ if (request.result === undefined) {
+ const existence = store.getKey(operation.key);
+ existence.onsuccess = () => guarded(() => compareAndWrite(existence.result !== undefined));
+ } else {
+ compareAndWrite(true);
+ }
+ });
+ }
+ guarded(advance);
+ });
+ }
+
+ /** Each store has one queue across instances, including operations awaiting ready. */
+ async function pump(coordinator) {
+ while (coordinator.queue.length > 0) {
+ const first = coordinator.queue.shift();
+ const batch = [first];
+ if (!first.barrier) {
+ let bytes = first.bytes;
+ while (coordinator.queue.length > 0 && batch.length < limits.maxBatchOperations) {
+ const next = coordinator.queue[0];
+ if (next.barrier || bytes + next.bytes > limits.maxBatchBytes) break;
+ batch.push(coordinator.queue.shift());
+ bytes += next.bytes;
+ }
+ }
+ try {
+ for (const raw of new Set(batch.map(operation => operation.raw))) {
+ await raw.ready();
+ assert(raw.driver() === localforage.INDEXEDDB, 'Managed chart storage requires the IndexedDB driver');
+ assert(raw._dbInfo.name === coordinator.name && raw._dbInfo.storeName === 'keyvaluepairs', 'Managed chart database configuration changed');
+ }
+ let values;
+ if (first.barrier) {
+ stats.barriers += 1;
+ const result = await first.raw[first.method](...first.args);
+ if (result !== undefined) assert(byteLength(snapshotJson(result)) <= limits.maxResultBytes, 'Chart barrier result byte limit exceeded');
+ values = [result];
+ } else {
+ values = await transact(first.raw._dbInfo.db, batch);
+ }
+ batch.forEach((operation, index) => finish(operation, true, values[index]));
+ } catch (error) {
+ // The batch boundary settles every accepted caller on validation, connection or IDB failure.
+ batch.forEach(operation => finish(operation, false, error));
+ }
+ }
+ coordinator.running = false;
+ notifyDrained();
+ }
+
+ function submit(raw, method, args, callback) {
+ if (callback !== undefined) assert(typeof callback === 'function', 'Storage callback must be a function');
+ let promise;
+ try {
+ let key;
+ let value;
+ if (['getItem', 'setItem', 'removeItem'].includes(method)) {
+ key = args[0];
+ assert(typeof key === 'string' && key.length > 0, 'Chart storage requires a nonempty string key');
+ }
+ if (method === 'setItem') value = snapshotJson(args[1]);
+ if (method === 'key') assert(Number.isSafeInteger(args[0]) && args[0] >= 0, 'Chart key index must be a nonnegative safe integer');
+ const copiedArgs = method === 'setItem' ? [key, value] : [...args];
+ const bytes = byteLength(copiedArgs);
+ const barrier = !((method === 'getItem' || method === 'setItem') && isChartKey(key));
+ if (method === 'setItem') assert(byteLength(value) <= limits.maxResultBytes, 'Chart set result byte limit exceeded');
+ assert(barrier || bytes <= limits.maxBatchBytes, 'Chart operation exceeds batch byte limit');
+ assert(stats.pendingOperations < limits.maxPendingOperations, 'Chart pending operation capacity exceeded');
+ assert(stats.pendingBytes + bytes <= limits.maxPendingBytes, 'Chart pending byte capacity exceeded');
+ const name = raw.config('name');
+ // Value inspection can run Proxy traps that synchronously stop admission.
+ assert(phase === 'active', 'Chart storage admission stopped during validation');
+ let coordinator = coordinators.get(name);
+ if (!coordinator) {
+ coordinator = { name, queue: [], running: false };
+ coordinators.set(name, coordinator);
+ }
+ promise = new Promise((resolve, reject) => {
+ coordinator.queue.push({ raw, method, args: copiedArgs, key, value, bytes, barrier, resolve, reject });
+ });
+ stats.acceptedOperations += 1;
+ stats.pendingOperations += 1;
+ stats.pendingBytes += bytes;
+ stats.peakPendingOperations = Math.max(stats.peakPendingOperations, stats.pendingOperations);
+ stats.peakPendingBytes = Math.max(stats.peakPendingBytes, stats.pendingBytes);
+ if (!coordinator.running) {
+ coordinator.running = true;
+ queueMicrotask(() => { void pump(coordinator); });
+ }
+ } catch (error) {
+ stats.rejectedOperations += 1;
+ promise = Promise.reject(error);
+ }
+ return withCallback(promise, callback);
+ }
+
+ /**
+ * Retained host instances outlive the optimization. The finite stop fence
+ * prevents new native calls from overtaking accepted batches during teardown.
+ * Native callbacks have method-specific signatures and receive the original args.
+ */
+ function forwardNative(raw, method, args) {
+ const invoke = () => Reflect.apply(raw[method], raw, args);
+ return phase === 'stopped' ? invoke() : stopping.then(invoke);
+ }
+
+ function callStorage(raw, method, arity, args) {
+ if (phase !== 'active') return forwardNative(raw, method, args);
+ return submit(raw, method, args.slice(0, arity), args[arity]);
+ }
+
+ function createInstance(...args) {
+ const raw = Reflect.apply(nativeCreateInstance, localforage, args);
+ if (phase === 'stopped' || !DATABASES.has(raw.config('name')) || raw.config('storeName') !== 'keyvaluepairs') return raw;
+ const facade = {
+ getItem: (...args) => callStorage(raw, 'getItem', 1, args),
+ setItem: (...args) => callStorage(raw, 'setItem', 2, args),
+ removeItem: (...args) => callStorage(raw, 'removeItem', 1, args),
+ clear: (...args) => callStorage(raw, 'clear', 0, args),
+ keys: (...args) => callStorage(raw, 'keys', 0, args),
+ key: (...args) => callStorage(raw, 'key', 1, args),
+ length: (...args) => callStorage(raw, 'length', 0, args),
+ ready(...args) {
+ return phase === 'active' ? raw.ready(...args) : forwardNative(raw, 'ready', args);
+ },
+ config(option) {
+ if (phase === 'stopped') return raw.config(option);
+ assert(option === undefined || typeof option === 'string', 'Managed chart configuration is immutable');
+ return structuredClone(raw.config(option));
+ },
+ driver() { return raw.driver(); },
+ createInstance,
+ };
+ for (const name of ['setDriver', 'dropInstance', 'defineDriver', 'iterate', 'getDriver', 'getSerializer']) {
+ facade[name] = (...args) => phase === 'active'
+ ? Promise.reject(new Error(`${name} is unsupported by the chart storage experiment`))
+ : forwardNative(raw, name, args);
+ }
+ return Object.freeze(facade);
+ }
+
+ localforage.createInstance = createInstance;
+ const installedFactoryDescriptor = Object.getOwnPropertyDescriptor(localforage, 'createInstance');
+ function assertFactoryOwnership() {
+ const descriptor = Object.getOwnPropertyDescriptor(localforage, 'createInstance');
+ assert(descriptor && ['value', 'writable', 'enumerable', 'configurable'].every(
+ key => descriptor[key] === installedFactoryDescriptor[key],
+ ), 'Chart factory ownership changed before restoration');
+ }
+
+ // A stopped facade cannot join a new installation's queue; require a fresh export lifecycle.
+ installations.add(localforage);
+ return Object.freeze({
+ getStats: () => ({ ...stats }),
+ drain,
+ stop() {
+ if (phase === 'active') {
+ assertFactoryOwnership();
+ phase = 'draining';
+ stopping = drain().then(() => {
+ assertFactoryOwnership();
+ if (originalFactoryDescriptor) Object.defineProperty(localforage, 'createInstance', originalFactoryDescriptor);
+ else delete localforage.createInstance;
+ phase = 'stopped';
+ });
+ }
+ return stopping;
+ },
+ });
+}
diff --git a/experiments/binance-chart-storage/anonymous-probe-contract.js b/experiments/binance-chart-storage/anonymous-probe-contract.js
new file mode 100644
index 0000000..35ff2b4
--- /dev/null
+++ b/experiments/binance-chart-storage/anonymous-probe-contract.js
@@ -0,0 +1,14 @@
+import { snapshotJson } from './adapter.js';
+
+/** Host catch may resolve failures, so diagnostic admission must account for its own rejection. */
+export function captureAnonymousMirrorBatch(target, entries, state) {
+ try {
+ if (self !== top || location.href !== 'https://www.binance.com/zh-CN/futures/USUSDT') {
+ throw new Error('Anonymous mirror probe left its fixed top-level URL');
+ }
+ return { target, entries: entries.map(([key, value]) => [key, snapshotJson(value)]) };
+ } catch (error) {
+ state.failed += 1;
+ throw error;
+ }
+}
diff --git a/experiments/binance-chart-storage/anonymous-probe-entry.js b/experiments/binance-chart-storage/anonymous-probe-entry.js
new file mode 100644
index 0000000..0be5d6e
--- /dev/null
+++ b/experiments/binance-chart-storage/anonymous-probe-entry.js
@@ -0,0 +1,51 @@
+import { observeChartStorageBootstrap } from './bootstrap.js';
+import { replaceChartMirrorFactory } from './mirror-module.js';
+import { createChartMirrorWriter } from './mirror-writer.js';
+import { snapshotJson, equalJson } from './adapter.js';
+import { captureAnonymousMirrorBatch } from './anonymous-probe-contract.js';
+
+/** Only the fresh-profile runner imports this entry; it is not a userscript. */
+if (self === top && location.origin === 'https://www.binance.com' && location.pathname === '/zh-CN/futures/USUSDT') {
+ const writer = createChartMirrorWriter({ scope: 'isolated-ususdt-probe' });
+ const state = { matched: 0, executed: 0, dispatched: 0, completed: 0, failed: 0, nativeCalls: 0, stopped: false, capture: 'waiting' };
+ let last;
+ const observer = observeChartStorageBootstrap({
+ replaceMirrorFactory(original) {
+ const factory = replaceChartMirrorFactory(original, (target, entries, nativeThunk) => {
+ const batch = captureAnonymousMirrorBatch(target, entries, state);
+ last = batch;
+ state.dispatched += 1;
+ const promises = writer.dispatch(target, entries, () => {
+ state.nativeCalls += 1;
+ return nativeThunk();
+ });
+ batch.completion = Promise.all(promises).then(
+ () => { state.completed += 1; return true; },
+ () => { state.failed += 1; return false; },
+ );
+ return promises;
+ });
+ state.matched += 1;
+ return factory;
+ },
+ onCapture() { state.executed += 1; },
+ });
+ observer.captured.then(() => { state.capture = 'captured'; }, () => { state.capture = 'rejected'; });
+
+ self.__ANONYMOUS_MIRROR_PROBE__ = Object.freeze({
+ snapshot: () => ({ ...state, writer: writer.getStats() }),
+ async verifyLast() {
+ if (!last) throw new Error('No mirror batch has executed');
+ const batch = last;
+ if (!await batch.completion) throw new Error('Observed mirror batch failed');
+ const values = await Promise.all(batch.entries.map(([key]) => batch.target.getItem(key)));
+ if (last !== batch) throw new Error('Mirror changed during verification');
+ return { entries: batch.entries.length, valuesMatch: values.every((value, index) => equalJson(snapshotJson(value), batch.entries[index][1])) };
+ },
+ async stop() {
+ await writer.stop();
+ observer.stop();
+ state.stopped = true;
+ },
+ });
+}
diff --git a/experiments/binance-chart-storage/bootstrap.js b/experiments/binance-chart-storage/bootstrap.js
new file mode 100644
index 0000000..3acf063
--- /dev/null
+++ b/experiments/binance-chart-storage/bootstrap.js
@@ -0,0 +1,162 @@
+const QUEUE_NAME = 'webpackChunkfutures_trade_ui';
+
+/**
+ * Observe first module execution before Rspack installs its chunk dispatcher.
+ * By default this only captures localForage. An explicit mirror replacer selects
+ * module 70940 and runs before registration; it never calls a storage method.
+ * The live scope is one incident page; the general experiment stays in the lab.
+ */
+export function observeChartStorageBootstrap({ onCapture, replaceMirrorFactory } = {}) {
+ const lab = location.origin === 'https://chart-storage.test';
+ const incidentPage = location.origin === 'https://www.binance.com'
+ && location.pathname === '/zh-CN/futures/USUSDT' && self === top;
+ if (!lab && !incidentPage) throw new Error('Storage bootstrap requires the lab or the top-level USUSDT incident page');
+ if (onCapture !== undefined && typeof onCapture !== 'function') throw new Error('onCapture must be a synchronous function');
+ if (replaceMirrorFactory !== undefined && typeof replaceMirrorFactory !== 'function') throw new Error('replaceMirrorFactory must be a synchronous function');
+ const moduleId = replaceMirrorFactory === undefined ? '43917' : '70940';
+
+ const globalDescriptor = Object.getOwnPropertyDescriptor(self, QUEUE_NAME);
+ if (globalDescriptor && !Object.hasOwn(globalDescriptor, 'value')) throw new Error('Storage bootstrap requires a native queue property');
+ const queue = globalDescriptor && globalDescriptor.value !== undefined ? globalDescriptor.value : [];
+ const nativeAppend = Array.prototype.push;
+ if (!Array.isArray(queue) || queue.length !== 0 || queue.push !== nativeAppend || Object.hasOwn(queue, 'push')) {
+ throw new Error('Storage bootstrap must start before runtime on a native empty queue');
+ }
+ if (globalDescriptor && globalDescriptor.value === undefined && !globalDescriptor.configurable) {
+ throw new Error('Storage bootstrap cannot own the existing global queue property');
+ }
+ const createdQueue = !globalDescriptor || globalDescriptor.value === undefined;
+ if (createdQueue) Object.defineProperty(self, QUEUE_NAME, { value: queue, writable: true, configurable: true, enumerable: true });
+
+ let runtimePush;
+ let inFlightAppends = 0;
+ let active = true;
+ let originalFactory;
+ let observedFactory;
+ let observedChunk;
+ let runtimeRequire;
+ let resolveCapture;
+ let rejectCapture;
+ const captured = new Promise((resolve, reject) => { resolveCapture = resolve; rejectCapture = reject; });
+
+ function restore() {
+ if (runtimeRequire && runtimeRequire.m[moduleId] === observedFactory) runtimeRequire.m[moduleId] = originalFactory;
+ if (observedChunk && observedChunk[1][moduleId] === observedFactory) observedChunk[1][moduleId] = originalFactory;
+ const descriptor = Object.getOwnPropertyDescriptor(queue, 'push');
+ if (!descriptor || descriptor.get !== getPush || descriptor.set !== setPush || !descriptor.configurable) {
+ throw new Error('Storage bootstrap lost queue accessor ownership');
+ }
+ if (runtimePush) {
+ Object.defineProperty(queue, 'push', { value: runtimePush, writable: true, configurable: true, enumerable: true });
+ } else {
+ delete queue.push;
+ if (createdQueue && inFlightAppends === 0 && queue.length === 0 && Object.getOwnPropertyDescriptor(self, QUEUE_NAME)?.value === queue) {
+ if (globalDescriptor) Object.defineProperty(self, QUEUE_NAME, globalDescriptor);
+ else delete self[QUEUE_NAME];
+ }
+ }
+ }
+
+ /** Observational failure is isolated from Rspack's persistent module-error cache. */
+ function finish(succeeded, result) {
+ if (!active) return;
+ active = false;
+ try {
+ restore();
+ } catch (cleanupError) {
+ if (succeeded) {
+ succeeded = false;
+ result = cleanupError;
+ }
+ }
+ if (succeeded) resolveCapture(result);
+ else rejectCapture(result);
+ }
+
+ function prepareChunk(chunk) {
+ if (!active) return chunk;
+ try {
+ if (!Array.isArray(chunk) || !Array.isArray(chunk[0]) || !chunk[1] || typeof chunk[1] !== 'object') {
+ throw new Error('Storage bootstrap observed an unsupported chunk shape');
+ }
+ if (!Object.hasOwn(chunk[1], moduleId)) return chunk;
+ if (originalFactory) throw new Error('Storage module registered twice before capture');
+ originalFactory = chunk[1][moduleId];
+ if (typeof originalFactory !== 'function') throw new Error('Storage module factory must be a function');
+ const hostRuntime = chunk[2];
+ if (hostRuntime !== undefined && typeof hostRuntime !== 'function') throw new Error('Storage chunk runtime callback must be a function');
+ const executionFactory = replaceMirrorFactory === undefined ? originalFactory : replaceMirrorFactory(originalFactory);
+ if (!active) return chunk;
+ if (typeof executionFactory !== 'function') throw new Error('Mirror replacement must return a factory synchronously');
+ observedFactory = function (module, exports, require) {
+ runtimeRequire = require;
+ let returned;
+ try {
+ returned = executionFactory.call(this, module, exports, require);
+ } catch (hostError) {
+ finish(false, hostError);
+ throw hostError;
+ }
+ if (active) {
+ try {
+ const result = onCapture === undefined ? undefined : onCapture(module.exports);
+ if (result && typeof result.then === 'function') throw new Error('Storage observation callback must complete synchronously');
+ finish(true, module.exports);
+ } catch (observationError) {
+ finish(false, observationError);
+ }
+ }
+ return returned;
+ };
+ observedChunk = [chunk[0], { ...chunk[1], [moduleId]: observedFactory }, require => {
+ runtimeRequire = require;
+ if (hostRuntime !== undefined) return hostRuntime(require);
+ }];
+ return observedChunk;
+ } catch (observationError) {
+ // Invalid or duplicate registrations still reach the host's original dispatcher.
+ finish(false, observationError);
+ return chunk;
+ }
+ }
+
+ /** Rspack retains this parent callback; after takeover it must only append. */
+ function preRuntimePush(...chunks) {
+ if (!active || runtimePush) return nativeAppend.apply(this, chunks);
+ inFlightAppends += 1;
+ try {
+ return nativeAppend.apply(this, chunks.map(prepareChunk));
+ } finally {
+ inFlightAppends -= 1;
+ }
+ }
+
+ function runtimeObservingPush(chunk) {
+ return runtimePush.call(this, prepareChunk(chunk));
+ }
+
+ function getPush() {
+ return runtimePush ? runtimeObservingPush : preRuntimePush;
+ }
+
+ function setPush(dispatcher) {
+ if (!active) {
+ Object.defineProperty(queue, 'push', { value: dispatcher, writable: true, configurable: true, enumerable: true });
+ return;
+ }
+ if (runtimePush || typeof dispatcher !== 'function') {
+ finish(false, new Error('Storage bootstrap observed an unexpected push replacement'));
+ Object.defineProperty(queue, 'push', { value: dispatcher, writable: true, configurable: true, enumerable: true });
+ return;
+ }
+ runtimePush = dispatcher;
+ }
+
+ Object.defineProperty(queue, 'push', { get: getPush, set: setPush, enumerable: true, configurable: true });
+ return Object.freeze({
+ captured,
+ stop() {
+ finish(false, new Error('Storage bootstrap stopped before module execution'));
+ },
+ });
+}
diff --git a/experiments/binance-chart-storage/build-native-preflight.js b/experiments/binance-chart-storage/build-native-preflight.js
new file mode 100644
index 0000000..29fdd23
--- /dev/null
+++ b/experiments/binance-chart-storage/build-native-preflight.js
@@ -0,0 +1,49 @@
+import assert from 'node:assert/strict';
+import { readFile, writeFile } from 'node:fs/promises';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+import { build } from 'esbuild';
+
+const directory = dirname(fileURLToPath(import.meta.url));
+const root = resolve(directory, '../..');
+const entry = resolve(directory, 'native-preflight-entry.user.js');
+const output = resolve(directory, 'native-preflight.user.js');
+
+/** Keep the review artifact self-contained and prove its complete dependency boundary. */
+export async function compileNativePreflight() {
+ const source = await readFile(entry, 'utf8');
+ const metadata = source.match(/^\/\/ ==UserScript==[\s\S]*?\/\/ ==\/UserScript==/)?.[0];
+ assert(metadata, 'Native preflight metadata is required');
+ const result = await build({
+ absWorkingDir: root,
+ banner: { js: metadata },
+ bundle: true,
+ charset: 'utf8',
+ format: 'iife',
+ legalComments: 'none',
+ minify: false,
+ platform: 'browser',
+ sourcemap: false,
+ target: ['es2020'],
+ metafile: true,
+ stdin: {
+ contents: source.replace(metadata, '').trimStart(), loader: 'js',
+ resolveDir: directory, sourcefile: entry,
+ },
+ write: false,
+ });
+ const inputs = Object.keys(result.metafile.inputs).sort();
+ assert.deepEqual(inputs, [
+ 'experiments/binance-chart-storage/bootstrap.js',
+ 'experiments/binance-chart-storage/mirror-module.js',
+ 'experiments/binance-chart-storage/native-preflight-core.js',
+ 'experiments/binance-chart-storage/native-preflight-entry.user.js',
+ ], 'Native preflight must not include a writer or runtime loader');
+ assert.equal(result.outputFiles.length, 1, 'Native preflight must be one static script');
+ return { code: result.outputFiles[0].text, inputs };
+}
+
+if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
+ const { code } = await compileNativePreflight();
+ await writeFile(output, code);
+}
diff --git a/experiments/binance-chart-storage/capture.js b/experiments/binance-chart-storage/capture.js
new file mode 100644
index 0000000..df5fdf9
--- /dev/null
+++ b/experiments/binance-chart-storage/capture.js
@@ -0,0 +1,71 @@
+const MODULE_ID = '43917';
+
+/**
+ * Observe the host's first module execution, never require a possibly unexecuted
+ * factory ourselves. onCapture runs synchronously before require returns so the
+ * host cannot create its initial instances before the adapter is installed.
+ */
+export function observeChartStorageModule(queue = self.webpackChunkfutures_trade_ui, { onCapture } = {}) {
+ if (location.origin !== 'https://chart-storage.test') throw new Error('Module capture is restricted to the isolated lab origin');
+ if (!Array.isArray(queue)) throw new Error('Expected the captured chart runtime chunk queue');
+ if (queue.some(chunk => Object.hasOwn(chunk[1], MODULE_ID))) throw new Error('Chart storage module is already registered; late capture is unsupported');
+ if (onCapture !== undefined && typeof onCapture !== 'function') throw new Error('onCapture must be a function');
+ const originalPush = queue.push;
+ let originalFactory;
+ let observedFactory;
+ let observedChunk;
+ let runtimeRequire;
+ let active = true;
+ let resolveCapture;
+ let rejectCapture;
+ const captured = new Promise((resolve, reject) => { resolveCapture = resolve; rejectCapture = reject; });
+
+ function restore() {
+ const ownsQueue = queue.push === observingPush;
+ if (ownsQueue) queue.push = originalPush;
+ if (runtimeRequire && runtimeRequire.m[MODULE_ID] === observedFactory) runtimeRequire.m[MODULE_ID] = originalFactory;
+ if (observedChunk) observedChunk[1][MODULE_ID] = originalFactory;
+ active = false;
+ if (!ownsQueue) throw new Error('Chart module observer lost chunk queue ownership');
+ }
+
+ function observingPush(chunk) {
+ if (!Object.hasOwn(chunk[1], MODULE_ID)) return originalPush.call(this, chunk);
+ if (originalFactory) throw new Error('Chart storage module was registered twice');
+ originalFactory = chunk[1][MODULE_ID];
+ observedFactory = function (module, exports, require) {
+ runtimeRequire = require;
+ try {
+ originalFactory.call(this, module, exports, require);
+ const actualExport = module.exports;
+ if (onCapture) {
+ const result = onCapture(actualExport);
+ if (result && typeof result.then === 'function') throw new Error('onCapture must install synchronously');
+ }
+ restore();
+ resolveCapture(actualExport);
+ } catch (error) {
+ rejectCapture(error);
+ if (active) restore();
+ throw error;
+ }
+ };
+ const hostRuntime = chunk[2];
+ observedChunk = [chunk[0], { ...chunk[1], [MODULE_ID]: observedFactory }, require => {
+ runtimeRequire = require;
+ if (hostRuntime) return hostRuntime(require);
+ }];
+ return originalPush.call(this, observedChunk);
+ }
+
+ queue.push = observingPush;
+ return Object.freeze({
+ captured,
+ stop() {
+ if (active) {
+ rejectCapture(new Error('Chart storage module capture stopped before execution'));
+ restore();
+ }
+ },
+ });
+}
diff --git a/experiments/binance-chart-storage/generate-mirror-module.js b/experiments/binance-chart-storage/generate-mirror-module.js
new file mode 100644
index 0000000..257c378
--- /dev/null
+++ b/experiments/binance-chart-storage/generate-mirror-module.js
@@ -0,0 +1,107 @@
+import assert from 'node:assert/strict';
+import { createHash } from 'node:crypto';
+import { readFile, writeFile } from 'node:fs/promises';
+import { parse } from 'acorn';
+
+const input = process.argv[2];
+const moduleOutput = process.argv[3] || new URL('mirror-module.js', import.meta.url);
+assert(input, 'Supply the public TradingView.99bc5074.js response file');
+const source = await readFile(input, 'utf8');
+const sha = value => createHash('sha256').update(value).digest('hex');
+assert.equal(sha(source), 'b6a2cddd017fb4517abb4ea458e19905c59f955a3f99efa18beb1e82f3938c1c', 'Public TradingView source changed');
+const ast = parse(source, { ecmaVersion: 'latest', ranges: true });
+function visit(node, action) {
+ if (!node || typeof node !== 'object') return;
+ action(node);
+ for (const value of Object.values(node)) {
+ if (Array.isArray(value)) value.forEach(child => visit(child, action));
+ else if (value?.type) visit(value, action);
+ }
+}
+const factories = [];
+visit(ast, node => {
+ if (node.type === 'Property' && node.key.value === 70940 && node.method) factories.push(node);
+});
+assert.equal(factories.length, 1, 'Expected one 70940 method factory');
+const factory = factories[0];
+assert.deepEqual([factory.start, factory.end], [6665, 36819]);
+const original = source.slice(factory.start, factory.end);
+assert.equal(sha(original), '37d249d1ceec4a41b54a959c1ab2c4b52b3061e504cf8cd81a824962af8c6c88');
+const targets = [];
+visit(factory, node => {
+ assert(node.type !== 'Identifier' || node.name !== '__dispatchChartMirror', 'Dispatch service name is shadowed');
+ if (node.type === 'AssignmentExpression' && node.operator === '=' && node.left.name === 'Wr'
+ && node.right.type === 'CallExpression' && node.right.callee.type === 'MemberExpression'
+ && node.right.callee.object.name === 'fr' && node.right.callee.property.name === 'map'
+ && source.slice(node.right.start, node.right.end).includes('Sr.setItem(Ot,At)')) targets.push(node.right);
+});
+assert.equal(targets.length, 1, 'Expected one original mirror write map');
+const target = targets[0];
+assert.deepEqual([target.start, target.end], [33783, 34025]);
+const raw = source.slice(target.start, target.end);
+const inserted = `__dispatchChartMirror(Sr,fr,()=>${raw})`;
+const first = target.start - factory.start;
+const last = target.end - factory.start;
+const transformed = original.slice(0, first) + inserted + original.slice(last);
+assert.equal(transformed.slice(0, first) + raw + transformed.slice(first + inserted.length), original, 'Reverse replacement must reproduce the whole factory');
+parse(`({${transformed}})`, { ecmaVersion: 'latest' });
+
+const moduleSource = `/* Generated from the pinned public 70940 factory; see mirror-scoped-manifest.json. */
+const originalFactorySource = ${JSON.stringify(original)};
+
+/** Static factory generation preserves the host lexical body except its mirror write expression. */
+export function createMirrorFactory(__dispatchChartMirror) {
+ if (typeof __dispatchChartMirror !== 'function') throw new Error('Mirror dispatch must be a function');
+ return {${transformed}}[70940];
+}
+
+/** Registration rejects source drift before any replacement can reach the runtime cache. */
+export function replaceChartMirrorFactory(originalFactory, dispatch) {
+ if (typeof originalFactory !== 'function' || Function.prototype.toString.call(originalFactory) !== originalFactorySource) {
+ throw new Error('Mirror factory source does not match the pinned public module');
+ }
+ return createMirrorFactory(dispatch);
+}
+`;
+const fixtureRoot = new URL('../../test/fixtures/binance-chart-storage/', import.meta.url);
+const hostManifest = JSON.parse(await readFile(new URL('host-persistence-manifest.json', fixtureRoot), 'utf8'));
+const declarations = hostManifest.fragments.filter(fragment => fragment.label.startsWith('declaration:70940:'));
+const callbackFragment = hostManifest.fragments.find(fragment => fragment.label === 'callback:70940:z');
+assert(callbackFragment, 'Missing original mirror callback provenance');
+for (const fragment of [...declarations, callbackFragment]) {
+ assert.equal(sha(source.slice(...fragment.source_utf16_range)), fragment.sha256, fragment.label);
+}
+const callback = source.slice(...callbackFragment.source_utf16_range);
+const callbackStart = target.start - callbackFragment.source_utf16_range[0];
+const callbackEnd = target.end - callbackFragment.source_utf16_range[0];
+const patchedCallback = callback.slice(0, callbackStart) + inserted + callback.slice(callbackEnd);
+assert.equal(transformed.slice(callbackFragment.source_utf16_range[0] - factory.start,
+ callbackFragment.source_utf16_range[1] - factory.start + inserted.length - raw.length), patchedCallback);
+const fixtureSource = `/* Exact native and single-expression transformed mirror callbacks. Dependencies come from host-persistence.js. */
+export function createNativeMirrorFactory() { return {${original}}[70940]; }
+export function createMirrorCallbacks(e,__dispatchChartMirror) {
+${declarations.map(fragment => `var ${source.slice(...fragment.source_utf16_range)};`).join('\n')}
+return {
+native(a,Cr={current:false},_r={current:true}) { return ${callback}; },
+patched(a,Cr={current:false},_r={current:true}) { return ${patchedCallback}; }
+};
+}
+`;
+parse(moduleSource, { ecmaVersion: 'latest', sourceType: 'module' });
+parse(fixtureSource, { ecmaVersion: 'latest', sourceType: 'module' });
+const manifest = {
+ description: 'Static 70940 factory with one pinned mirror-write RHS replacement; exact original callback fixture uses original host dependencies.',
+ source: { file: 'TradingView.99bc5074.js', url: 'https://bin.bnbstatic.com/static/chunks/TradingView.99bc5074.js', sha256: sha(source) },
+ offset_contract: 'Zero-based UTF-16 String.slice offsets; exclusive ends; SHA-256 uses UTF-8 bytes.',
+ original_factory: { source_utf16_range: [factory.start, factory.end], sha256: sha(original) },
+ replacement: { source_utf16_range: [target.start, target.end], factory_utf16_range: [first, last], original_sha256: sha(raw), inserted_sha256: sha(inserted) },
+ original_callback: callbackFragment,
+ transformed_callback_sha256: sha(patchedCallback),
+ outputs: Object.fromEntries([['mirror-module.js', moduleSource], ['mirror-scoped-callback.js', fixtureSource]]
+ .map(([file, value]) => [file, { sha256: sha(value), bytes: Buffer.byteLength(value) }])),
+ evidence_limits: 'Whole module source and inverse replacement are statically verified. Offline behavior executes exact callbacks with original dependency factories; full React module initialization is not executed.',
+};
+await writeFile(moduleOutput, moduleSource);
+await writeFile(new URL('mirror-scoped-callback.js', fixtureRoot), fixtureSource);
+await writeFile(new URL('mirror-scoped-manifest.json', fixtureRoot), `${JSON.stringify(manifest, null, 2)}\n`);
+console.log(JSON.stringify({ originalFactorySha256: sha(original), replacementRange: [target.start, target.end], outputs: manifest.outputs }));
diff --git a/experiments/binance-chart-storage/mirror-module.js b/experiments/binance-chart-storage/mirror-module.js
new file mode 100644
index 0000000..48b4c61
--- /dev/null
+++ b/experiments/binance-chart-storage/mirror-module.js
@@ -0,0 +1,16 @@
+/* Generated from the pinned public 70940 factory; see mirror-scoped-manifest.json. */
+const originalFactorySource = "70940(Z,x,e){\"use strict\";e.r(x),e.d(x,{TradingView:()=>Dt,default:()=>Cn});var o=e(49253),v=e(77135),d=e(75510),i=e(42288),p=e(31085),r=e(41594),M=e(43917),h=e.n(M),P=e(81936),b=e.n(P),V=e(7980),U=e.n(V),D=e(76469),c=e(4260),k=e(53837),B=e(27571),C=e(45250),y=e(87646),A=e.n(y),q=e(80817);const E=(0,C.range)(1,1380),m=(0,C.range)(1,365).map(t=>`${t}D`),R=(0,C.range)(1,52).map(t=>`${t}W`),I=(0,C.range)(1,12).map(t=>`${t}M`),F=[\"1S\",...E,...m,...R,...I],L=({symbol:t,params:n})=>{const a=new URLSearchParams(n);return`${t}@${a.toString()}`},X=t=>{const[n,a]=t.split(\"@\"),s=new URLSearchParams(a).entries(),g=A()(Array.from(s));return{symbol:n,params:g}},ue=()=>{const t=new Map;let n={},a=[],u={onReady:()=>null,resolveSymbol:()=>null,searchSymbols:()=>null,getBars:()=>null,subscribeBars:()=>null,unsubscribeBars:()=>null};return({config:s,onSymbolResolving:g,onSymbolResolved:S,onFetchSymbolsInfo:K,onFetchBars:Y,onSubscribeBars:H,onUnsubscribeBars:j,onFetchServerTimeApi:$,onTransformSymbolInfo:f=({template:T})=>T,onSearchSymbols:l=()=>!0})=>(u={onReady:T=>{setTimeout(async()=>{const _=(await K()).map(O=>({...O,ticker:L({symbol:O.symbol,params:O.params||{}})}));a=_.map(({ticker:O,symbol:G,description:te=\"\",exchange:J=\"\",type:oe=\"\"})=>({symbol:G,ticker:O,description:te,exchange:J,type:oe})),n=(0,C.keyBy)(_,\"ticker\"),T({exchanges:[],symbols_types:[],supported_resolutions:F,supports_marks:!1,supports_timescale_marks:!1,supports_time:!0,...s})})},resolveSymbol:async(T,W,_)=>{const O=n[T];if(!O){_(`cannot resolve symbol - ${T}`);return}const G=f({info:O,template:{description:O.description||\"\",fractional:!1,has_seconds:!0,seconds_multipliers:[\"1\"],has_intraday:!0,intraday_multipliers:[\"1\",\"3\",\"5\",\"15\",\"30\",\"60\",\"120\",\"240\",\"360\",\"480\",\"720\"],has_daily:!0,daily_multipliers:[\"1\"],has_weekly_and_monthly:!0,weekly_multipliers:[\"1\"],monthly_multipliers:[\"1\"],minmov:1,minmove2:0,name:O.symbol,ticker:O.ticker,timezone:\"Etc/UTC\",pricescale:O.tickSize,session:\"24x7\",type:O.type||\"\",exchange:O.exchange||\"\",listed_exchange:O.exchange||\"\",format:\"price\",supported_resolutions:F,visible_plots_set:\"ohlcv\",volume_precision:3}});if(g){const te=X(G.ticker||\"\");if(!await g({symbol:T,info:G,tickerInfo:te})){_(`symbol resolving error - ${T}`);return}}setTimeout(()=>{W(G),S?.(G)})},searchSymbols:(T,W,_,O)=>{const G=new RegExp(T,\"i\"),te=new RegExp(W,\"i\"),J=new RegExp(_,\"i\"),oe=a.filter(Te=>{const{symbol:N,ticker:w,description:re=\"\",exchange:ie=\"\",type:le=\"\"}=Te;return[N,w,re].some(de=>G.test(de||\"\"))&&te.test(ie)&&J.test(le)&&l?.({item:Te})});O(oe)},getBars:async(T,W,_,O,G)=>{const{from:te,to:J,firstDataRequest:oe}=_,Te=(0,q.Cz)(W);try{if(J>=0){const N=X(T.ticker||\"\"),w=await Y({symbolInfo:T,resolution:W,rangeStartDate:te,rangeEndDate:J,tickerInfo:N,binanceInterval:Te,firstDataRequest:oe});O(w,{noData:w.length===0})}else O([],{noData:!0})}catch(N){G(N.toString())}},subscribeBars:(T,W,_,O,G)=>{const te=X(T.ticker||\"\"),J=(0,q.Cz)(W),oe=H({symbolInfo:T,resolution:W,callback:_,onResetCacheNeededCallback:G,tickerInfo:te,binanceInterval:J});t.set(O,oe)},unsubscribeBars:T=>{t?.get(T)?.(),t?.delete(T),j?.(T)},getServerTime:async T=>{try{const W=await $();T(W)}catch(W){console.warn(W),T(Date.now()/1e3)}}},{datafeed:u,observers:t,symbolsInfo:n})},Le=({namespace:t,type:n})=>{const a=(0,r.useMemo)(()=>h().createInstance({name:t}),[t]);return(0,r.useCallback)(()=>{n===\"tv\"&&a.removeItem(\"myTradingView\")},[a,n])};var Ue=e(76535),pr=e(26531),Ye=e(17409),Qe=e(94652),Ae=e(76830),he=e(48651),Fe=e(79344),se=e(51029),Je=e(36307),or=e(25379),yr=e(44745),Ne=e(15727),rr=e(45358),sr=e(82071),Ve=e(85411),ge=e(94889),hr=e(94547),tr=e(80686),Ar=e(61875);function Fr(t){return(0,ge._)(t)||(0,hr._)(t)||(0,Ar._)(t)||(0,tr._)()}var Nr=e(92873),zr=e(35630),Vr=e(12055),Qr=e(79515),Br=e(80737),Jr=e(40545),Zr=e(98107),Kr=e(51846),Xr=\"Chart.Crosshair.PlusButton.DrawHorizontalLine\",wr=function(t){var n=t.namespace,a=t.symbol,u=t.priceType,s=t.crosshairPrice,g=(0,Nr.o)(\"\",\"kline-ui\").getI18n,S=(0,Vr.r)().format,K=k.y$(n),Y=(0,Br.S)({namespace:n}),H=(0,Jr.hw)(),j=(0,Zr.c)(),$=(0,Qr.nH)(),f=(0,se.Ye)(),l=(0,Kr.E)({symbol:a}),T=(0,Fe.f0)(a),W=T.isCM,_=(0,r.useRef)(s);(0,r.useEffect)(function(){_.current=s},[s]);var O=(0,r.useRef)(H);(0,r.useEffect)(function(){O.current=H},[H]);var G=(0,r.useRef)(j);(0,r.useEffect)(function(){G.current=j},[j]);var te=(0,r.useRef)(u);(0,r.useEffect)(function(){te.current=u},[u]);var J=(0,r.useRef)($);(0,r.useEffect)(function(){J.current=$},[$]);var oe=(0,r.useRef)(f);(0,r.useEffect)(function(){oe.current=f},[f]);var Te=(0,r.useRef)(l);(0,r.useEffect)(function(){Te.current=l},[l]);var N=(0,r.useRef)(W);return(0,r.useEffect)(function(){N.current=W},[W]),{items_processor:(function(){var w=(0,o._)(function(re,ie,le){var de,Be,ve,Ce,ce,Ke,Ze,Ge,Xe,Ie,He,ur,Tr,_r,Cr;return(0,i._)(this,function(Er){if(Be=K.getState().tradingViewReference,!Be)return console.warn(\"[context-menu] trading view reference is not found\"),[2,re];if(Be.chartsCount()>1)return[2,re];if(ve=Be.activeChart().symbolExt(),!ve)return[2,re];switch(Ce=ve.name,ce=_.current,Ke=le.menuName===\"CrosshairMenuView\"?\"chart-trading\":\"context-menu\",Ze={price:\"\".concat(ce.value),formattedPrice:ce.formatted,source:Ke,chartType:\"tradingview\"},Ge=(de=oe.current[Ce])===null||de===void 0?void 0:de.baseAsset,Xe=g(\"trade\",{defaultValue:\"Trade\"}),Ie=S(ce.formatted),He=G.current&&te.current===c.SJ.Last&&J.current&&Ge?[ie.createAction({actionId:\"Chart.PlaceOrder.BuyLimit\",label:\"\".concat(Xe,\" \").concat(Ge,\" @ \").concat(Ie,\" \").concat(g(\"limit\",{defaultValue:\"Limit\"})),onExecute:function(){return Te.current.onClickBuyLimit(Ze)}}),ie.createAction({actionId:\"Chart.PlaceOrder.BuyStop\",label:\"\".concat(Xe,\" \").concat(Ge,\" @ \").concat(Ie,\" \").concat(g(\"stop\",{defaultValue:\"Stop\"})),onExecute:function(){return Te.current.onClickBuyStop(Ze)}})]:[],ur=O.current?ie.createAction({actionId:\"Chart.PriceAlert.CreateAtCrosshair\",label:g(\"create-alert-at\",{defaultValue:\"Create Alert at {{price}}\",price:ce.formatted}),onExecute:function(){Y({symbol:Ce,price:ce.formatted,mode:zr.n.CREATE})}}):null,re.forEach(function(we){if(!(!(\"execute\"in we)||!(\"getState\"in we))){var Dr=we.getState();if(Dr.actionId===Xr){var z=we.execute.bind(we);we.execute=function(){(0,Kr.A)({elementId:\"draw_horizontal_line\",isDelivery:N.current,chartType:\"tradingview\"}),z()}}}}),le.menuName){case\"CrosshairMenuView\":return ur?[2,[ur].concat((0,d._)(He),(0,d._)(re.slice()))]:[2,(0,d._)(He).concat((0,d._)(re.slice()))];case\"ChartContextMenu\":return ur?(Tr=Fr(re),_r=Tr[0],Cr=Tr.slice(1),[2,[_r,ur].concat((0,d._)(He),(0,d._)(Cr))]):[2,(0,d._)(He).concat((0,d._)(re.slice()))];default:return[2,re]}return[2]})});return function(re,ie,le){return w.apply(this,arguments)}})()}},qr=function(){var t=(0,r.useState)({value:NaN,formatted:\"\"}),n=t[0],a=t[1],u=(0,r.useCallback)(function(s){var g=s.crosshairData,S=s.activeChart;S&&a({value:g.price,formatted:S.priceFormatter().format(g.price)})},[]);return{onCrosshairMoved:u,crosshairPrice:n}};const Hr=t=>Math.floor(Math.log10(t)),et=t=>{const[n=\"1\"]=t.split(\",\");return Hr(+n)},pe=({symbolInfo:t,minTick:n})=>{const{pricescale:a=2}=t||{};return n===\"default\"||n===void 0?Hr(a):et(n)},br=t=>(n,a)=>{try{return n?{format:u=>{const s=pe({symbolInfo:n,minTick:a});return t.format({symbolInfo:n,minTick:a,value:u,precision:s})}}:null}catch{return null}},Mr=10**12,rt=10**9,tt=10**6,jr=10**3,gr=t=>{const n=t.toString(),[,a=\"\"]=n.split(\".\");return Math.min(a.length,3)},nt=({value:t})=>{const n=Math.abs(t);if(n>=Mr){const a=t/Mr;return{value:a,unit:\"T\",precision:gr(a)}}if(n>=rt){const a=t/rt;return{value:a,unit:\"B\",precision:gr(a)}}if(n>=tt){const a=t/tt;return{value:a,unit:\"M\",precision:gr(a)}}if(n>=jr){const a=t/jr;return{value:a,unit:\"K\",precision:gr(a)}}return{value:t,unit:\"\",precision:gr(t)}},ut=t=>(n,a,u)=>{try{return a&&n.type===\"volume\"?{format:s=>s===void 0?\"\":t.formatVolume({format:n,symbolInfo:a,precision:u,value:s,volumeDetail:nt({value:s})})}:null}catch{return null}};var lt=function(){var t=(0,Vr.r)(),n=t.format,a=(0,r.useMemo)(function(){return br({format:function(s){var g=s.value,S=s.precision;return n(g,{precision:S})}})},[n]),u=(0,r.useMemo)(function(){return ut({formatVolume:function(s){var g=s.value,S=s.volumeDetail;if(g===void 0||!S)return\"\";var K=n(S.value,{precision:S.precision});return\"\".concat(K).concat(S.unit)}})},[n]);return{priceFormatterFactory:a,studyFormatterFactory:u}},dt=e(74069),ir=function(t){var n=(0,dt.K)(t),a=(0,r.useMemo)(function(){return n.map(function(u){var s=u.id,g=u.time,S=u.isBuy,K=u.price,Y=u.tooltip;return{id:s,text:\"\",price:K,quantity:\"\",time:Math.round(g/1e3),isBuy:S,tooltip:Y}})},[n]);return{executionOrders:a}},at=function(t){var n=t.namespace,a=t.symbol,u=(0,Br.j)({namespace:n,symbol:a});return(0,r.useMemo)(function(){return u.map(function(s){var g=s.id,S=s.price,K=s.formattedPrice,Y=s.sideText,H=s.showCloseButton,j=s.isPriceChangeable,$=s.cancelTooltipText,f=s.onClick,l=s.onCancel,T=s.onPriceChanged;return{id:g,price:+S,text:\"\".concat(Y,\" \").concat(K),editable:j,cancellable:H,cancelTooltip:$,type:c.ND.PriceAlert,onCancel:H?l:void 0,onMove:j?function(W){var _=W.price;T({currentPrice:_})}:void 0,onClick:f}})},[u])},vt=e(90664),ot=function(t){var n=t.symbol,a=(0,vt.w)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){var s=u.id,g=u.text,S=u.price;return{id:s,text:g,price:+S,type:c.ND.BreakEvenPrice}})},[a])},cr=e(82508),ft=e(91025),mt=e(36077),Rr=function(t){var n=t.symbol,a=(0,mt.c)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){return(0,ft._)((0,cr._)({},u),{type:c.ND.LiquidationPrice})})},[a])},pt=function(t){var n=t.namespace,a=t.symbol,u=at({namespace:n,symbol:a}),s=ot({symbol:a}),g=Rr({symbol:a});return(0,r.useMemo)(function(){return(0,d._)(u).concat((0,d._)(s),(0,d._)(g))},[u,s,g])},st=e(42902),Ir=e(90170),yt=e(63345),ht=e(48210),Pr=e(51289),xr=e(24326),$r=e(90922),kr=e(34235),it=e(63762),Ft=function(t){var n=(0,Fe.f0)(t),a=n.pricePrecision,u=n.isCM,s=(0,st.yD)(t)||{},g=s.lastPrice,S=g===void 0?\"0\":g,K=(0,kr.Bp)(u),Y=(0,it.HY)(t),H=(0,kr.gv)(u),j=(0,kr.WF)(u);return(0,r.useMemo)(function(){return(0,$r.qF)({orders:(0,d._)(K).concat((0,d._)(H),(0,d._)(j)),symbol:t}).map(function($){return Y({order:$,lastPrice:S,pricePrecision:a})})},[S,K,H,a,t,Y])},gt=e(31542),Tt=function(t){var n=t.type;(0,gt.u4)(\"webClick\",{module:\"chart\",elementId:n})},Nt=function(t){var n=t.symbol,a=t.namespace,u=(0,r.useState)(0),s=u[0],g=u[1],S=(0,r.useCallback)(function(){return g(function(N){return N+1})},[]),K=Ft(n),Y=(0,xr.IT)({namespace:a}),H=(0,Fe.f0)(n),j=H.isDelivery,$=H.contractType,f=(0,st.yD)(n)||{},l=f.lastPrice,T=l===void 0?\"0\":l,W=(0,yt.s)(n),_=(0,it.YZ)({symbol:n,afterConfirmationModalIsShown:S}),O=(0,it.u8)(n),G=(0,it.jn)(n),te=(0,r.useCallback)(function(N){return(0,ht.D)({order:N,isDelivery:j,symbol:j?N.symbol:\"\".concat(N.symbol,\"_\").concat($),module:\"tradingview_kline\"})},[$,j]),J=(0,r.useMemo)(function(){return K.map(function(N){var w=N.order,re=N.price,ie=N.quantity,le=N.quantityText,de=N.isBuy,Be=N.text,ve=N.priceTooltipText,Ce=N.pnlText,ce=(0,Pr.$L)(w),Ke=(0,Pr.OX)(w),Ze=(0,Pr.xL)(w),Ge=ce===\"LIMIT\",Xe=(0,Ir.Y)(w);return{id:String(Ke),text:Be,price:+re,quantity:le,isBuy:de,editable:Xe&&G,tooltip:ve,modifyTooltip:w.closePosition||!Ge?\"\":void 0,onMove:G?function(Ie){var He=Ie.price;te(w),Tt({type:\"TV_vertical\"}),_({order:w,price:+He,quantity:+Ze,lastPrice:T})}:void 0,onModify:w.closePosition||!Ge||!Xe?void 0:function(){Tt({type:\"TV_amountclick\"}),O({order:w,price:re.toString(),quantity:ie,lastPrice:T})},onCancel:function(){Tt({type:\"TV_cancel\"}),W(w)}}})},[G,K,T,te,_,O,W,s]),oe=(0,r.useMemo)(function(){return Y.map(function(N){var w=N.id,re=N.value,ie=N.text,le=N.onChange,de=N.isBuy,Be=N.type;return{id:w,price:+re,quantity:\"\",text:ie,isBuy:de,editable:!0,limitOrderType:c.aA.OrderPreview,onMove:function(ve){var Ce=ve.price;(0,xr.eP)({namespace:a,orderType:Be}),le({id:w,newPrice:Ce})},onMoving:function(ve){var Ce=ve.price;return le({id:w,newPrice:Ce})}}})},[Y,a]),Te=(0,r.useMemo)(function(){return(0,d._)(J).concat((0,d._)(oe))},[J,oe]);return{orderLines:Te}},Vt=e(17855),Bt=function(t,n){var a=(0,r.useState)(t()),u=a[0],s=a[1];return(0,Vt.A)(function(){return s(t())},n),u},Kt=e(58656),Ht=function(t){var n=t.symbol,a=(0,Kt.Q)({symbol:n})||[],u=Bt(function(){return a.map(function(s){var g=s.position,S=s.pnl,K=s.pnlInNumber,Y=s.pnlText,H=s.price,j=s.quantity,$=s.reverseText,f=s.isBuy,l=s.displayReverseOrderModal,T=s.displayClosePositionModal;return{id:g.id||\"\".concat(g.symbol,\"-\").concat(g.positionSide),PNL:K,text:\"\".concat(Y,\" \").concat(S),price:H,quantity:j,tooltip:\"\",reverseTooltip:$,isBuy:f,onReverse:l,onClose:function(){T(),(0,gt.u4)(\"$WebClick\",{item:1,module:\"xxx\",$element_id:\"kline_close_position\"})}}})},[a]);return{positionLines:u}},jt=e(47354),bt=e.n(jt),$t=e(87017),Ut=e(99168),Yt=e(3742),Wt=e(43335),Gt=e(15540),zt=e(20163),Qt=e(47255);const Jt=t=>({endTime:n})=>(n??0)===t;var Zt=e(36488),Ct=e(48018),Xt=e(82911),We=e(69578),wt=e(53208),Rt=e(19862),qt=e(8874),en=e(26679),rn=e(66547),It=(function(){var t=(0,o._)(function(n){var a,u,s,g,S,K,Y,H,j;return(0,i._)(this,function($){switch($.label){case 0:a=n.onFetchKlineData,u=n.startTime,s=n.endTime,g=n.interval,S=n.limit,K=S===void 0?1e3:S,Y=[],H=[],j=s,$.label=1;case 1:return[4,a((0,cr._)({interval:g,limit:K},j===0?{}:{endTime:j}))];case 2:if(H=$.sent(),H.length===0)return[3,4];H.forEach(function(f){var l=(0,v._)(f,8),T=l[0],W=l[1],_=l[2],O=l[3],G=l[4],te=l[5],J=l[7];Y.push({time:+T,open:+W,high:+_,low:+O,close:+G,volume:+te,quantity:+J})}),j=H[0][0]-1,$.label=3;case 3:if(j>u)return[3,1];$.label=4;case 4:return Y.sort(function(f,l){return f.time-l.time}),[2,Y]}})});return function(a){return t.apply(this,arguments)}})(),tn=function(t){var n=t.firstDataRequest,a=t.hasRequested,u=t.rangeEndDate;return n&&!a?0:u*1e3},nn=e(78142),an=e(16238),St=e.n(an),on=e(98087),xt=e.n(on),sn=e(55009),cn=e.n(sn),kt=e(56477);const Et=t=>cn()(t.chartsCount(),n=>t.chart(n)),un=async t=>{try{const{schema:n,data:a}=await t.exportData({includedStudies:[]}),u=xt()(a)?.[n.findIndex(s=>s.type===\"time\")];return u!==void 0&&Number.isFinite(u)?u*1e3:void 0}catch{return}},ln=({getWidget:t,...n})=>{const a=new Map,u=new Map,s=()=>{try{const f=t();return f?St()(Et(f).map(l=>l.symbolExt()?.ticker)):[]}catch{return[]}},g=f=>{const l=s();return!l.length||l.includes(f)},S=f=>{const l=u.get(f);if(l)return l;const T={controller:new AbortController,claimedAsMain:!1};return u.set(f,T),T},K=f=>{const l=u.get(f);l&&(l.controller.abort(),u.delete(f))},Y=()=>{const f=s();f.length&&Array.from(u.keys()).forEach(l=>{const T=u.get(l);if(T){if(f.includes(l)){T.claimedAsMain=!0;return}a.has(l)||T.claimedAsMain&&K(l)}})},H=(f,l)=>{f.closed||l.time{f.held=[];let T=l;try{const _=(await f.options.fetchSince(l)).filter(O=>O.time>=l&&!f.closed);_.forEach(O=>{f.highWater=Math.max(f.highWater,O.time),f.options.onTick(O)}),T=xt()(_)?.time??l}finally{const W=f.held??[];f.held=void 0,W.filter(_=>_.time>=T).forEach(_=>{H(f,_)})}},$=(0,kt.k)({...n,onCheck:Y,repair:async()=>{const f=t();if(f)try{const l=Et(f),T=St()(l.map(_=>{const O=a.get(_.symbolExt()?.ticker??\"\");return O&&{chart:_,feed:O}}));if(!T.length){f.resetCache(),l.forEach(_=>{_.resetData()});return}const W=St()(await Promise.all(T.map(async({chart:_,feed:O})=>{const G=await un(_);return G===void 0?void 0:{feed:O,fromMs:G}})));await Promise.allSettled(W.map(({feed:_,fromMs:O})=>j(_,O)))}catch{}}});return{openFeed:f=>{const l={options:f,held:void 0,highWater:0,closed:!1,detector:void 0};return l.detector=(0,kt.k)({...n,subscribeSignals:f.subscribeSignals,repair:async()=>{l.closed||!l.highWater||g(f.ticker)||await j(l,l.highWater)}}),a.set(f.ticker,l),g(f.ticker)&&$.resubscribeSignals(f.subscribeSignals),{push:T=>{l.held?l.held.push(T):H(l,T)},close:()=>{l.closed=!0,l.detector?.dispose(),a.get(f.ticker)===l&&a.delete(f.ticker)}}},subscribeWake:$.subscribeWake,getSignal:f=>{const l=S(f);return s().includes(f)&&(l.claimedAsMain=!0),l.controller.signal},dispose:()=>{u.forEach(({controller:f})=>{f.abort()}),u.clear(),a.forEach(f=>{f.detector?.dispose()}),a.clear(),$.dispose()}}};var dn=e(71730),vn=e(36620),fn=function(t){var n=t.namespace,a=k.y$(n),u=(0,vn.l)().getSocketSignals,s=(0,r.useRef)();(0,r.useEffect)(function(){return function(){var S;(S=s.current)===null||S===void 0||S.instance.dispose(),s.current=void 0}},[]);var g=(0,r.useCallback)(function(S){var K,Y,H=S;if(((K=s.current)===null||K===void 0?void 0:K.key)===H)return s.current.instance;(Y=s.current)===null||Y===void 0||Y.instance.dispose();var j=nn.wr(S),$=ln({getWidget:function(){return a.getState().tradingViewReference},checkPeriodMs:dn.D(j)});return s.current={key:H,instance:$},$},[a]);return(0,r.useMemo)(function(){return{ensureRecovery:g,getSocketSignals:u}},[g,u])},mn=\"tv_web_error\",pn=[Ye.bt.TRADING,Ye.bt.DELIVERING],Re;(function(t){t.Futures=\"futures\",t.FuturesMark=\"futures-mark\",t.FuturesIndex=\"futures-index\",t.Delivery=\"delivery\",t.DeliveryMark=\"delivery-mark\",t.DeliveryIndex=\"delivery-index\"})(Re||(Re={}));var Mt=function(t){var n=t.symbol,a=t.description,u=t.productType,s=t.pricePrecision,g=t.params,S=t.supportedResolutions,K=S===void 0?F:S;return{symbol:n,description:a,tickSize:Math.pow(10,s),exchange:\"Binance\",supportedResolutions:K,type:u,params:g}},yn=function(t){var n=t.namespace,a=t.useMiniTicker,u=a===void 0?!1:a,s=t.isCM,g=c.y$(n),S=k.y$(n),K=(0,$t.useQueryClient)(),Y=fn({namespace:n}),H=Y.ensureRecovery,j=Y.getSocketSignals,$=(0,r.useRef)(0),f=(0,r.useRef)(!1),l=(0,r.useMemo)(function(){return bt()(function(){return(0,We.HP)({eventId:\"trd_chart_tv_load_timeout\",stepId:\"getBarsStart\",catch:function(Q){var ee=Q.error,ae=Q.track;ae({GET_BAR_START_TYPE:ee})},log:function(Q){var ee=Q.track;ee({GET_BAR_START_TYPE:\"SUCCESS\"})}})})},[]),T=(0,r.useMemo)(function(){return bt()(function(z){return(0,We.HP)({eventId:\"trd_chart_tv_load_timeout\",stepId:\"getBarsEnd\",catch:function(ee){var ae=ee.error,Se=ee.track;Se({GET_BAR_END_TYPE:ae,duration_:(0,We.Pu)(z-$.current)})},log:function(ee){var ae=ee.track;ae({GET_BAR_END_TYPE:\"SUCCESS\",duration_:(0,We.Pu)(z-$.current)})}})})},[]),W=(0,Nr.o)(\"trd-chart\",\"trade-ui\"),_=W.getI18n,O=(0,he.ud)(),G=O.getI18n,te=(0,v._)(g(c.Ir),1),J=te[0],oe=(0,v._)(S(k.A7),2),Te=oe[1],N=(0,se.Ye)(),w=(0,Zt.JD)(s).isContUnit,re=(0,se.y_)(),ie=(0,r.useRef)(N),le=(0,r.useRef)(J),de=(0,r.useRef)(re);(0,r.useEffect)(function(){ie.current=N},[N]),(0,r.useEffect)(function(){le.current=J},[J]),(0,r.useEffect)(function(){de.current=re},[re]);var Be=(0,r.useMemo)(function(){return w?Rt.b:Rt.S},[w]),ve=(0,r.useMemo)(function(){return G(\"usdtFutures-13\",{defaultValue:\"Futures\"})||\"\"},[G]),Ce=(0,r.useMemo)(function(){return G(\"inverseFutures-13\",{defaultValue:\"Delivery\"})||\"\"},[G]),ce=(0,r.useMemo)(function(){return _(\"indexPrice\",{defaultValue:\"Index Price\"})||\"\"},[_]),Ke=(0,r.useMemo)(function(){return _(\"markPrice\",{defaultValue:\"Mark Price\"})||\"\"},[_]),Ze=(0,r.useMemo)(function(){return _(\"lastPrice\",{defaultValue:\"Last Price\"})||\"\"},[_]),Ge=(0,r.useMemo)(function(){return{exchanges:[{name:\"Binance\",value:\"Binance\",desc:\"\"}],symbols_types:[{name:ve,value:Re.Futures},{name:\"\".concat(ve,\" \").concat(Ke),value:Re.FuturesMark},{name:\"\".concat(ve,\" \").concat(ce),value:Re.FuturesIndex},{name:Ce,value:Re.Delivery},{name:\"\".concat(Ce,\" \").concat(Ke),value:Re.DeliveryMark},{name:\"\".concat(Ce,\" \").concat(ce),value:Re.DeliveryIndex}]}},[Ce,ve,ce,Ke]),Xe=(0,r.useCallback)((0,o._)(function(){var z,Q,ee;return(0,i._)(this,function(ae){switch(ae.label){case 0:return ae.trys.push([0,2,,3]),[4,(0,en.c)(K)];case 1:return ae.sent(),[3,3];case 2:return z=ae.sent(),(0,gt.u4)(mn,{type:\"onFetchSymbolsInfo\",message:z.message,business:\"futures\"}),[3,3];case 3:return Q=ie.current,ee=Object.keys(Q).reduce(function(Se,xe){var ne=Q[xe]||Ct.o,ke=ne.symbol,Me=ne.pricePrecision,fe=ne.isDelivery,Pe=ne.contractStatus;if(!pn.includes(Pe))return Se;var _e=(0,Yt.S)(ne,G),je=_e.symbol,Ee=_e.contractTypeI18n,me=_e.deliveryDateFormat,ye=\"\".concat(je,\" \").concat(Ee).concat(me?\" \".concat(me):\"\"),$e=\"\".concat(ye,\" \").concat(Ze),Oe=\"\".concat(ye,\" \").concat(ce),lr=\"\".concat(ye,\" \").concat(Ke);return Se.concat([Mt({symbol:ke,description:$e,productType:fe?Re.Delivery:Re.Futures,pricePrecision:Me,params:{priceType:c.SJ.Last}}),Mt({symbol:ke,description:Oe,productType:fe?Re.DeliveryIndex:Re.FuturesIndex,pricePrecision:Me,params:{priceType:c.SJ.Index}}),Mt({symbol:ke,description:lr,productType:fe?Re.DeliveryMark:Re.FuturesMark,pricePrecision:(0,Xt.X)(Me),params:{priceType:c.SJ.Mark}})])},[]),[2,ee]}})}),[G,ce,Ke,Ze,K]),Ie=(0,r.useCallback)(function(z){var Q=z.ticker,ee=z.symbol,ae=z.interval,Se=z.priceType,xe=z.retryUntil,ne=ie.current[ee]||Ct.o,ke=ne.pair,Me=ne.contractType,fe=ne.contractVal,Pe=ne.isDelivery,_e=Pe?Be(fe):function(Oe){return Oe},je=(0,wt.h)({symbol:ee,pair:ke,contractType:Me,isDelivery:Pe,priceType:Se}),Ee,me,ye=function(Oe){return zt.y(\"tv\",\"\".concat(Pe?\"cm\":\"um\",\"|\").concat(ee,\"|\").concat(Se,\"|\").concat(Oe.interval,\"|\").concat((Ee=Oe.limit)!==null&&Ee!==void 0?Ee:\"\",\"|\").concat((me=Oe.endTime)!==null&&me!==void 0?me:\"\"))};if(xe===void 0)return{transform:_e,onFetchKlineData:je};var $e=H(ae);return{transform:_e,onFetchKlineData:Qt.b(je,{enabled:Jt(xe),key:ye,signal:$e.getSignal(Q),subscribeWake:$e.subscribeWake})}},[H,Be]),He=(0,r.useCallback)((function(){var z=(0,o._)(function(Q){var ee,ae,Se,xe,ne,ke,Me,fe,Pe,_e,je,Ee,me,ye,$e,Oe,lr,Or;return(0,i._)(this,function(dr){switch(dr.label){case 0:return ee=Q.binanceInterval,ae=Q.rangeStartDate,Se=Q.rangeEndDate,xe=Q.tickerInfo,ne=Q.symbolInfo,ke=Q.firstDataRequest,$.current=Date.now(),l(),Me=xe.symbol,fe=Me===void 0?\"\":Me,Pe=xe.params,_e=Pe.priceType,je=_e===void 0?le.current:_e,de.current({symbol:fe,priceType:je})?(T(Date.now()),[2,[]]):(Ee=Math.max(0,ae*1e3),me=tn({firstDataRequest:ke,hasRequested:f.current,rangeEndDate:Se}),f.current=!0,$e=Ie({ticker:(ye=ne.ticker)!==null&&ye!==void 0?ye:\"\",symbol:fe,interval:ee,priceType:je,retryUntil:ke?me:void 0}),Oe=$e.transform,lr=$e.onFetchKlineData,[4,It({onFetchKlineData:lr,startTime:Ee,endTime:me,interval:ee})]);case 1:return Or=dr.sent(),T(Date.now()),[2,Or.map(Oe)]}})});return function(Q){return z.apply(this,arguments)}})(),[Ie,T,l]),ur=(0,r.useCallback)(function(z){var Q=z.binanceInterval,ee=z.callback,ae=z.tickerInfo,Se=z.symbolInfo,xe=ae.symbol,ne=xe===void 0?\"\":xe,ke=ae.params,Me=ke.priceType,fe=Me===void 0?le.current:Me;if(de.current({symbol:ne,priceType:fe}))return function(){return null};var Pe=ie.current[ne]||Ct.o,_e=Pe.pair,je=Pe.contractType,Ee=Pe.isDelivery,me,ye=(me=Se.ticker)!==null&&me!==void 0?me:\"\",$e=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe}).transform,Oe=H(Q),lr=Oe.openFeed({ticker:ye,onTick:ee,subscribeSignals:j(Ee),fetchSince:(function(){var dr=(0,o._)(function(Lr){var Ur,Yr,ct,De;return(0,i._)(this,function(ze){switch(ze.label){case 0:return Ur=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe,retryUntil:0}),Yr=Ur.transform,ct=Ur.onFetchKlineData,[4,It({onFetchKlineData:ct,startTime:Lr,endTime:0,interval:Q})];case 1:return De=ze.sent(),[2,De.map(Yr)]}})});return function(Lr){return dr.apply(this,arguments)}})()}),Or=(0,rn.B)({symbol:ne,pair:_e,contractType:je,interval:Q,priceType:fe,isDelivery:Ee,useMiniTicker:u,callback:function(dr){lr.push($e(dr))}});return function(){lr.close(),Or()}},[Ie,H,j,u]),Tr=(0,r.useCallback)((0,o._)(function(){var z;return(0,i._)(this,function(Q){switch(Q.label){case 0:return[4,K.fetchQuery(Wt.Bz.SERVER_TIME(s),function(){return(0,Gt.l)(s)},{})];case 1:return z=Q.sent(),[2,Math.floor(z/1e3)]}})}),[K,s]),_r=(0,r.useCallback)(function(z){return(0,cr._)({},z.template,(0,qt.sp)(s))},[s]),Cr=(0,r.useMemo)(function(){return ue()},[]),Er=Cr({config:Ge,onFetchSymbolsInfo:Xe,onFetchBars:He,onSubscribeBars:ur,onFetchServerTimeApi:Tr,onTransformSymbolInfo:_r}),we=Er.datafeed,Dr=Er.symbolsInfo;return(0,Ut.A)(function(){return Te()},[w]),{datafeed:we,symbolsInfo:Dr}},Pt=\"trd_chart_tv_load_timeout\",hn=function(){var t=(0,We.PM)({id:\"chartReady\"}),n=(0,We.PM)({id:\"getBarsStart\",timeout:3}),a=(0,We.PM)({id:\"getBarsEnd\",timeout:6}),u=(0,We.PM)({id:\"chartResponsive\",timeout:3});We.HP.init({step:t._(n)._(a)._(u),eventId:Pt}),(0,We.HP)({eventId:Pt,stepId:\"chartReady\",catch:function(g){var S=g.error,K=g.track;K({CHART_READY_TYPE:S,connection:JSON.stringify(navigator.connection)})},log:function(g){var S=g.track;S({CHART_READY_TYPE:\"SUCCESS\",connection:JSON.stringify(navigator.connection)})}})},gn=function(t){(0,We.HP)({eventId:Pt,stepId:\"chartResponsive\",catch:function(a){var u=a.error,s=a.track;s({CHART_RESPONSIVE_TYPE:u,connection:JSON.stringify(navigator.connection)})},log:function(a){var u=a.track;u({CHART_RESPONSIVE_TYPE:\"SUCCESS\",duration_:(0,We.Pu)(Date.now()-t)})}})},Tn=[Qe.ZE,Qe.F3],Dt=function(t){var n=t.symbol,a=t.namespace,u=t.useMiniTicker,s=u===void 0?!1:u,g=c.y$(a),S=k.y$(a),K=(0,v._)(g(c.A8),1),Y=K[0],H=(0,v._)(g(c.Ir),2),j=H[0],$=H[1],f=(0,v._)(S(k.yj),1),l=f[0],T=(0,v._)(g(c.e6),1),W=T[0],_=(0,v._)(g(c._b),1),O=_[0],G=(0,he.ot)(),te=(0,pr.K7)(),J=te.pathname.includes(Ae.Q2),oe=te.pathname.includes(Ae.eq),Te=(0,r.useMemo)(function(){return J?(0,d._)(B.oT).concat([\"hide_left_toolbar_by_default\"]):B.oT},[J]),N=(0,se.Ye)(),w=(0,se.v3)({symbol:n}),re=(0,Fe.es)(n),ie=yn({namespace:a,useMiniTicker:s,isCM:re}),le=ie.datafeed,de=ie.symbolsInfo,Be=(0,Je.DP)(),ve=Be.theme,Ce=ve===\"light\"||ve===\"light_glacier\"?\"light\":\"dark\",ce=(0,yr.Z)(Ce),Ke=(0,r.useRef)(-1),Ze=Ht({symbol:n}).positionLines,Ge=Nt({symbol:n,namespace:a}).orderLines,Xe=ir({symbol:n}).executionOrders,Ie=pt({namespace:a,symbol:n}),He=(0,se.y0)(),ur=(0,r.useRef)(He),Tr=(0,r.useRef)(N),_r=(0,r.useRef)(l),Cr=(0,r.useRef)(J),Er=(0,r.useRef)(oe),we=(0,r.useRef)(de);(0,r.useEffect)(function(){we.current=de},[de]),(0,r.useEffect)(function(){ur.current=He},[He]),(0,r.useEffect)(function(){Tr.current=N},[N]),(0,r.useEffect)(function(){_r.current=l},[l]),(0,r.useEffect)(function(){Cr.current=J},[J]),(0,r.useEffect)(function(){Er.current=oe},[oe]),(0,Ve.a)(n,s),(0,sr.A)({symbol:n,interval:Y,priceType:j});var Dr=(0,r.useCallback)(function(){Ke.current=Date.now()},[]),z=(0,r.useCallback)((0,o._)(function(){var De,ze,qe,be,vr,fr,er;return(0,i._)(this,function(nr){switch(nr.label){case 0:if(Cr.current||!_r.current)return[2,Promise.resolve()];nr.label=1;case 1:return nr.trys.push([1,5,,6]),De=h().createInstance({name:a}),[4,De.keys()];case 2:return ze=nr.sent(),qe=ze.filter(function(mr){return/^((#TV_SYMBOL-)|(myTradingView))/g.test(mr)}),be=qe.map((function(){var mr=(0,o._)(function(ar){return(0,i._)(this,function(Sr){return[2,De.getItem(ar)]})});return function(ar){return mr.apply(this,arguments)}})()),[4,Promise.all(be)];case 3:return vr=nr.sent(),fr=U()(qe,vr),[4,Promise.all(Tn.map((function(){var mr=(0,o._)(function(ar){var Sr,Wr;return(0,i._)(this,function(Gr){switch(Gr.label){case 0:return a===ar?[2,Promise.resolve()]:(Sr=h().createInstance({name:ar}),Wr=fr.map((function(){var Sn=(0,o._)(function(Lt){var _t,Ot,At;return(0,i._)(this,function(Mn){return _t=(0,v._)(Lt,2),Ot=_t[0],At=_t[1],Ot?[2,Sr.setItem(Ot,At)]:[2,Promise.resolve()]})});return function(Lt){return Sn.apply(this,arguments)}})()),[4,Promise.all(Wr)]);case 1:return Gr.sent(),[2,Promise.resolve()]}})});return function(ar){return mr.apply(this,arguments)}})()))];case 4:return nr.sent(),[2,Promise.resolve()];case 5:return er=nr.sent(),console.warn(typeof er==\"string\"?er:er.message),[2,Promise.resolve()];case 6:return[2]}})}),[a]),Q=(0,r.useMemo)(function(){return z},[z]),ee=(0,or.R)(),ae=(0,Ne.fQ)(a),Se=(0,Ne.m3)(a),xe=(0,Ne.S_)(a),ne=(0,Ne.Ds)(a),ke=(0,r.useRef)(n),Me=(0,r.useRef)(Y),fe=(0,r.useCallback)(function(){return ee(\"tradingView\"),xe({panel_index:0,symbol:n,interval:Y}),hn()},[Y,ee,n,xe]),Pe=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be,vr,fr,er=(fr=(vr=(be=qe.symbolExt())===null||be===void 0?void 0:be.name)!==null&&vr!==void 0?vr:qe.symbol())!==null&&fr!==void 0?fr:\"\";ae({panel_index:ze,symbol:er,previous_symbol:ke.current}),ke.current=er},[ae]),_e=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be=qe.resolution();Se({panel_index:ze,interval:be,previous_interval:Me.current}),Me.current=be},[Se]),je=(0,r.useCallback)(function(){gn(Ke.current)},[]),Ee=(0,r.useCallback)(function(){},[]),me=(0,r.useMemo)(function(){return J?Ee:void 0},[Ee,J]),ye=(0,r.useCallback)((function(){var De=(0,o._)(function(ze){var qe,be,vr,fr,er,nr,mr,ar,Sr;return(0,i._)(this,function(Wr){switch(Wr.label){case 0:return qe=ze.chart,be=qe.symbolExt(),be?(vr=b()(we.current,function(Gr){return Gr.ticker===be.ticker||Gr.symbol===be.ticker}),fr=vr||{},er=fr.params,nr=er===void 0?{}:er,mr=nr.priceType,ar=mr===void 0?\"\":mr,ar&&$(ar),Sr=Tr.current[be.name],Sr?Er.current||Cr.current?[2]:[4,z()]:[2]):[2];case 1:return Wr.sent(),G({stateSymbol:Sr.symbol,isDeliveryColumn:Sr.isDelivery}),[2]}})});return function(ze){return De.apply(this,arguments)}})(),[G,z,$]),$e=qr(),Oe=$e.onCrosshairMoved,lr=$e.crosshairPrice,Or=wr({namespace:a,symbol:n,priceType:j,crosshairPrice:lr}),dr=lt(),Lr=(0,r.useMemo)(function(){return L({symbol:n,params:{priceType:j}})},[n,j]),Ur=(0,r.useMemo)(function(){return{symbol:Lr,theme:ce,limitOrders:Ge,positionOrders:Ze,executionOrders:Xe,labelLines:Ie,initialConfig:{tvConfig:{symbol:Lr,library_path:\"/\".concat(Ye.u_,\"/trading-platform-30/\"),theme:ce,datafeed:le,enabled_features:Te,context_menu:Or,custom_formatters:dr},onCrosshairMoved:Oe,onScriptLoaded:Dr,onSave:Q,onChartReadyDone:fe,onFirstTimeDataLoadedDone:je,onInitChart:me,onActiveChartChanged:ye,onSymbolChanged:Pe,onIntervalChanged:_e}}},[Lr,le,Te,Xe,Ie,Or,dr,Oe,ye,fe,je,me,_e,Q,Dr,Pe,Ge,Ze,ce]),Yr=Le({namespace:a,type:\"tv\"}),ct=(0,r.useCallback)(function(){ne({panel_index:0,symbol:n}),Yr()},[Yr,n,ne]);return(0,p.jsxs)(D.t,{name:\"trading_view\",onReset:ct,children:[(0,p.jsx)(Ue.A,{namespace:a,tradingViewProps:Ur}),w&&j===\"index\"&&O===c.Ev.Single&&W===c.tU.TradingView&&(0,p.jsx)(rr.B,{})]})};const Cn=Dt}";
+
+/** Static factory generation preserves the host lexical body except its mirror write expression. */
+export function createMirrorFactory(__dispatchChartMirror) {
+ if (typeof __dispatchChartMirror !== 'function') throw new Error('Mirror dispatch must be a function');
+ return {70940(Z,x,e){"use strict";e.r(x),e.d(x,{TradingView:()=>Dt,default:()=>Cn});var o=e(49253),v=e(77135),d=e(75510),i=e(42288),p=e(31085),r=e(41594),M=e(43917),h=e.n(M),P=e(81936),b=e.n(P),V=e(7980),U=e.n(V),D=e(76469),c=e(4260),k=e(53837),B=e(27571),C=e(45250),y=e(87646),A=e.n(y),q=e(80817);const E=(0,C.range)(1,1380),m=(0,C.range)(1,365).map(t=>`${t}D`),R=(0,C.range)(1,52).map(t=>`${t}W`),I=(0,C.range)(1,12).map(t=>`${t}M`),F=["1S",...E,...m,...R,...I],L=({symbol:t,params:n})=>{const a=new URLSearchParams(n);return`${t}@${a.toString()}`},X=t=>{const[n,a]=t.split("@"),s=new URLSearchParams(a).entries(),g=A()(Array.from(s));return{symbol:n,params:g}},ue=()=>{const t=new Map;let n={},a=[],u={onReady:()=>null,resolveSymbol:()=>null,searchSymbols:()=>null,getBars:()=>null,subscribeBars:()=>null,unsubscribeBars:()=>null};return({config:s,onSymbolResolving:g,onSymbolResolved:S,onFetchSymbolsInfo:K,onFetchBars:Y,onSubscribeBars:H,onUnsubscribeBars:j,onFetchServerTimeApi:$,onTransformSymbolInfo:f=({template:T})=>T,onSearchSymbols:l=()=>!0})=>(u={onReady:T=>{setTimeout(async()=>{const _=(await K()).map(O=>({...O,ticker:L({symbol:O.symbol,params:O.params||{}})}));a=_.map(({ticker:O,symbol:G,description:te="",exchange:J="",type:oe=""})=>({symbol:G,ticker:O,description:te,exchange:J,type:oe})),n=(0,C.keyBy)(_,"ticker"),T({exchanges:[],symbols_types:[],supported_resolutions:F,supports_marks:!1,supports_timescale_marks:!1,supports_time:!0,...s})})},resolveSymbol:async(T,W,_)=>{const O=n[T];if(!O){_(`cannot resolve symbol - ${T}`);return}const G=f({info:O,template:{description:O.description||"",fractional:!1,has_seconds:!0,seconds_multipliers:["1"],has_intraday:!0,intraday_multipliers:["1","3","5","15","30","60","120","240","360","480","720"],has_daily:!0,daily_multipliers:["1"],has_weekly_and_monthly:!0,weekly_multipliers:["1"],monthly_multipliers:["1"],minmov:1,minmove2:0,name:O.symbol,ticker:O.ticker,timezone:"Etc/UTC",pricescale:O.tickSize,session:"24x7",type:O.type||"",exchange:O.exchange||"",listed_exchange:O.exchange||"",format:"price",supported_resolutions:F,visible_plots_set:"ohlcv",volume_precision:3}});if(g){const te=X(G.ticker||"");if(!await g({symbol:T,info:G,tickerInfo:te})){_(`symbol resolving error - ${T}`);return}}setTimeout(()=>{W(G),S?.(G)})},searchSymbols:(T,W,_,O)=>{const G=new RegExp(T,"i"),te=new RegExp(W,"i"),J=new RegExp(_,"i"),oe=a.filter(Te=>{const{symbol:N,ticker:w,description:re="",exchange:ie="",type:le=""}=Te;return[N,w,re].some(de=>G.test(de||""))&&te.test(ie)&&J.test(le)&&l?.({item:Te})});O(oe)},getBars:async(T,W,_,O,G)=>{const{from:te,to:J,firstDataRequest:oe}=_,Te=(0,q.Cz)(W);try{if(J>=0){const N=X(T.ticker||""),w=await Y({symbolInfo:T,resolution:W,rangeStartDate:te,rangeEndDate:J,tickerInfo:N,binanceInterval:Te,firstDataRequest:oe});O(w,{noData:w.length===0})}else O([],{noData:!0})}catch(N){G(N.toString())}},subscribeBars:(T,W,_,O,G)=>{const te=X(T.ticker||""),J=(0,q.Cz)(W),oe=H({symbolInfo:T,resolution:W,callback:_,onResetCacheNeededCallback:G,tickerInfo:te,binanceInterval:J});t.set(O,oe)},unsubscribeBars:T=>{t?.get(T)?.(),t?.delete(T),j?.(T)},getServerTime:async T=>{try{const W=await $();T(W)}catch(W){console.warn(W),T(Date.now()/1e3)}}},{datafeed:u,observers:t,symbolsInfo:n})},Le=({namespace:t,type:n})=>{const a=(0,r.useMemo)(()=>h().createInstance({name:t}),[t]);return(0,r.useCallback)(()=>{n==="tv"&&a.removeItem("myTradingView")},[a,n])};var Ue=e(76535),pr=e(26531),Ye=e(17409),Qe=e(94652),Ae=e(76830),he=e(48651),Fe=e(79344),se=e(51029),Je=e(36307),or=e(25379),yr=e(44745),Ne=e(15727),rr=e(45358),sr=e(82071),Ve=e(85411),ge=e(94889),hr=e(94547),tr=e(80686),Ar=e(61875);function Fr(t){return(0,ge._)(t)||(0,hr._)(t)||(0,Ar._)(t)||(0,tr._)()}var Nr=e(92873),zr=e(35630),Vr=e(12055),Qr=e(79515),Br=e(80737),Jr=e(40545),Zr=e(98107),Kr=e(51846),Xr="Chart.Crosshair.PlusButton.DrawHorizontalLine",wr=function(t){var n=t.namespace,a=t.symbol,u=t.priceType,s=t.crosshairPrice,g=(0,Nr.o)("","kline-ui").getI18n,S=(0,Vr.r)().format,K=k.y$(n),Y=(0,Br.S)({namespace:n}),H=(0,Jr.hw)(),j=(0,Zr.c)(),$=(0,Qr.nH)(),f=(0,se.Ye)(),l=(0,Kr.E)({symbol:a}),T=(0,Fe.f0)(a),W=T.isCM,_=(0,r.useRef)(s);(0,r.useEffect)(function(){_.current=s},[s]);var O=(0,r.useRef)(H);(0,r.useEffect)(function(){O.current=H},[H]);var G=(0,r.useRef)(j);(0,r.useEffect)(function(){G.current=j},[j]);var te=(0,r.useRef)(u);(0,r.useEffect)(function(){te.current=u},[u]);var J=(0,r.useRef)($);(0,r.useEffect)(function(){J.current=$},[$]);var oe=(0,r.useRef)(f);(0,r.useEffect)(function(){oe.current=f},[f]);var Te=(0,r.useRef)(l);(0,r.useEffect)(function(){Te.current=l},[l]);var N=(0,r.useRef)(W);return(0,r.useEffect)(function(){N.current=W},[W]),{items_processor:(function(){var w=(0,o._)(function(re,ie,le){var de,Be,ve,Ce,ce,Ke,Ze,Ge,Xe,Ie,He,ur,Tr,_r,Cr;return(0,i._)(this,function(Er){if(Be=K.getState().tradingViewReference,!Be)return console.warn("[context-menu] trading view reference is not found"),[2,re];if(Be.chartsCount()>1)return[2,re];if(ve=Be.activeChart().symbolExt(),!ve)return[2,re];switch(Ce=ve.name,ce=_.current,Ke=le.menuName==="CrosshairMenuView"?"chart-trading":"context-menu",Ze={price:"".concat(ce.value),formattedPrice:ce.formatted,source:Ke,chartType:"tradingview"},Ge=(de=oe.current[Ce])===null||de===void 0?void 0:de.baseAsset,Xe=g("trade",{defaultValue:"Trade"}),Ie=S(ce.formatted),He=G.current&&te.current===c.SJ.Last&&J.current&&Ge?[ie.createAction({actionId:"Chart.PlaceOrder.BuyLimit",label:"".concat(Xe," ").concat(Ge," @ ").concat(Ie," ").concat(g("limit",{defaultValue:"Limit"})),onExecute:function(){return Te.current.onClickBuyLimit(Ze)}}),ie.createAction({actionId:"Chart.PlaceOrder.BuyStop",label:"".concat(Xe," ").concat(Ge," @ ").concat(Ie," ").concat(g("stop",{defaultValue:"Stop"})),onExecute:function(){return Te.current.onClickBuyStop(Ze)}})]:[],ur=O.current?ie.createAction({actionId:"Chart.PriceAlert.CreateAtCrosshair",label:g("create-alert-at",{defaultValue:"Create Alert at {{price}}",price:ce.formatted}),onExecute:function(){Y({symbol:Ce,price:ce.formatted,mode:zr.n.CREATE})}}):null,re.forEach(function(we){if(!(!("execute"in we)||!("getState"in we))){var Dr=we.getState();if(Dr.actionId===Xr){var z=we.execute.bind(we);we.execute=function(){(0,Kr.A)({elementId:"draw_horizontal_line",isDelivery:N.current,chartType:"tradingview"}),z()}}}}),le.menuName){case"CrosshairMenuView":return ur?[2,[ur].concat((0,d._)(He),(0,d._)(re.slice()))]:[2,(0,d._)(He).concat((0,d._)(re.slice()))];case"ChartContextMenu":return ur?(Tr=Fr(re),_r=Tr[0],Cr=Tr.slice(1),[2,[_r,ur].concat((0,d._)(He),(0,d._)(Cr))]):[2,(0,d._)(He).concat((0,d._)(re.slice()))];default:return[2,re]}return[2]})});return function(re,ie,le){return w.apply(this,arguments)}})()}},qr=function(){var t=(0,r.useState)({value:NaN,formatted:""}),n=t[0],a=t[1],u=(0,r.useCallback)(function(s){var g=s.crosshairData,S=s.activeChart;S&&a({value:g.price,formatted:S.priceFormatter().format(g.price)})},[]);return{onCrosshairMoved:u,crosshairPrice:n}};const Hr=t=>Math.floor(Math.log10(t)),et=t=>{const[n="1"]=t.split(",");return Hr(+n)},pe=({symbolInfo:t,minTick:n})=>{const{pricescale:a=2}=t||{};return n==="default"||n===void 0?Hr(a):et(n)},br=t=>(n,a)=>{try{return n?{format:u=>{const s=pe({symbolInfo:n,minTick:a});return t.format({symbolInfo:n,minTick:a,value:u,precision:s})}}:null}catch{return null}},Mr=10**12,rt=10**9,tt=10**6,jr=10**3,gr=t=>{const n=t.toString(),[,a=""]=n.split(".");return Math.min(a.length,3)},nt=({value:t})=>{const n=Math.abs(t);if(n>=Mr){const a=t/Mr;return{value:a,unit:"T",precision:gr(a)}}if(n>=rt){const a=t/rt;return{value:a,unit:"B",precision:gr(a)}}if(n>=tt){const a=t/tt;return{value:a,unit:"M",precision:gr(a)}}if(n>=jr){const a=t/jr;return{value:a,unit:"K",precision:gr(a)}}return{value:t,unit:"",precision:gr(t)}},ut=t=>(n,a,u)=>{try{return a&&n.type==="volume"?{format:s=>s===void 0?"":t.formatVolume({format:n,symbolInfo:a,precision:u,value:s,volumeDetail:nt({value:s})})}:null}catch{return null}};var lt=function(){var t=(0,Vr.r)(),n=t.format,a=(0,r.useMemo)(function(){return br({format:function(s){var g=s.value,S=s.precision;return n(g,{precision:S})}})},[n]),u=(0,r.useMemo)(function(){return ut({formatVolume:function(s){var g=s.value,S=s.volumeDetail;if(g===void 0||!S)return"";var K=n(S.value,{precision:S.precision});return"".concat(K).concat(S.unit)}})},[n]);return{priceFormatterFactory:a,studyFormatterFactory:u}},dt=e(74069),ir=function(t){var n=(0,dt.K)(t),a=(0,r.useMemo)(function(){return n.map(function(u){var s=u.id,g=u.time,S=u.isBuy,K=u.price,Y=u.tooltip;return{id:s,text:"",price:K,quantity:"",time:Math.round(g/1e3),isBuy:S,tooltip:Y}})},[n]);return{executionOrders:a}},at=function(t){var n=t.namespace,a=t.symbol,u=(0,Br.j)({namespace:n,symbol:a});return(0,r.useMemo)(function(){return u.map(function(s){var g=s.id,S=s.price,K=s.formattedPrice,Y=s.sideText,H=s.showCloseButton,j=s.isPriceChangeable,$=s.cancelTooltipText,f=s.onClick,l=s.onCancel,T=s.onPriceChanged;return{id:g,price:+S,text:"".concat(Y," ").concat(K),editable:j,cancellable:H,cancelTooltip:$,type:c.ND.PriceAlert,onCancel:H?l:void 0,onMove:j?function(W){var _=W.price;T({currentPrice:_})}:void 0,onClick:f}})},[u])},vt=e(90664),ot=function(t){var n=t.symbol,a=(0,vt.w)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){var s=u.id,g=u.text,S=u.price;return{id:s,text:g,price:+S,type:c.ND.BreakEvenPrice}})},[a])},cr=e(82508),ft=e(91025),mt=e(36077),Rr=function(t){var n=t.symbol,a=(0,mt.c)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){return(0,ft._)((0,cr._)({},u),{type:c.ND.LiquidationPrice})})},[a])},pt=function(t){var n=t.namespace,a=t.symbol,u=at({namespace:n,symbol:a}),s=ot({symbol:a}),g=Rr({symbol:a});return(0,r.useMemo)(function(){return(0,d._)(u).concat((0,d._)(s),(0,d._)(g))},[u,s,g])},st=e(42902),Ir=e(90170),yt=e(63345),ht=e(48210),Pr=e(51289),xr=e(24326),$r=e(90922),kr=e(34235),it=e(63762),Ft=function(t){var n=(0,Fe.f0)(t),a=n.pricePrecision,u=n.isCM,s=(0,st.yD)(t)||{},g=s.lastPrice,S=g===void 0?"0":g,K=(0,kr.Bp)(u),Y=(0,it.HY)(t),H=(0,kr.gv)(u),j=(0,kr.WF)(u);return(0,r.useMemo)(function(){return(0,$r.qF)({orders:(0,d._)(K).concat((0,d._)(H),(0,d._)(j)),symbol:t}).map(function($){return Y({order:$,lastPrice:S,pricePrecision:a})})},[S,K,H,a,t,Y])},gt=e(31542),Tt=function(t){var n=t.type;(0,gt.u4)("webClick",{module:"chart",elementId:n})},Nt=function(t){var n=t.symbol,a=t.namespace,u=(0,r.useState)(0),s=u[0],g=u[1],S=(0,r.useCallback)(function(){return g(function(N){return N+1})},[]),K=Ft(n),Y=(0,xr.IT)({namespace:a}),H=(0,Fe.f0)(n),j=H.isDelivery,$=H.contractType,f=(0,st.yD)(n)||{},l=f.lastPrice,T=l===void 0?"0":l,W=(0,yt.s)(n),_=(0,it.YZ)({symbol:n,afterConfirmationModalIsShown:S}),O=(0,it.u8)(n),G=(0,it.jn)(n),te=(0,r.useCallback)(function(N){return(0,ht.D)({order:N,isDelivery:j,symbol:j?N.symbol:"".concat(N.symbol,"_").concat($),module:"tradingview_kline"})},[$,j]),J=(0,r.useMemo)(function(){return K.map(function(N){var w=N.order,re=N.price,ie=N.quantity,le=N.quantityText,de=N.isBuy,Be=N.text,ve=N.priceTooltipText,Ce=N.pnlText,ce=(0,Pr.$L)(w),Ke=(0,Pr.OX)(w),Ze=(0,Pr.xL)(w),Ge=ce==="LIMIT",Xe=(0,Ir.Y)(w);return{id:String(Ke),text:Be,price:+re,quantity:le,isBuy:de,editable:Xe&&G,tooltip:ve,modifyTooltip:w.closePosition||!Ge?"":void 0,onMove:G?function(Ie){var He=Ie.price;te(w),Tt({type:"TV_vertical"}),_({order:w,price:+He,quantity:+Ze,lastPrice:T})}:void 0,onModify:w.closePosition||!Ge||!Xe?void 0:function(){Tt({type:"TV_amountclick"}),O({order:w,price:re.toString(),quantity:ie,lastPrice:T})},onCancel:function(){Tt({type:"TV_cancel"}),W(w)}}})},[G,K,T,te,_,O,W,s]),oe=(0,r.useMemo)(function(){return Y.map(function(N){var w=N.id,re=N.value,ie=N.text,le=N.onChange,de=N.isBuy,Be=N.type;return{id:w,price:+re,quantity:"",text:ie,isBuy:de,editable:!0,limitOrderType:c.aA.OrderPreview,onMove:function(ve){var Ce=ve.price;(0,xr.eP)({namespace:a,orderType:Be}),le({id:w,newPrice:Ce})},onMoving:function(ve){var Ce=ve.price;return le({id:w,newPrice:Ce})}}})},[Y,a]),Te=(0,r.useMemo)(function(){return(0,d._)(J).concat((0,d._)(oe))},[J,oe]);return{orderLines:Te}},Vt=e(17855),Bt=function(t,n){var a=(0,r.useState)(t()),u=a[0],s=a[1];return(0,Vt.A)(function(){return s(t())},n),u},Kt=e(58656),Ht=function(t){var n=t.symbol,a=(0,Kt.Q)({symbol:n})||[],u=Bt(function(){return a.map(function(s){var g=s.position,S=s.pnl,K=s.pnlInNumber,Y=s.pnlText,H=s.price,j=s.quantity,$=s.reverseText,f=s.isBuy,l=s.displayReverseOrderModal,T=s.displayClosePositionModal;return{id:g.id||"".concat(g.symbol,"-").concat(g.positionSide),PNL:K,text:"".concat(Y," ").concat(S),price:H,quantity:j,tooltip:"",reverseTooltip:$,isBuy:f,onReverse:l,onClose:function(){T(),(0,gt.u4)("$WebClick",{item:1,module:"xxx",$element_id:"kline_close_position"})}}})},[a]);return{positionLines:u}},jt=e(47354),bt=e.n(jt),$t=e(87017),Ut=e(99168),Yt=e(3742),Wt=e(43335),Gt=e(15540),zt=e(20163),Qt=e(47255);const Jt=t=>({endTime:n})=>(n??0)===t;var Zt=e(36488),Ct=e(48018),Xt=e(82911),We=e(69578),wt=e(53208),Rt=e(19862),qt=e(8874),en=e(26679),rn=e(66547),It=(function(){var t=(0,o._)(function(n){var a,u,s,g,S,K,Y,H,j;return(0,i._)(this,function($){switch($.label){case 0:a=n.onFetchKlineData,u=n.startTime,s=n.endTime,g=n.interval,S=n.limit,K=S===void 0?1e3:S,Y=[],H=[],j=s,$.label=1;case 1:return[4,a((0,cr._)({interval:g,limit:K},j===0?{}:{endTime:j}))];case 2:if(H=$.sent(),H.length===0)return[3,4];H.forEach(function(f){var l=(0,v._)(f,8),T=l[0],W=l[1],_=l[2],O=l[3],G=l[4],te=l[5],J=l[7];Y.push({time:+T,open:+W,high:+_,low:+O,close:+G,volume:+te,quantity:+J})}),j=H[0][0]-1,$.label=3;case 3:if(j>u)return[3,1];$.label=4;case 4:return Y.sort(function(f,l){return f.time-l.time}),[2,Y]}})});return function(a){return t.apply(this,arguments)}})(),tn=function(t){var n=t.firstDataRequest,a=t.hasRequested,u=t.rangeEndDate;return n&&!a?0:u*1e3},nn=e(78142),an=e(16238),St=e.n(an),on=e(98087),xt=e.n(on),sn=e(55009),cn=e.n(sn),kt=e(56477);const Et=t=>cn()(t.chartsCount(),n=>t.chart(n)),un=async t=>{try{const{schema:n,data:a}=await t.exportData({includedStudies:[]}),u=xt()(a)?.[n.findIndex(s=>s.type==="time")];return u!==void 0&&Number.isFinite(u)?u*1e3:void 0}catch{return}},ln=({getWidget:t,...n})=>{const a=new Map,u=new Map,s=()=>{try{const f=t();return f?St()(Et(f).map(l=>l.symbolExt()?.ticker)):[]}catch{return[]}},g=f=>{const l=s();return!l.length||l.includes(f)},S=f=>{const l=u.get(f);if(l)return l;const T={controller:new AbortController,claimedAsMain:!1};return u.set(f,T),T},K=f=>{const l=u.get(f);l&&(l.controller.abort(),u.delete(f))},Y=()=>{const f=s();f.length&&Array.from(u.keys()).forEach(l=>{const T=u.get(l);if(T){if(f.includes(l)){T.claimedAsMain=!0;return}a.has(l)||T.claimedAsMain&&K(l)}})},H=(f,l)=>{f.closed||l.time{f.held=[];let T=l;try{const _=(await f.options.fetchSince(l)).filter(O=>O.time>=l&&!f.closed);_.forEach(O=>{f.highWater=Math.max(f.highWater,O.time),f.options.onTick(O)}),T=xt()(_)?.time??l}finally{const W=f.held??[];f.held=void 0,W.filter(_=>_.time>=T).forEach(_=>{H(f,_)})}},$=(0,kt.k)({...n,onCheck:Y,repair:async()=>{const f=t();if(f)try{const l=Et(f),T=St()(l.map(_=>{const O=a.get(_.symbolExt()?.ticker??"");return O&&{chart:_,feed:O}}));if(!T.length){f.resetCache(),l.forEach(_=>{_.resetData()});return}const W=St()(await Promise.all(T.map(async({chart:_,feed:O})=>{const G=await un(_);return G===void 0?void 0:{feed:O,fromMs:G}})));await Promise.allSettled(W.map(({feed:_,fromMs:O})=>j(_,O)))}catch{}}});return{openFeed:f=>{const l={options:f,held:void 0,highWater:0,closed:!1,detector:void 0};return l.detector=(0,kt.k)({...n,subscribeSignals:f.subscribeSignals,repair:async()=>{l.closed||!l.highWater||g(f.ticker)||await j(l,l.highWater)}}),a.set(f.ticker,l),g(f.ticker)&&$.resubscribeSignals(f.subscribeSignals),{push:T=>{l.held?l.held.push(T):H(l,T)},close:()=>{l.closed=!0,l.detector?.dispose(),a.get(f.ticker)===l&&a.delete(f.ticker)}}},subscribeWake:$.subscribeWake,getSignal:f=>{const l=S(f);return s().includes(f)&&(l.claimedAsMain=!0),l.controller.signal},dispose:()=>{u.forEach(({controller:f})=>{f.abort()}),u.clear(),a.forEach(f=>{f.detector?.dispose()}),a.clear(),$.dispose()}}};var dn=e(71730),vn=e(36620),fn=function(t){var n=t.namespace,a=k.y$(n),u=(0,vn.l)().getSocketSignals,s=(0,r.useRef)();(0,r.useEffect)(function(){return function(){var S;(S=s.current)===null||S===void 0||S.instance.dispose(),s.current=void 0}},[]);var g=(0,r.useCallback)(function(S){var K,Y,H=S;if(((K=s.current)===null||K===void 0?void 0:K.key)===H)return s.current.instance;(Y=s.current)===null||Y===void 0||Y.instance.dispose();var j=nn.wr(S),$=ln({getWidget:function(){return a.getState().tradingViewReference},checkPeriodMs:dn.D(j)});return s.current={key:H,instance:$},$},[a]);return(0,r.useMemo)(function(){return{ensureRecovery:g,getSocketSignals:u}},[g,u])},mn="tv_web_error",pn=[Ye.bt.TRADING,Ye.bt.DELIVERING],Re;(function(t){t.Futures="futures",t.FuturesMark="futures-mark",t.FuturesIndex="futures-index",t.Delivery="delivery",t.DeliveryMark="delivery-mark",t.DeliveryIndex="delivery-index"})(Re||(Re={}));var Mt=function(t){var n=t.symbol,a=t.description,u=t.productType,s=t.pricePrecision,g=t.params,S=t.supportedResolutions,K=S===void 0?F:S;return{symbol:n,description:a,tickSize:Math.pow(10,s),exchange:"Binance",supportedResolutions:K,type:u,params:g}},yn=function(t){var n=t.namespace,a=t.useMiniTicker,u=a===void 0?!1:a,s=t.isCM,g=c.y$(n),S=k.y$(n),K=(0,$t.useQueryClient)(),Y=fn({namespace:n}),H=Y.ensureRecovery,j=Y.getSocketSignals,$=(0,r.useRef)(0),f=(0,r.useRef)(!1),l=(0,r.useMemo)(function(){return bt()(function(){return(0,We.HP)({eventId:"trd_chart_tv_load_timeout",stepId:"getBarsStart",catch:function(Q){var ee=Q.error,ae=Q.track;ae({GET_BAR_START_TYPE:ee})},log:function(Q){var ee=Q.track;ee({GET_BAR_START_TYPE:"SUCCESS"})}})})},[]),T=(0,r.useMemo)(function(){return bt()(function(z){return(0,We.HP)({eventId:"trd_chart_tv_load_timeout",stepId:"getBarsEnd",catch:function(ee){var ae=ee.error,Se=ee.track;Se({GET_BAR_END_TYPE:ae,duration_:(0,We.Pu)(z-$.current)})},log:function(ee){var ae=ee.track;ae({GET_BAR_END_TYPE:"SUCCESS",duration_:(0,We.Pu)(z-$.current)})}})})},[]),W=(0,Nr.o)("trd-chart","trade-ui"),_=W.getI18n,O=(0,he.ud)(),G=O.getI18n,te=(0,v._)(g(c.Ir),1),J=te[0],oe=(0,v._)(S(k.A7),2),Te=oe[1],N=(0,se.Ye)(),w=(0,Zt.JD)(s).isContUnit,re=(0,se.y_)(),ie=(0,r.useRef)(N),le=(0,r.useRef)(J),de=(0,r.useRef)(re);(0,r.useEffect)(function(){ie.current=N},[N]),(0,r.useEffect)(function(){le.current=J},[J]),(0,r.useEffect)(function(){de.current=re},[re]);var Be=(0,r.useMemo)(function(){return w?Rt.b:Rt.S},[w]),ve=(0,r.useMemo)(function(){return G("usdtFutures-13",{defaultValue:"Futures"})||""},[G]),Ce=(0,r.useMemo)(function(){return G("inverseFutures-13",{defaultValue:"Delivery"})||""},[G]),ce=(0,r.useMemo)(function(){return _("indexPrice",{defaultValue:"Index Price"})||""},[_]),Ke=(0,r.useMemo)(function(){return _("markPrice",{defaultValue:"Mark Price"})||""},[_]),Ze=(0,r.useMemo)(function(){return _("lastPrice",{defaultValue:"Last Price"})||""},[_]),Ge=(0,r.useMemo)(function(){return{exchanges:[{name:"Binance",value:"Binance",desc:""}],symbols_types:[{name:ve,value:Re.Futures},{name:"".concat(ve," ").concat(Ke),value:Re.FuturesMark},{name:"".concat(ve," ").concat(ce),value:Re.FuturesIndex},{name:Ce,value:Re.Delivery},{name:"".concat(Ce," ").concat(Ke),value:Re.DeliveryMark},{name:"".concat(Ce," ").concat(ce),value:Re.DeliveryIndex}]}},[Ce,ve,ce,Ke]),Xe=(0,r.useCallback)((0,o._)(function(){var z,Q,ee;return(0,i._)(this,function(ae){switch(ae.label){case 0:return ae.trys.push([0,2,,3]),[4,(0,en.c)(K)];case 1:return ae.sent(),[3,3];case 2:return z=ae.sent(),(0,gt.u4)(mn,{type:"onFetchSymbolsInfo",message:z.message,business:"futures"}),[3,3];case 3:return Q=ie.current,ee=Object.keys(Q).reduce(function(Se,xe){var ne=Q[xe]||Ct.o,ke=ne.symbol,Me=ne.pricePrecision,fe=ne.isDelivery,Pe=ne.contractStatus;if(!pn.includes(Pe))return Se;var _e=(0,Yt.S)(ne,G),je=_e.symbol,Ee=_e.contractTypeI18n,me=_e.deliveryDateFormat,ye="".concat(je," ").concat(Ee).concat(me?" ".concat(me):""),$e="".concat(ye," ").concat(Ze),Oe="".concat(ye," ").concat(ce),lr="".concat(ye," ").concat(Ke);return Se.concat([Mt({symbol:ke,description:$e,productType:fe?Re.Delivery:Re.Futures,pricePrecision:Me,params:{priceType:c.SJ.Last}}),Mt({symbol:ke,description:Oe,productType:fe?Re.DeliveryIndex:Re.FuturesIndex,pricePrecision:Me,params:{priceType:c.SJ.Index}}),Mt({symbol:ke,description:lr,productType:fe?Re.DeliveryMark:Re.FuturesMark,pricePrecision:(0,Xt.X)(Me),params:{priceType:c.SJ.Mark}})])},[]),[2,ee]}})}),[G,ce,Ke,Ze,K]),Ie=(0,r.useCallback)(function(z){var Q=z.ticker,ee=z.symbol,ae=z.interval,Se=z.priceType,xe=z.retryUntil,ne=ie.current[ee]||Ct.o,ke=ne.pair,Me=ne.contractType,fe=ne.contractVal,Pe=ne.isDelivery,_e=Pe?Be(fe):function(Oe){return Oe},je=(0,wt.h)({symbol:ee,pair:ke,contractType:Me,isDelivery:Pe,priceType:Se}),Ee,me,ye=function(Oe){return zt.y("tv","".concat(Pe?"cm":"um","|").concat(ee,"|").concat(Se,"|").concat(Oe.interval,"|").concat((Ee=Oe.limit)!==null&&Ee!==void 0?Ee:"","|").concat((me=Oe.endTime)!==null&&me!==void 0?me:""))};if(xe===void 0)return{transform:_e,onFetchKlineData:je};var $e=H(ae);return{transform:_e,onFetchKlineData:Qt.b(je,{enabled:Jt(xe),key:ye,signal:$e.getSignal(Q),subscribeWake:$e.subscribeWake})}},[H,Be]),He=(0,r.useCallback)((function(){var z=(0,o._)(function(Q){var ee,ae,Se,xe,ne,ke,Me,fe,Pe,_e,je,Ee,me,ye,$e,Oe,lr,Or;return(0,i._)(this,function(dr){switch(dr.label){case 0:return ee=Q.binanceInterval,ae=Q.rangeStartDate,Se=Q.rangeEndDate,xe=Q.tickerInfo,ne=Q.symbolInfo,ke=Q.firstDataRequest,$.current=Date.now(),l(),Me=xe.symbol,fe=Me===void 0?"":Me,Pe=xe.params,_e=Pe.priceType,je=_e===void 0?le.current:_e,de.current({symbol:fe,priceType:je})?(T(Date.now()),[2,[]]):(Ee=Math.max(0,ae*1e3),me=tn({firstDataRequest:ke,hasRequested:f.current,rangeEndDate:Se}),f.current=!0,$e=Ie({ticker:(ye=ne.ticker)!==null&&ye!==void 0?ye:"",symbol:fe,interval:ee,priceType:je,retryUntil:ke?me:void 0}),Oe=$e.transform,lr=$e.onFetchKlineData,[4,It({onFetchKlineData:lr,startTime:Ee,endTime:me,interval:ee})]);case 1:return Or=dr.sent(),T(Date.now()),[2,Or.map(Oe)]}})});return function(Q){return z.apply(this,arguments)}})(),[Ie,T,l]),ur=(0,r.useCallback)(function(z){var Q=z.binanceInterval,ee=z.callback,ae=z.tickerInfo,Se=z.symbolInfo,xe=ae.symbol,ne=xe===void 0?"":xe,ke=ae.params,Me=ke.priceType,fe=Me===void 0?le.current:Me;if(de.current({symbol:ne,priceType:fe}))return function(){return null};var Pe=ie.current[ne]||Ct.o,_e=Pe.pair,je=Pe.contractType,Ee=Pe.isDelivery,me,ye=(me=Se.ticker)!==null&&me!==void 0?me:"",$e=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe}).transform,Oe=H(Q),lr=Oe.openFeed({ticker:ye,onTick:ee,subscribeSignals:j(Ee),fetchSince:(function(){var dr=(0,o._)(function(Lr){var Ur,Yr,ct,De;return(0,i._)(this,function(ze){switch(ze.label){case 0:return Ur=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe,retryUntil:0}),Yr=Ur.transform,ct=Ur.onFetchKlineData,[4,It({onFetchKlineData:ct,startTime:Lr,endTime:0,interval:Q})];case 1:return De=ze.sent(),[2,De.map(Yr)]}})});return function(Lr){return dr.apply(this,arguments)}})()}),Or=(0,rn.B)({symbol:ne,pair:_e,contractType:je,interval:Q,priceType:fe,isDelivery:Ee,useMiniTicker:u,callback:function(dr){lr.push($e(dr))}});return function(){lr.close(),Or()}},[Ie,H,j,u]),Tr=(0,r.useCallback)((0,o._)(function(){var z;return(0,i._)(this,function(Q){switch(Q.label){case 0:return[4,K.fetchQuery(Wt.Bz.SERVER_TIME(s),function(){return(0,Gt.l)(s)},{})];case 1:return z=Q.sent(),[2,Math.floor(z/1e3)]}})}),[K,s]),_r=(0,r.useCallback)(function(z){return(0,cr._)({},z.template,(0,qt.sp)(s))},[s]),Cr=(0,r.useMemo)(function(){return ue()},[]),Er=Cr({config:Ge,onFetchSymbolsInfo:Xe,onFetchBars:He,onSubscribeBars:ur,onFetchServerTimeApi:Tr,onTransformSymbolInfo:_r}),we=Er.datafeed,Dr=Er.symbolsInfo;return(0,Ut.A)(function(){return Te()},[w]),{datafeed:we,symbolsInfo:Dr}},Pt="trd_chart_tv_load_timeout",hn=function(){var t=(0,We.PM)({id:"chartReady"}),n=(0,We.PM)({id:"getBarsStart",timeout:3}),a=(0,We.PM)({id:"getBarsEnd",timeout:6}),u=(0,We.PM)({id:"chartResponsive",timeout:3});We.HP.init({step:t._(n)._(a)._(u),eventId:Pt}),(0,We.HP)({eventId:Pt,stepId:"chartReady",catch:function(g){var S=g.error,K=g.track;K({CHART_READY_TYPE:S,connection:JSON.stringify(navigator.connection)})},log:function(g){var S=g.track;S({CHART_READY_TYPE:"SUCCESS",connection:JSON.stringify(navigator.connection)})}})},gn=function(t){(0,We.HP)({eventId:Pt,stepId:"chartResponsive",catch:function(a){var u=a.error,s=a.track;s({CHART_RESPONSIVE_TYPE:u,connection:JSON.stringify(navigator.connection)})},log:function(a){var u=a.track;u({CHART_RESPONSIVE_TYPE:"SUCCESS",duration_:(0,We.Pu)(Date.now()-t)})}})},Tn=[Qe.ZE,Qe.F3],Dt=function(t){var n=t.symbol,a=t.namespace,u=t.useMiniTicker,s=u===void 0?!1:u,g=c.y$(a),S=k.y$(a),K=(0,v._)(g(c.A8),1),Y=K[0],H=(0,v._)(g(c.Ir),2),j=H[0],$=H[1],f=(0,v._)(S(k.yj),1),l=f[0],T=(0,v._)(g(c.e6),1),W=T[0],_=(0,v._)(g(c._b),1),O=_[0],G=(0,he.ot)(),te=(0,pr.K7)(),J=te.pathname.includes(Ae.Q2),oe=te.pathname.includes(Ae.eq),Te=(0,r.useMemo)(function(){return J?(0,d._)(B.oT).concat(["hide_left_toolbar_by_default"]):B.oT},[J]),N=(0,se.Ye)(),w=(0,se.v3)({symbol:n}),re=(0,Fe.es)(n),ie=yn({namespace:a,useMiniTicker:s,isCM:re}),le=ie.datafeed,de=ie.symbolsInfo,Be=(0,Je.DP)(),ve=Be.theme,Ce=ve==="light"||ve==="light_glacier"?"light":"dark",ce=(0,yr.Z)(Ce),Ke=(0,r.useRef)(-1),Ze=Ht({symbol:n}).positionLines,Ge=Nt({symbol:n,namespace:a}).orderLines,Xe=ir({symbol:n}).executionOrders,Ie=pt({namespace:a,symbol:n}),He=(0,se.y0)(),ur=(0,r.useRef)(He),Tr=(0,r.useRef)(N),_r=(0,r.useRef)(l),Cr=(0,r.useRef)(J),Er=(0,r.useRef)(oe),we=(0,r.useRef)(de);(0,r.useEffect)(function(){we.current=de},[de]),(0,r.useEffect)(function(){ur.current=He},[He]),(0,r.useEffect)(function(){Tr.current=N},[N]),(0,r.useEffect)(function(){_r.current=l},[l]),(0,r.useEffect)(function(){Cr.current=J},[J]),(0,r.useEffect)(function(){Er.current=oe},[oe]),(0,Ve.a)(n,s),(0,sr.A)({symbol:n,interval:Y,priceType:j});var Dr=(0,r.useCallback)(function(){Ke.current=Date.now()},[]),z=(0,r.useCallback)((0,o._)(function(){var De,ze,qe,be,vr,fr,er;return(0,i._)(this,function(nr){switch(nr.label){case 0:if(Cr.current||!_r.current)return[2,Promise.resolve()];nr.label=1;case 1:return nr.trys.push([1,5,,6]),De=h().createInstance({name:a}),[4,De.keys()];case 2:return ze=nr.sent(),qe=ze.filter(function(mr){return/^((#TV_SYMBOL-)|(myTradingView))/g.test(mr)}),be=qe.map((function(){var mr=(0,o._)(function(ar){return(0,i._)(this,function(Sr){return[2,De.getItem(ar)]})});return function(ar){return mr.apply(this,arguments)}})()),[4,Promise.all(be)];case 3:return vr=nr.sent(),fr=U()(qe,vr),[4,Promise.all(Tn.map((function(){var mr=(0,o._)(function(ar){var Sr,Wr;return(0,i._)(this,function(Gr){switch(Gr.label){case 0:return a===ar?[2,Promise.resolve()]:(Sr=h().createInstance({name:ar}),Wr=__dispatchChartMirror(Sr,fr,()=>fr.map((function(){var Sn=(0,o._)(function(Lt){var _t,Ot,At;return(0,i._)(this,function(Mn){return _t=(0,v._)(Lt,2),Ot=_t[0],At=_t[1],Ot?[2,Sr.setItem(Ot,At)]:[2,Promise.resolve()]})});return function(Lt){return Sn.apply(this,arguments)}})())),[4,Promise.all(Wr)]);case 1:return Gr.sent(),[2,Promise.resolve()]}})});return function(ar){return mr.apply(this,arguments)}})()))];case 4:return nr.sent(),[2,Promise.resolve()];case 5:return er=nr.sent(),console.warn(typeof er=="string"?er:er.message),[2,Promise.resolve()];case 6:return[2]}})}),[a]),Q=(0,r.useMemo)(function(){return z},[z]),ee=(0,or.R)(),ae=(0,Ne.fQ)(a),Se=(0,Ne.m3)(a),xe=(0,Ne.S_)(a),ne=(0,Ne.Ds)(a),ke=(0,r.useRef)(n),Me=(0,r.useRef)(Y),fe=(0,r.useCallback)(function(){return ee("tradingView"),xe({panel_index:0,symbol:n,interval:Y}),hn()},[Y,ee,n,xe]),Pe=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be,vr,fr,er=(fr=(vr=(be=qe.symbolExt())===null||be===void 0?void 0:be.name)!==null&&vr!==void 0?vr:qe.symbol())!==null&&fr!==void 0?fr:"";ae({panel_index:ze,symbol:er,previous_symbol:ke.current}),ke.current=er},[ae]),_e=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be=qe.resolution();Se({panel_index:ze,interval:be,previous_interval:Me.current}),Me.current=be},[Se]),je=(0,r.useCallback)(function(){gn(Ke.current)},[]),Ee=(0,r.useCallback)(function(){},[]),me=(0,r.useMemo)(function(){return J?Ee:void 0},[Ee,J]),ye=(0,r.useCallback)((function(){var De=(0,o._)(function(ze){var qe,be,vr,fr,er,nr,mr,ar,Sr;return(0,i._)(this,function(Wr){switch(Wr.label){case 0:return qe=ze.chart,be=qe.symbolExt(),be?(vr=b()(we.current,function(Gr){return Gr.ticker===be.ticker||Gr.symbol===be.ticker}),fr=vr||{},er=fr.params,nr=er===void 0?{}:er,mr=nr.priceType,ar=mr===void 0?"":mr,ar&&$(ar),Sr=Tr.current[be.name],Sr?Er.current||Cr.current?[2]:[4,z()]:[2]):[2];case 1:return Wr.sent(),G({stateSymbol:Sr.symbol,isDeliveryColumn:Sr.isDelivery}),[2]}})});return function(ze){return De.apply(this,arguments)}})(),[G,z,$]),$e=qr(),Oe=$e.onCrosshairMoved,lr=$e.crosshairPrice,Or=wr({namespace:a,symbol:n,priceType:j,crosshairPrice:lr}),dr=lt(),Lr=(0,r.useMemo)(function(){return L({symbol:n,params:{priceType:j}})},[n,j]),Ur=(0,r.useMemo)(function(){return{symbol:Lr,theme:ce,limitOrders:Ge,positionOrders:Ze,executionOrders:Xe,labelLines:Ie,initialConfig:{tvConfig:{symbol:Lr,library_path:"/".concat(Ye.u_,"/trading-platform-30/"),theme:ce,datafeed:le,enabled_features:Te,context_menu:Or,custom_formatters:dr},onCrosshairMoved:Oe,onScriptLoaded:Dr,onSave:Q,onChartReadyDone:fe,onFirstTimeDataLoadedDone:je,onInitChart:me,onActiveChartChanged:ye,onSymbolChanged:Pe,onIntervalChanged:_e}}},[Lr,le,Te,Xe,Ie,Or,dr,Oe,ye,fe,je,me,_e,Q,Dr,Pe,Ge,Ze,ce]),Yr=Le({namespace:a,type:"tv"}),ct=(0,r.useCallback)(function(){ne({panel_index:0,symbol:n}),Yr()},[Yr,n,ne]);return(0,p.jsxs)(D.t,{name:"trading_view",onReset:ct,children:[(0,p.jsx)(Ue.A,{namespace:a,tradingViewProps:Ur}),w&&j==="index"&&O===c.Ev.Single&&W===c.tU.TradingView&&(0,p.jsx)(rr.B,{})]})};const Cn=Dt}}[70940];
+}
+
+/** Registration rejects source drift before any replacement can reach the runtime cache. */
+export function replaceChartMirrorFactory(originalFactory, dispatch) {
+ if (typeof originalFactory !== 'function' || Function.prototype.toString.call(originalFactory) !== originalFactorySource) {
+ throw new Error('Mirror factory source does not match the pinned public module');
+ }
+ return createMirrorFactory(dispatch);
+}
diff --git a/experiments/binance-chart-storage/mirror-writer.js b/experiments/binance-chart-storage/mirror-writer.js
new file mode 100644
index 0000000..e4aa8cc
--- /dev/null
+++ b/experiments/binance-chart-storage/mirror-writer.js
@@ -0,0 +1,184 @@
+import { snapshotJson, byteLength, equalJson } from './adapter.js';
+
+const DEFAULT_LIMITS = Object.freeze({
+ maxEntries: 512,
+ maxBatchBytes: 4 * 1024 * 1024,
+ maxReadBytes: 4 * 1024 * 1024,
+ maxPendingBatches: 16,
+ maxPendingBytes: 64 * 1024 * 1024,
+});
+
+function assert(condition, message) {
+ if (!condition) throw new Error(message);
+}
+
+/**
+ * One explicit mirror destination batch, never a replacement localForage API.
+ * The caller owns native instance creation, source reads and the original catch.
+ * Each accepted batch commits or aborts as a whole; it is never replayed.
+ * The explicit page probe scope relies on its harness to own an anonymous browser;
+ * origin/path validation alone cannot establish profile isolation or authorization.
+ */
+export function createChartMirrorWriter({ limits: suppliedLimits = {}, scope = 'lab' } = {}) {
+ assert(scope === 'lab' || scope === 'isolated-ususdt-probe', 'Unknown chart mirror scope');
+ if (scope === 'lab') {
+ assert(location.origin === 'https://chart-storage.test', 'Chart mirror writer requires the isolated lab origin');
+ } else {
+ assert(location.origin === 'https://www.binance.com' && location.pathname === '/zh-CN/futures/USUSDT' && self === top,
+ 'Chart mirror writer requires the top-level USUSDT probe page');
+ }
+ const limits = { ...DEFAULT_LIMITS, ...suppliedLimits };
+ for (const [key, value] of Object.entries(limits)) {
+ assert(Object.hasOwn(DEFAULT_LIMITS, key), `Unknown mirror limit: ${key}`);
+ assert(Number.isSafeInteger(value) && value > 0, `${key} must be a positive safe integer`);
+ }
+ let phase = 'active';
+ let stopping;
+ let resolveDrain;
+ const stats = {
+ acceptedBatches: 0, rejectedBatches: 0, failedBatches: 0,
+ pendingBatches: 0, pendingBytes: 0, peakPendingBatches: 0, peakPendingBytes: 0,
+ transactions: 0, committedTransactions: 0, abortedTransactions: 0,
+ committedWrites: 0, skippedWrites: 0,
+ };
+
+ function transact(database, entries) {
+ return new Promise((resolve, reject) => {
+ const transaction = database.transaction('keyvaluepairs', 'readwrite');
+ stats.transactions += 1;
+ const store = transaction.objectStore('keyvaluepairs');
+ let index = 0;
+ let readBytes = 0;
+ let writes = 0;
+ let skipped = 0;
+ let failure;
+ transaction.oncomplete = () => {
+ stats.committedTransactions += 1;
+ stats.committedWrites += writes;
+ stats.skippedWrites += skipped;
+ resolve();
+ };
+ transaction.onabort = () => {
+ stats.abortedTransactions += 1;
+ reject(failure || transaction.error || new DOMException('Chart mirror batch aborted', 'AbortError'));
+ };
+ transaction.onerror = event => { failure = event.target.error; };
+
+ function guard(action) {
+ try {
+ action();
+ } catch (error) {
+ failure = error;
+ try {
+ transaction.abort();
+ } catch (abortError) {
+ // A competing native abort listener may already own completion.
+ if (abortError instanceof DOMException && abortError.name === 'InvalidStateError') return;
+ throw abortError;
+ }
+ }
+ }
+
+ function advance() {
+ if (index === entries.length) return;
+ const [key, value] = entries[index];
+ const read = store.get(key);
+ read.onsuccess = () => guard(() => {
+ const current = read.result === undefined ? null : snapshotJson(read.result);
+ readBytes += byteLength(current);
+ assert(readBytes <= limits.maxReadBytes, 'Mirror stored-value byte limit exceeded');
+ function writeIfChanged(present) {
+ if (present && equalJson(current, value)) {
+ skipped += 1;
+ index += 1;
+ advance();
+ } else {
+ const put = store.put(value === null ? undefined : value, key);
+ writes += 1;
+ put.onsuccess = () => guard(() => { index += 1; advance(); });
+ }
+ }
+ if (read.result === undefined) {
+ const exists = store.getKey(key);
+ exists.onsuccess = () => guard(() => writeIfChanged(exists.result !== undefined));
+ } else {
+ writeIfChanged(true);
+ }
+ });
+ }
+ guard(advance);
+ });
+ }
+
+ async function write(target, entries) {
+ await target.ready();
+ assert(target.driver() === 'asyncStorage', 'Chart mirror batching requires the native IndexedDB driver');
+ const info = target._dbInfo;
+ assert((info.name === 'chart_futures' || info.name === 'chart_delivery') && info.storeName === 'keyvaluepairs',
+ 'Unexpected chart mirror database configuration');
+ await transact(info.db, entries);
+ }
+
+ function release(bytes) {
+ stats.pendingBatches -= 1;
+ stats.pendingBytes -= bytes;
+ if (phase === 'draining' && stats.pendingBatches === 0) resolveDrain();
+ }
+
+ function dispatch(target, entries, nativeThunk) {
+ assert(typeof nativeThunk === 'function', 'Chart mirror dispatch requires its original native expression');
+ if (phase === 'stopped') return nativeThunk();
+ if (phase === 'draining') return [stopping.then(() => Promise.all(nativeThunk()))];
+
+ let copied;
+ let bytes;
+ try {
+ assert(Array.isArray(entries), 'Chart mirror entries must be an array');
+ if (entries.length === 0) return [];
+ assert(entries.length <= limits.maxEntries, 'Chart mirror entry limit exceeded');
+ const keys = new Set();
+ copied = entries.map(entry => {
+ assert(Array.isArray(entry) && entry.length === 2, 'Chart mirror entry must be a key/value pair');
+ const [key, value] = entry;
+ assert(typeof key === 'string' && (key.startsWith('#TV_SYMBOL-') || key.startsWith('myTradingView')),
+ 'Chart mirror received a non-chart key');
+ assert(!keys.has(key), 'Chart mirror keys must be unique');
+ keys.add(key);
+ return [key, snapshotJson(value)];
+ });
+ bytes = byteLength(copied);
+ assert(bytes <= limits.maxBatchBytes, 'Chart mirror batch byte limit exceeded');
+ assert(stats.pendingBatches < limits.maxPendingBatches, 'Chart mirror pending batch limit exceeded');
+ assert(stats.pendingBytes + bytes <= limits.maxPendingBytes, 'Chart mirror pending byte limit exceeded');
+ assert(phase === 'active', 'Chart mirror admission stopped during validation');
+ } catch (error) {
+ stats.rejectedBatches += 1;
+ return [Promise.reject(error)];
+ }
+ stats.acceptedBatches += 1;
+ stats.pendingBatches += 1;
+ stats.pendingBytes += bytes;
+ stats.peakPendingBatches = Math.max(stats.peakPendingBatches, stats.pendingBatches);
+ stats.peakPendingBytes = Math.max(stats.peakPendingBytes, stats.pendingBytes);
+ const completion = write(target, copied).then(
+ () => { release(bytes); },
+ error => { stats.failedBatches += 1; release(bytes); throw error; },
+ );
+ return [completion];
+ }
+
+ return Object.freeze({
+ dispatch,
+ getStats: () => ({ ...stats }),
+ stop() {
+ if (phase === 'active') {
+ phase = 'draining';
+ const drained = stats.pendingBatches === 0
+ ? Promise.resolve()
+ : new Promise(resolve => { resolveDrain = resolve; });
+ stopping = drained.then(() => { phase = 'stopped'; });
+ }
+ return stopping;
+ },
+ });
+}
diff --git a/experiments/binance-chart-storage/native-preflight-core.js b/experiments/binance-chart-storage/native-preflight-core.js
new file mode 100644
index 0000000..ccc12f4
--- /dev/null
+++ b/experiments/binance-chart-storage/native-preflight-core.js
@@ -0,0 +1,124 @@
+import { observeChartStorageBootstrap } from './bootstrap.js';
+import { replaceChartMirrorFactory } from './mirror-module.js';
+
+const INCIDENT_URL = 'https://www.binance.com/zh-CN/futures/USUSDT';
+const OBSERVATION_MS = 30_000;
+
+function isIncidentPage() {
+ return location.href === INCIDENT_URL && self === top;
+}
+
+/**
+ * Observe the pinned mirror boundary without inspecting values or adding storage
+ * work. Native thunk evaluation stays synchronous, including its array and error
+ * identities. The returned dispatcher is for module composition; the userscript
+ * publishes only snapshot and stop.
+ */
+export function startNativeMirrorPreflight() {
+ const state = {
+ active: true, outcome: 'waiting', stopReason: null, failure: null,
+ attempts: 0, matches: 0, executions: 0, completed: false,
+ dispatches: 0, entryCount: 0,
+ };
+ let observer;
+ let timer;
+
+ function snapshot() {
+ return { ...state, failure: state.failure === null ? null : { ...state.failure } };
+ }
+
+ function retire(reason) {
+ if (!state.active) return snapshot();
+ state.active = false;
+ state.stopReason = reason;
+ clearTimeout(timer);
+ self.removeEventListener('pagehide', onPageHide);
+ if (observer) observer.stop();
+ return snapshot();
+ }
+
+ function fail(code) {
+ state.failure = { name: 'PreflightError', code };
+ state.outcome = 'failed';
+ retire('failure');
+ }
+
+ function onPageHide() {
+ retire('pagehide');
+ }
+
+ function dispatch(_target, entries, nativeThunk) {
+ if (state.active) {
+ if (!isIncidentPage()) retire('scope_changed');
+ else {
+ try {
+ const count = entries.length;
+ if (state.active) {
+ state.dispatches += 1;
+ state.entryCount += count;
+ }
+ } catch {
+ // Diagnostic metadata failure retires observation, never the native operation.
+ fail('entry_count_unavailable');
+ }
+ }
+ }
+ return nativeThunk();
+ }
+
+ const session = Object.freeze({ snapshot, stop: () => retire('manual'), dispatch });
+ if (!isIncidentPage()) {
+ fail('unsupported_page');
+ return session;
+ }
+
+ try {
+ observer = observeChartStorageBootstrap({
+ replaceMirrorFactory(original) {
+ if (!isIncidentPage()) {
+ retire('scope_changed');
+ return original;
+ }
+ state.attempts += 1;
+ let factory;
+ try {
+ factory = replaceChartMirrorFactory(original, dispatch);
+ } catch {
+ fail('source_mismatch');
+ return original;
+ }
+ state.matches += 1;
+ return function (...args) {
+ if (state.active) {
+ if (!isIncidentPage()) retire('scope_changed');
+ else state.executions += 1;
+ }
+ try {
+ return Reflect.apply(factory, this, args);
+ } catch (error) {
+ if (state.active) fail('module_execution_failed');
+ throw error;
+ }
+ };
+ },
+ onCapture() {
+ if (!state.active) return;
+ if (!isIncidentPage()) retire('scope_changed');
+ else {
+ state.completed = true;
+ state.outcome = 'captured';
+ }
+ },
+ });
+ } catch {
+ fail('bootstrap_rejected');
+ return session;
+ }
+
+ observer.captured.catch(() => {
+ if (state.active) fail('observation_failed');
+ });
+ self.addEventListener('pagehide', onPageHide, { once: true });
+ timer = setTimeout(() => retire('deadline'), OBSERVATION_MS);
+ return session;
+}
diff --git a/experiments/binance-chart-storage/native-preflight-entry.user.js b/experiments/binance-chart-storage/native-preflight-entry.user.js
new file mode 100644
index 0000000..b2c11a1
--- /dev/null
+++ b/experiments/binance-chart-storage/native-preflight-entry.user.js
@@ -0,0 +1,21 @@
+// ==UserScript==
+// @name Binance Chart Mirror Native Preflight (Experiment)
+// @namespace binance.chart.mirror.native-preflight
+// @version 0.0.1
+// @description Observe pinned mirror startup and native dispatch counts without custom storage writes.
+// @match https://www.binance.com/zh-CN/futures/USUSDT
+// @run-at document-start
+// @sandbox raw
+// @grant none
+// @noframes
+// ==/UserScript==
+
+import { startNativeMirrorPreflight } from './native-preflight-core.js';
+
+(function installNativeMirrorPreflight() {
+ if (Object.hasOwn(self, '__BINANCE_MIRROR_PREFLIGHT__')) return;
+ const { snapshot, stop } = startNativeMirrorPreflight();
+ Object.defineProperty(self, '__BINANCE_MIRROR_PREFLIGHT__', {
+ value: Object.freeze({ snapshot, stop }), configurable: true,
+ });
+})();
diff --git a/experiments/binance-chart-storage/native-preflight.user.js b/experiments/binance-chart-storage/native-preflight.user.js
new file mode 100644
index 0000000..1cd8537
--- /dev/null
+++ b/experiments/binance-chart-storage/native-preflight.user.js
@@ -0,0 +1,1025 @@
+// ==UserScript==
+// @name Binance Chart Mirror Native Preflight (Experiment)
+// @namespace binance.chart.mirror.native-preflight
+// @version 0.0.1
+// @description Observe pinned mirror startup and native dispatch counts without custom storage writes.
+// @match https://www.binance.com/zh-CN/futures/USUSDT
+// @run-at document-start
+// @sandbox raw
+// @grant none
+// @noframes
+// ==/UserScript==
+(() => {
+ // experiments/binance-chart-storage/bootstrap.js
+ var QUEUE_NAME = "webpackChunkfutures_trade_ui";
+ function observeChartStorageBootstrap({ onCapture, replaceMirrorFactory } = {}) {
+ const lab = location.origin === "https://chart-storage.test";
+ const incidentPage = location.origin === "https://www.binance.com" && location.pathname === "/zh-CN/futures/USUSDT" && self === top;
+ if (!lab && !incidentPage) throw new Error("Storage bootstrap requires the lab or the top-level USUSDT incident page");
+ if (onCapture !== void 0 && typeof onCapture !== "function") throw new Error("onCapture must be a synchronous function");
+ if (replaceMirrorFactory !== void 0 && typeof replaceMirrorFactory !== "function") throw new Error("replaceMirrorFactory must be a synchronous function");
+ const moduleId = replaceMirrorFactory === void 0 ? "43917" : "70940";
+ const globalDescriptor = Object.getOwnPropertyDescriptor(self, QUEUE_NAME);
+ if (globalDescriptor && !Object.hasOwn(globalDescriptor, "value")) throw new Error("Storage bootstrap requires a native queue property");
+ const queue = globalDescriptor && globalDescriptor.value !== void 0 ? globalDescriptor.value : [];
+ const nativeAppend = Array.prototype.push;
+ if (!Array.isArray(queue) || queue.length !== 0 || queue.push !== nativeAppend || Object.hasOwn(queue, "push")) {
+ throw new Error("Storage bootstrap must start before runtime on a native empty queue");
+ }
+ if (globalDescriptor && globalDescriptor.value === void 0 && !globalDescriptor.configurable) {
+ throw new Error("Storage bootstrap cannot own the existing global queue property");
+ }
+ const createdQueue = !globalDescriptor || globalDescriptor.value === void 0;
+ if (createdQueue) Object.defineProperty(self, QUEUE_NAME, { value: queue, writable: true, configurable: true, enumerable: true });
+ let runtimePush;
+ let inFlightAppends = 0;
+ let active = true;
+ let originalFactory;
+ let observedFactory;
+ let observedChunk;
+ let runtimeRequire;
+ let resolveCapture;
+ let rejectCapture;
+ const captured = new Promise((resolve, reject) => {
+ resolveCapture = resolve;
+ rejectCapture = reject;
+ });
+ function restore() {
+ if (runtimeRequire && runtimeRequire.m[moduleId] === observedFactory) runtimeRequire.m[moduleId] = originalFactory;
+ if (observedChunk && observedChunk[1][moduleId] === observedFactory) observedChunk[1][moduleId] = originalFactory;
+ const descriptor = Object.getOwnPropertyDescriptor(queue, "push");
+ if (!descriptor || descriptor.get !== getPush || descriptor.set !== setPush || !descriptor.configurable) {
+ throw new Error("Storage bootstrap lost queue accessor ownership");
+ }
+ if (runtimePush) {
+ Object.defineProperty(queue, "push", { value: runtimePush, writable: true, configurable: true, enumerable: true });
+ } else {
+ delete queue.push;
+ if (createdQueue && inFlightAppends === 0 && queue.length === 0 && Object.getOwnPropertyDescriptor(self, QUEUE_NAME)?.value === queue) {
+ if (globalDescriptor) Object.defineProperty(self, QUEUE_NAME, globalDescriptor);
+ else delete self[QUEUE_NAME];
+ }
+ }
+ }
+ function finish(succeeded, result) {
+ if (!active) return;
+ active = false;
+ try {
+ restore();
+ } catch (cleanupError) {
+ if (succeeded) {
+ succeeded = false;
+ result = cleanupError;
+ }
+ }
+ if (succeeded) resolveCapture(result);
+ else rejectCapture(result);
+ }
+ function prepareChunk(chunk) {
+ if (!active) return chunk;
+ try {
+ if (!Array.isArray(chunk) || !Array.isArray(chunk[0]) || !chunk[1] || typeof chunk[1] !== "object") {
+ throw new Error("Storage bootstrap observed an unsupported chunk shape");
+ }
+ if (!Object.hasOwn(chunk[1], moduleId)) return chunk;
+ if (originalFactory) throw new Error("Storage module registered twice before capture");
+ originalFactory = chunk[1][moduleId];
+ if (typeof originalFactory !== "function") throw new Error("Storage module factory must be a function");
+ const hostRuntime = chunk[2];
+ if (hostRuntime !== void 0 && typeof hostRuntime !== "function") throw new Error("Storage chunk runtime callback must be a function");
+ const executionFactory = replaceMirrorFactory === void 0 ? originalFactory : replaceMirrorFactory(originalFactory);
+ if (!active) return chunk;
+ if (typeof executionFactory !== "function") throw new Error("Mirror replacement must return a factory synchronously");
+ observedFactory = function(module, exports, require2) {
+ runtimeRequire = require2;
+ let returned;
+ try {
+ returned = executionFactory.call(this, module, exports, require2);
+ } catch (hostError) {
+ finish(false, hostError);
+ throw hostError;
+ }
+ if (active) {
+ try {
+ const result = onCapture === void 0 ? void 0 : onCapture(module.exports);
+ if (result && typeof result.then === "function") throw new Error("Storage observation callback must complete synchronously");
+ finish(true, module.exports);
+ } catch (observationError) {
+ finish(false, observationError);
+ }
+ }
+ return returned;
+ };
+ observedChunk = [chunk[0], { ...chunk[1], [moduleId]: observedFactory }, (require2) => {
+ runtimeRequire = require2;
+ if (hostRuntime !== void 0) return hostRuntime(require2);
+ }];
+ return observedChunk;
+ } catch (observationError) {
+ finish(false, observationError);
+ return chunk;
+ }
+ }
+ function preRuntimePush(...chunks) {
+ if (!active || runtimePush) return nativeAppend.apply(this, chunks);
+ inFlightAppends += 1;
+ try {
+ return nativeAppend.apply(this, chunks.map(prepareChunk));
+ } finally {
+ inFlightAppends -= 1;
+ }
+ }
+ function runtimeObservingPush(chunk) {
+ return runtimePush.call(this, prepareChunk(chunk));
+ }
+ function getPush() {
+ return runtimePush ? runtimeObservingPush : preRuntimePush;
+ }
+ function setPush(dispatcher) {
+ if (!active) {
+ Object.defineProperty(queue, "push", { value: dispatcher, writable: true, configurable: true, enumerable: true });
+ return;
+ }
+ if (runtimePush || typeof dispatcher !== "function") {
+ finish(false, new Error("Storage bootstrap observed an unexpected push replacement"));
+ Object.defineProperty(queue, "push", { value: dispatcher, writable: true, configurable: true, enumerable: true });
+ return;
+ }
+ runtimePush = dispatcher;
+ }
+ Object.defineProperty(queue, "push", { get: getPush, set: setPush, enumerable: true, configurable: true });
+ return Object.freeze({
+ captured,
+ stop() {
+ finish(false, new Error("Storage bootstrap stopped before module execution"));
+ }
+ });
+ }
+
+ // experiments/binance-chart-storage/mirror-module.js
+ var originalFactorySource = '70940(Z,x,e){"use strict";e.r(x),e.d(x,{TradingView:()=>Dt,default:()=>Cn});var o=e(49253),v=e(77135),d=e(75510),i=e(42288),p=e(31085),r=e(41594),M=e(43917),h=e.n(M),P=e(81936),b=e.n(P),V=e(7980),U=e.n(V),D=e(76469),c=e(4260),k=e(53837),B=e(27571),C=e(45250),y=e(87646),A=e.n(y),q=e(80817);const E=(0,C.range)(1,1380),m=(0,C.range)(1,365).map(t=>`${t}D`),R=(0,C.range)(1,52).map(t=>`${t}W`),I=(0,C.range)(1,12).map(t=>`${t}M`),F=["1S",...E,...m,...R,...I],L=({symbol:t,params:n})=>{const a=new URLSearchParams(n);return`${t}@${a.toString()}`},X=t=>{const[n,a]=t.split("@"),s=new URLSearchParams(a).entries(),g=A()(Array.from(s));return{symbol:n,params:g}},ue=()=>{const t=new Map;let n={},a=[],u={onReady:()=>null,resolveSymbol:()=>null,searchSymbols:()=>null,getBars:()=>null,subscribeBars:()=>null,unsubscribeBars:()=>null};return({config:s,onSymbolResolving:g,onSymbolResolved:S,onFetchSymbolsInfo:K,onFetchBars:Y,onSubscribeBars:H,onUnsubscribeBars:j,onFetchServerTimeApi:$,onTransformSymbolInfo:f=({template:T})=>T,onSearchSymbols:l=()=>!0})=>(u={onReady:T=>{setTimeout(async()=>{const _=(await K()).map(O=>({...O,ticker:L({symbol:O.symbol,params:O.params||{}})}));a=_.map(({ticker:O,symbol:G,description:te="",exchange:J="",type:oe=""})=>({symbol:G,ticker:O,description:te,exchange:J,type:oe})),n=(0,C.keyBy)(_,"ticker"),T({exchanges:[],symbols_types:[],supported_resolutions:F,supports_marks:!1,supports_timescale_marks:!1,supports_time:!0,...s})})},resolveSymbol:async(T,W,_)=>{const O=n[T];if(!O){_(`cannot resolve symbol - ${T}`);return}const G=f({info:O,template:{description:O.description||"",fractional:!1,has_seconds:!0,seconds_multipliers:["1"],has_intraday:!0,intraday_multipliers:["1","3","5","15","30","60","120","240","360","480","720"],has_daily:!0,daily_multipliers:["1"],has_weekly_and_monthly:!0,weekly_multipliers:["1"],monthly_multipliers:["1"],minmov:1,minmove2:0,name:O.symbol,ticker:O.ticker,timezone:"Etc/UTC",pricescale:O.tickSize,session:"24x7",type:O.type||"",exchange:O.exchange||"",listed_exchange:O.exchange||"",format:"price",supported_resolutions:F,visible_plots_set:"ohlcv",volume_precision:3}});if(g){const te=X(G.ticker||"");if(!await g({symbol:T,info:G,tickerInfo:te})){_(`symbol resolving error - ${T}`);return}}setTimeout(()=>{W(G),S?.(G)})},searchSymbols:(T,W,_,O)=>{const G=new RegExp(T,"i"),te=new RegExp(W,"i"),J=new RegExp(_,"i"),oe=a.filter(Te=>{const{symbol:N,ticker:w,description:re="",exchange:ie="",type:le=""}=Te;return[N,w,re].some(de=>G.test(de||""))&&te.test(ie)&&J.test(le)&&l?.({item:Te})});O(oe)},getBars:async(T,W,_,O,G)=>{const{from:te,to:J,firstDataRequest:oe}=_,Te=(0,q.Cz)(W);try{if(J>=0){const N=X(T.ticker||""),w=await Y({symbolInfo:T,resolution:W,rangeStartDate:te,rangeEndDate:J,tickerInfo:N,binanceInterval:Te,firstDataRequest:oe});O(w,{noData:w.length===0})}else O([],{noData:!0})}catch(N){G(N.toString())}},subscribeBars:(T,W,_,O,G)=>{const te=X(T.ticker||""),J=(0,q.Cz)(W),oe=H({symbolInfo:T,resolution:W,callback:_,onResetCacheNeededCallback:G,tickerInfo:te,binanceInterval:J});t.set(O,oe)},unsubscribeBars:T=>{t?.get(T)?.(),t?.delete(T),j?.(T)},getServerTime:async T=>{try{const W=await $();T(W)}catch(W){console.warn(W),T(Date.now()/1e3)}}},{datafeed:u,observers:t,symbolsInfo:n})},Le=({namespace:t,type:n})=>{const a=(0,r.useMemo)(()=>h().createInstance({name:t}),[t]);return(0,r.useCallback)(()=>{n==="tv"&&a.removeItem("myTradingView")},[a,n])};var Ue=e(76535),pr=e(26531),Ye=e(17409),Qe=e(94652),Ae=e(76830),he=e(48651),Fe=e(79344),se=e(51029),Je=e(36307),or=e(25379),yr=e(44745),Ne=e(15727),rr=e(45358),sr=e(82071),Ve=e(85411),ge=e(94889),hr=e(94547),tr=e(80686),Ar=e(61875);function Fr(t){return(0,ge._)(t)||(0,hr._)(t)||(0,Ar._)(t)||(0,tr._)()}var Nr=e(92873),zr=e(35630),Vr=e(12055),Qr=e(79515),Br=e(80737),Jr=e(40545),Zr=e(98107),Kr=e(51846),Xr="Chart.Crosshair.PlusButton.DrawHorizontalLine",wr=function(t){var n=t.namespace,a=t.symbol,u=t.priceType,s=t.crosshairPrice,g=(0,Nr.o)("","kline-ui").getI18n,S=(0,Vr.r)().format,K=k.y$(n),Y=(0,Br.S)({namespace:n}),H=(0,Jr.hw)(),j=(0,Zr.c)(),$=(0,Qr.nH)(),f=(0,se.Ye)(),l=(0,Kr.E)({symbol:a}),T=(0,Fe.f0)(a),W=T.isCM,_=(0,r.useRef)(s);(0,r.useEffect)(function(){_.current=s},[s]);var O=(0,r.useRef)(H);(0,r.useEffect)(function(){O.current=H},[H]);var G=(0,r.useRef)(j);(0,r.useEffect)(function(){G.current=j},[j]);var te=(0,r.useRef)(u);(0,r.useEffect)(function(){te.current=u},[u]);var J=(0,r.useRef)($);(0,r.useEffect)(function(){J.current=$},[$]);var oe=(0,r.useRef)(f);(0,r.useEffect)(function(){oe.current=f},[f]);var Te=(0,r.useRef)(l);(0,r.useEffect)(function(){Te.current=l},[l]);var N=(0,r.useRef)(W);return(0,r.useEffect)(function(){N.current=W},[W]),{items_processor:(function(){var w=(0,o._)(function(re,ie,le){var de,Be,ve,Ce,ce,Ke,Ze,Ge,Xe,Ie,He,ur,Tr,_r,Cr;return(0,i._)(this,function(Er){if(Be=K.getState().tradingViewReference,!Be)return console.warn("[context-menu] trading view reference is not found"),[2,re];if(Be.chartsCount()>1)return[2,re];if(ve=Be.activeChart().symbolExt(),!ve)return[2,re];switch(Ce=ve.name,ce=_.current,Ke=le.menuName==="CrosshairMenuView"?"chart-trading":"context-menu",Ze={price:"".concat(ce.value),formattedPrice:ce.formatted,source:Ke,chartType:"tradingview"},Ge=(de=oe.current[Ce])===null||de===void 0?void 0:de.baseAsset,Xe=g("trade",{defaultValue:"Trade"}),Ie=S(ce.formatted),He=G.current&&te.current===c.SJ.Last&&J.current&&Ge?[ie.createAction({actionId:"Chart.PlaceOrder.BuyLimit",label:"".concat(Xe," ").concat(Ge," @ ").concat(Ie," ").concat(g("limit",{defaultValue:"Limit"})),onExecute:function(){return Te.current.onClickBuyLimit(Ze)}}),ie.createAction({actionId:"Chart.PlaceOrder.BuyStop",label:"".concat(Xe," ").concat(Ge," @ ").concat(Ie," ").concat(g("stop",{defaultValue:"Stop"})),onExecute:function(){return Te.current.onClickBuyStop(Ze)}})]:[],ur=O.current?ie.createAction({actionId:"Chart.PriceAlert.CreateAtCrosshair",label:g("create-alert-at",{defaultValue:"Create Alert at {{price}}",price:ce.formatted}),onExecute:function(){Y({symbol:Ce,price:ce.formatted,mode:zr.n.CREATE})}}):null,re.forEach(function(we){if(!(!("execute"in we)||!("getState"in we))){var Dr=we.getState();if(Dr.actionId===Xr){var z=we.execute.bind(we);we.execute=function(){(0,Kr.A)({elementId:"draw_horizontal_line",isDelivery:N.current,chartType:"tradingview"}),z()}}}}),le.menuName){case"CrosshairMenuView":return ur?[2,[ur].concat((0,d._)(He),(0,d._)(re.slice()))]:[2,(0,d._)(He).concat((0,d._)(re.slice()))];case"ChartContextMenu":return ur?(Tr=Fr(re),_r=Tr[0],Cr=Tr.slice(1),[2,[_r,ur].concat((0,d._)(He),(0,d._)(Cr))]):[2,(0,d._)(He).concat((0,d._)(re.slice()))];default:return[2,re]}return[2]})});return function(re,ie,le){return w.apply(this,arguments)}})()}},qr=function(){var t=(0,r.useState)({value:NaN,formatted:""}),n=t[0],a=t[1],u=(0,r.useCallback)(function(s){var g=s.crosshairData,S=s.activeChart;S&&a({value:g.price,formatted:S.priceFormatter().format(g.price)})},[]);return{onCrosshairMoved:u,crosshairPrice:n}};const Hr=t=>Math.floor(Math.log10(t)),et=t=>{const[n="1"]=t.split(",");return Hr(+n)},pe=({symbolInfo:t,minTick:n})=>{const{pricescale:a=2}=t||{};return n==="default"||n===void 0?Hr(a):et(n)},br=t=>(n,a)=>{try{return n?{format:u=>{const s=pe({symbolInfo:n,minTick:a});return t.format({symbolInfo:n,minTick:a,value:u,precision:s})}}:null}catch{return null}},Mr=10**12,rt=10**9,tt=10**6,jr=10**3,gr=t=>{const n=t.toString(),[,a=""]=n.split(".");return Math.min(a.length,3)},nt=({value:t})=>{const n=Math.abs(t);if(n>=Mr){const a=t/Mr;return{value:a,unit:"T",precision:gr(a)}}if(n>=rt){const a=t/rt;return{value:a,unit:"B",precision:gr(a)}}if(n>=tt){const a=t/tt;return{value:a,unit:"M",precision:gr(a)}}if(n>=jr){const a=t/jr;return{value:a,unit:"K",precision:gr(a)}}return{value:t,unit:"",precision:gr(t)}},ut=t=>(n,a,u)=>{try{return a&&n.type==="volume"?{format:s=>s===void 0?"":t.formatVolume({format:n,symbolInfo:a,precision:u,value:s,volumeDetail:nt({value:s})})}:null}catch{return null}};var lt=function(){var t=(0,Vr.r)(),n=t.format,a=(0,r.useMemo)(function(){return br({format:function(s){var g=s.value,S=s.precision;return n(g,{precision:S})}})},[n]),u=(0,r.useMemo)(function(){return ut({formatVolume:function(s){var g=s.value,S=s.volumeDetail;if(g===void 0||!S)return"";var K=n(S.value,{precision:S.precision});return"".concat(K).concat(S.unit)}})},[n]);return{priceFormatterFactory:a,studyFormatterFactory:u}},dt=e(74069),ir=function(t){var n=(0,dt.K)(t),a=(0,r.useMemo)(function(){return n.map(function(u){var s=u.id,g=u.time,S=u.isBuy,K=u.price,Y=u.tooltip;return{id:s,text:"",price:K,quantity:"",time:Math.round(g/1e3),isBuy:S,tooltip:Y}})},[n]);return{executionOrders:a}},at=function(t){var n=t.namespace,a=t.symbol,u=(0,Br.j)({namespace:n,symbol:a});return(0,r.useMemo)(function(){return u.map(function(s){var g=s.id,S=s.price,K=s.formattedPrice,Y=s.sideText,H=s.showCloseButton,j=s.isPriceChangeable,$=s.cancelTooltipText,f=s.onClick,l=s.onCancel,T=s.onPriceChanged;return{id:g,price:+S,text:"".concat(Y," ").concat(K),editable:j,cancellable:H,cancelTooltip:$,type:c.ND.PriceAlert,onCancel:H?l:void 0,onMove:j?function(W){var _=W.price;T({currentPrice:_})}:void 0,onClick:f}})},[u])},vt=e(90664),ot=function(t){var n=t.symbol,a=(0,vt.w)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){var s=u.id,g=u.text,S=u.price;return{id:s,text:g,price:+S,type:c.ND.BreakEvenPrice}})},[a])},cr=e(82508),ft=e(91025),mt=e(36077),Rr=function(t){var n=t.symbol,a=(0,mt.c)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){return(0,ft._)((0,cr._)({},u),{type:c.ND.LiquidationPrice})})},[a])},pt=function(t){var n=t.namespace,a=t.symbol,u=at({namespace:n,symbol:a}),s=ot({symbol:a}),g=Rr({symbol:a});return(0,r.useMemo)(function(){return(0,d._)(u).concat((0,d._)(s),(0,d._)(g))},[u,s,g])},st=e(42902),Ir=e(90170),yt=e(63345),ht=e(48210),Pr=e(51289),xr=e(24326),$r=e(90922),kr=e(34235),it=e(63762),Ft=function(t){var n=(0,Fe.f0)(t),a=n.pricePrecision,u=n.isCM,s=(0,st.yD)(t)||{},g=s.lastPrice,S=g===void 0?"0":g,K=(0,kr.Bp)(u),Y=(0,it.HY)(t),H=(0,kr.gv)(u),j=(0,kr.WF)(u);return(0,r.useMemo)(function(){return(0,$r.qF)({orders:(0,d._)(K).concat((0,d._)(H),(0,d._)(j)),symbol:t}).map(function($){return Y({order:$,lastPrice:S,pricePrecision:a})})},[S,K,H,a,t,Y])},gt=e(31542),Tt=function(t){var n=t.type;(0,gt.u4)("webClick",{module:"chart",elementId:n})},Nt=function(t){var n=t.symbol,a=t.namespace,u=(0,r.useState)(0),s=u[0],g=u[1],S=(0,r.useCallback)(function(){return g(function(N){return N+1})},[]),K=Ft(n),Y=(0,xr.IT)({namespace:a}),H=(0,Fe.f0)(n),j=H.isDelivery,$=H.contractType,f=(0,st.yD)(n)||{},l=f.lastPrice,T=l===void 0?"0":l,W=(0,yt.s)(n),_=(0,it.YZ)({symbol:n,afterConfirmationModalIsShown:S}),O=(0,it.u8)(n),G=(0,it.jn)(n),te=(0,r.useCallback)(function(N){return(0,ht.D)({order:N,isDelivery:j,symbol:j?N.symbol:"".concat(N.symbol,"_").concat($),module:"tradingview_kline"})},[$,j]),J=(0,r.useMemo)(function(){return K.map(function(N){var w=N.order,re=N.price,ie=N.quantity,le=N.quantityText,de=N.isBuy,Be=N.text,ve=N.priceTooltipText,Ce=N.pnlText,ce=(0,Pr.$L)(w),Ke=(0,Pr.OX)(w),Ze=(0,Pr.xL)(w),Ge=ce==="LIMIT",Xe=(0,Ir.Y)(w);return{id:String(Ke),text:Be,price:+re,quantity:le,isBuy:de,editable:Xe&&G,tooltip:ve,modifyTooltip:w.closePosition||!Ge?"":void 0,onMove:G?function(Ie){var He=Ie.price;te(w),Tt({type:"TV_vertical"}),_({order:w,price:+He,quantity:+Ze,lastPrice:T})}:void 0,onModify:w.closePosition||!Ge||!Xe?void 0:function(){Tt({type:"TV_amountclick"}),O({order:w,price:re.toString(),quantity:ie,lastPrice:T})},onCancel:function(){Tt({type:"TV_cancel"}),W(w)}}})},[G,K,T,te,_,O,W,s]),oe=(0,r.useMemo)(function(){return Y.map(function(N){var w=N.id,re=N.value,ie=N.text,le=N.onChange,de=N.isBuy,Be=N.type;return{id:w,price:+re,quantity:"",text:ie,isBuy:de,editable:!0,limitOrderType:c.aA.OrderPreview,onMove:function(ve){var Ce=ve.price;(0,xr.eP)({namespace:a,orderType:Be}),le({id:w,newPrice:Ce})},onMoving:function(ve){var Ce=ve.price;return le({id:w,newPrice:Ce})}}})},[Y,a]),Te=(0,r.useMemo)(function(){return(0,d._)(J).concat((0,d._)(oe))},[J,oe]);return{orderLines:Te}},Vt=e(17855),Bt=function(t,n){var a=(0,r.useState)(t()),u=a[0],s=a[1];return(0,Vt.A)(function(){return s(t())},n),u},Kt=e(58656),Ht=function(t){var n=t.symbol,a=(0,Kt.Q)({symbol:n})||[],u=Bt(function(){return a.map(function(s){var g=s.position,S=s.pnl,K=s.pnlInNumber,Y=s.pnlText,H=s.price,j=s.quantity,$=s.reverseText,f=s.isBuy,l=s.displayReverseOrderModal,T=s.displayClosePositionModal;return{id:g.id||"".concat(g.symbol,"-").concat(g.positionSide),PNL:K,text:"".concat(Y," ").concat(S),price:H,quantity:j,tooltip:"",reverseTooltip:$,isBuy:f,onReverse:l,onClose:function(){T(),(0,gt.u4)("$WebClick",{item:1,module:"xxx",$element_id:"kline_close_position"})}}})},[a]);return{positionLines:u}},jt=e(47354),bt=e.n(jt),$t=e(87017),Ut=e(99168),Yt=e(3742),Wt=e(43335),Gt=e(15540),zt=e(20163),Qt=e(47255);const Jt=t=>({endTime:n})=>(n??0)===t;var Zt=e(36488),Ct=e(48018),Xt=e(82911),We=e(69578),wt=e(53208),Rt=e(19862),qt=e(8874),en=e(26679),rn=e(66547),It=(function(){var t=(0,o._)(function(n){var a,u,s,g,S,K,Y,H,j;return(0,i._)(this,function($){switch($.label){case 0:a=n.onFetchKlineData,u=n.startTime,s=n.endTime,g=n.interval,S=n.limit,K=S===void 0?1e3:S,Y=[],H=[],j=s,$.label=1;case 1:return[4,a((0,cr._)({interval:g,limit:K},j===0?{}:{endTime:j}))];case 2:if(H=$.sent(),H.length===0)return[3,4];H.forEach(function(f){var l=(0,v._)(f,8),T=l[0],W=l[1],_=l[2],O=l[3],G=l[4],te=l[5],J=l[7];Y.push({time:+T,open:+W,high:+_,low:+O,close:+G,volume:+te,quantity:+J})}),j=H[0][0]-1,$.label=3;case 3:if(j>u)return[3,1];$.label=4;case 4:return Y.sort(function(f,l){return f.time-l.time}),[2,Y]}})});return function(a){return t.apply(this,arguments)}})(),tn=function(t){var n=t.firstDataRequest,a=t.hasRequested,u=t.rangeEndDate;return n&&!a?0:u*1e3},nn=e(78142),an=e(16238),St=e.n(an),on=e(98087),xt=e.n(on),sn=e(55009),cn=e.n(sn),kt=e(56477);const Et=t=>cn()(t.chartsCount(),n=>t.chart(n)),un=async t=>{try{const{schema:n,data:a}=await t.exportData({includedStudies:[]}),u=xt()(a)?.[n.findIndex(s=>s.type==="time")];return u!==void 0&&Number.isFinite(u)?u*1e3:void 0}catch{return}},ln=({getWidget:t,...n})=>{const a=new Map,u=new Map,s=()=>{try{const f=t();return f?St()(Et(f).map(l=>l.symbolExt()?.ticker)):[]}catch{return[]}},g=f=>{const l=s();return!l.length||l.includes(f)},S=f=>{const l=u.get(f);if(l)return l;const T={controller:new AbortController,claimedAsMain:!1};return u.set(f,T),T},K=f=>{const l=u.get(f);l&&(l.controller.abort(),u.delete(f))},Y=()=>{const f=s();f.length&&Array.from(u.keys()).forEach(l=>{const T=u.get(l);if(T){if(f.includes(l)){T.claimedAsMain=!0;return}a.has(l)||T.claimedAsMain&&K(l)}})},H=(f,l)=>{f.closed||l.time{f.held=[];let T=l;try{const _=(await f.options.fetchSince(l)).filter(O=>O.time>=l&&!f.closed);_.forEach(O=>{f.highWater=Math.max(f.highWater,O.time),f.options.onTick(O)}),T=xt()(_)?.time??l}finally{const W=f.held??[];f.held=void 0,W.filter(_=>_.time>=T).forEach(_=>{H(f,_)})}},$=(0,kt.k)({...n,onCheck:Y,repair:async()=>{const f=t();if(f)try{const l=Et(f),T=St()(l.map(_=>{const O=a.get(_.symbolExt()?.ticker??"");return O&&{chart:_,feed:O}}));if(!T.length){f.resetCache(),l.forEach(_=>{_.resetData()});return}const W=St()(await Promise.all(T.map(async({chart:_,feed:O})=>{const G=await un(_);return G===void 0?void 0:{feed:O,fromMs:G}})));await Promise.allSettled(W.map(({feed:_,fromMs:O})=>j(_,O)))}catch{}}});return{openFeed:f=>{const l={options:f,held:void 0,highWater:0,closed:!1,detector:void 0};return l.detector=(0,kt.k)({...n,subscribeSignals:f.subscribeSignals,repair:async()=>{l.closed||!l.highWater||g(f.ticker)||await j(l,l.highWater)}}),a.set(f.ticker,l),g(f.ticker)&&$.resubscribeSignals(f.subscribeSignals),{push:T=>{l.held?l.held.push(T):H(l,T)},close:()=>{l.closed=!0,l.detector?.dispose(),a.get(f.ticker)===l&&a.delete(f.ticker)}}},subscribeWake:$.subscribeWake,getSignal:f=>{const l=S(f);return s().includes(f)&&(l.claimedAsMain=!0),l.controller.signal},dispose:()=>{u.forEach(({controller:f})=>{f.abort()}),u.clear(),a.forEach(f=>{f.detector?.dispose()}),a.clear(),$.dispose()}}};var dn=e(71730),vn=e(36620),fn=function(t){var n=t.namespace,a=k.y$(n),u=(0,vn.l)().getSocketSignals,s=(0,r.useRef)();(0,r.useEffect)(function(){return function(){var S;(S=s.current)===null||S===void 0||S.instance.dispose(),s.current=void 0}},[]);var g=(0,r.useCallback)(function(S){var K,Y,H=S;if(((K=s.current)===null||K===void 0?void 0:K.key)===H)return s.current.instance;(Y=s.current)===null||Y===void 0||Y.instance.dispose();var j=nn.wr(S),$=ln({getWidget:function(){return a.getState().tradingViewReference},checkPeriodMs:dn.D(j)});return s.current={key:H,instance:$},$},[a]);return(0,r.useMemo)(function(){return{ensureRecovery:g,getSocketSignals:u}},[g,u])},mn="tv_web_error",pn=[Ye.bt.TRADING,Ye.bt.DELIVERING],Re;(function(t){t.Futures="futures",t.FuturesMark="futures-mark",t.FuturesIndex="futures-index",t.Delivery="delivery",t.DeliveryMark="delivery-mark",t.DeliveryIndex="delivery-index"})(Re||(Re={}));var Mt=function(t){var n=t.symbol,a=t.description,u=t.productType,s=t.pricePrecision,g=t.params,S=t.supportedResolutions,K=S===void 0?F:S;return{symbol:n,description:a,tickSize:Math.pow(10,s),exchange:"Binance",supportedResolutions:K,type:u,params:g}},yn=function(t){var n=t.namespace,a=t.useMiniTicker,u=a===void 0?!1:a,s=t.isCM,g=c.y$(n),S=k.y$(n),K=(0,$t.useQueryClient)(),Y=fn({namespace:n}),H=Y.ensureRecovery,j=Y.getSocketSignals,$=(0,r.useRef)(0),f=(0,r.useRef)(!1),l=(0,r.useMemo)(function(){return bt()(function(){return(0,We.HP)({eventId:"trd_chart_tv_load_timeout",stepId:"getBarsStart",catch:function(Q){var ee=Q.error,ae=Q.track;ae({GET_BAR_START_TYPE:ee})},log:function(Q){var ee=Q.track;ee({GET_BAR_START_TYPE:"SUCCESS"})}})})},[]),T=(0,r.useMemo)(function(){return bt()(function(z){return(0,We.HP)({eventId:"trd_chart_tv_load_timeout",stepId:"getBarsEnd",catch:function(ee){var ae=ee.error,Se=ee.track;Se({GET_BAR_END_TYPE:ae,duration_:(0,We.Pu)(z-$.current)})},log:function(ee){var ae=ee.track;ae({GET_BAR_END_TYPE:"SUCCESS",duration_:(0,We.Pu)(z-$.current)})}})})},[]),W=(0,Nr.o)("trd-chart","trade-ui"),_=W.getI18n,O=(0,he.ud)(),G=O.getI18n,te=(0,v._)(g(c.Ir),1),J=te[0],oe=(0,v._)(S(k.A7),2),Te=oe[1],N=(0,se.Ye)(),w=(0,Zt.JD)(s).isContUnit,re=(0,se.y_)(),ie=(0,r.useRef)(N),le=(0,r.useRef)(J),de=(0,r.useRef)(re);(0,r.useEffect)(function(){ie.current=N},[N]),(0,r.useEffect)(function(){le.current=J},[J]),(0,r.useEffect)(function(){de.current=re},[re]);var Be=(0,r.useMemo)(function(){return w?Rt.b:Rt.S},[w]),ve=(0,r.useMemo)(function(){return G("usdtFutures-13",{defaultValue:"Futures"})||""},[G]),Ce=(0,r.useMemo)(function(){return G("inverseFutures-13",{defaultValue:"Delivery"})||""},[G]),ce=(0,r.useMemo)(function(){return _("indexPrice",{defaultValue:"Index Price"})||""},[_]),Ke=(0,r.useMemo)(function(){return _("markPrice",{defaultValue:"Mark Price"})||""},[_]),Ze=(0,r.useMemo)(function(){return _("lastPrice",{defaultValue:"Last Price"})||""},[_]),Ge=(0,r.useMemo)(function(){return{exchanges:[{name:"Binance",value:"Binance",desc:""}],symbols_types:[{name:ve,value:Re.Futures},{name:"".concat(ve," ").concat(Ke),value:Re.FuturesMark},{name:"".concat(ve," ").concat(ce),value:Re.FuturesIndex},{name:Ce,value:Re.Delivery},{name:"".concat(Ce," ").concat(Ke),value:Re.DeliveryMark},{name:"".concat(Ce," ").concat(ce),value:Re.DeliveryIndex}]}},[Ce,ve,ce,Ke]),Xe=(0,r.useCallback)((0,o._)(function(){var z,Q,ee;return(0,i._)(this,function(ae){switch(ae.label){case 0:return ae.trys.push([0,2,,3]),[4,(0,en.c)(K)];case 1:return ae.sent(),[3,3];case 2:return z=ae.sent(),(0,gt.u4)(mn,{type:"onFetchSymbolsInfo",message:z.message,business:"futures"}),[3,3];case 3:return Q=ie.current,ee=Object.keys(Q).reduce(function(Se,xe){var ne=Q[xe]||Ct.o,ke=ne.symbol,Me=ne.pricePrecision,fe=ne.isDelivery,Pe=ne.contractStatus;if(!pn.includes(Pe))return Se;var _e=(0,Yt.S)(ne,G),je=_e.symbol,Ee=_e.contractTypeI18n,me=_e.deliveryDateFormat,ye="".concat(je," ").concat(Ee).concat(me?" ".concat(me):""),$e="".concat(ye," ").concat(Ze),Oe="".concat(ye," ").concat(ce),lr="".concat(ye," ").concat(Ke);return Se.concat([Mt({symbol:ke,description:$e,productType:fe?Re.Delivery:Re.Futures,pricePrecision:Me,params:{priceType:c.SJ.Last}}),Mt({symbol:ke,description:Oe,productType:fe?Re.DeliveryIndex:Re.FuturesIndex,pricePrecision:Me,params:{priceType:c.SJ.Index}}),Mt({symbol:ke,description:lr,productType:fe?Re.DeliveryMark:Re.FuturesMark,pricePrecision:(0,Xt.X)(Me),params:{priceType:c.SJ.Mark}})])},[]),[2,ee]}})}),[G,ce,Ke,Ze,K]),Ie=(0,r.useCallback)(function(z){var Q=z.ticker,ee=z.symbol,ae=z.interval,Se=z.priceType,xe=z.retryUntil,ne=ie.current[ee]||Ct.o,ke=ne.pair,Me=ne.contractType,fe=ne.contractVal,Pe=ne.isDelivery,_e=Pe?Be(fe):function(Oe){return Oe},je=(0,wt.h)({symbol:ee,pair:ke,contractType:Me,isDelivery:Pe,priceType:Se}),Ee,me,ye=function(Oe){return zt.y("tv","".concat(Pe?"cm":"um","|").concat(ee,"|").concat(Se,"|").concat(Oe.interval,"|").concat((Ee=Oe.limit)!==null&&Ee!==void 0?Ee:"","|").concat((me=Oe.endTime)!==null&&me!==void 0?me:""))};if(xe===void 0)return{transform:_e,onFetchKlineData:je};var $e=H(ae);return{transform:_e,onFetchKlineData:Qt.b(je,{enabled:Jt(xe),key:ye,signal:$e.getSignal(Q),subscribeWake:$e.subscribeWake})}},[H,Be]),He=(0,r.useCallback)((function(){var z=(0,o._)(function(Q){var ee,ae,Se,xe,ne,ke,Me,fe,Pe,_e,je,Ee,me,ye,$e,Oe,lr,Or;return(0,i._)(this,function(dr){switch(dr.label){case 0:return ee=Q.binanceInterval,ae=Q.rangeStartDate,Se=Q.rangeEndDate,xe=Q.tickerInfo,ne=Q.symbolInfo,ke=Q.firstDataRequest,$.current=Date.now(),l(),Me=xe.symbol,fe=Me===void 0?"":Me,Pe=xe.params,_e=Pe.priceType,je=_e===void 0?le.current:_e,de.current({symbol:fe,priceType:je})?(T(Date.now()),[2,[]]):(Ee=Math.max(0,ae*1e3),me=tn({firstDataRequest:ke,hasRequested:f.current,rangeEndDate:Se}),f.current=!0,$e=Ie({ticker:(ye=ne.ticker)!==null&&ye!==void 0?ye:"",symbol:fe,interval:ee,priceType:je,retryUntil:ke?me:void 0}),Oe=$e.transform,lr=$e.onFetchKlineData,[4,It({onFetchKlineData:lr,startTime:Ee,endTime:me,interval:ee})]);case 1:return Or=dr.sent(),T(Date.now()),[2,Or.map(Oe)]}})});return function(Q){return z.apply(this,arguments)}})(),[Ie,T,l]),ur=(0,r.useCallback)(function(z){var Q=z.binanceInterval,ee=z.callback,ae=z.tickerInfo,Se=z.symbolInfo,xe=ae.symbol,ne=xe===void 0?"":xe,ke=ae.params,Me=ke.priceType,fe=Me===void 0?le.current:Me;if(de.current({symbol:ne,priceType:fe}))return function(){return null};var Pe=ie.current[ne]||Ct.o,_e=Pe.pair,je=Pe.contractType,Ee=Pe.isDelivery,me,ye=(me=Se.ticker)!==null&&me!==void 0?me:"",$e=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe}).transform,Oe=H(Q),lr=Oe.openFeed({ticker:ye,onTick:ee,subscribeSignals:j(Ee),fetchSince:(function(){var dr=(0,o._)(function(Lr){var Ur,Yr,ct,De;return(0,i._)(this,function(ze){switch(ze.label){case 0:return Ur=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe,retryUntil:0}),Yr=Ur.transform,ct=Ur.onFetchKlineData,[4,It({onFetchKlineData:ct,startTime:Lr,endTime:0,interval:Q})];case 1:return De=ze.sent(),[2,De.map(Yr)]}})});return function(Lr){return dr.apply(this,arguments)}})()}),Or=(0,rn.B)({symbol:ne,pair:_e,contractType:je,interval:Q,priceType:fe,isDelivery:Ee,useMiniTicker:u,callback:function(dr){lr.push($e(dr))}});return function(){lr.close(),Or()}},[Ie,H,j,u]),Tr=(0,r.useCallback)((0,o._)(function(){var z;return(0,i._)(this,function(Q){switch(Q.label){case 0:return[4,K.fetchQuery(Wt.Bz.SERVER_TIME(s),function(){return(0,Gt.l)(s)},{})];case 1:return z=Q.sent(),[2,Math.floor(z/1e3)]}})}),[K,s]),_r=(0,r.useCallback)(function(z){return(0,cr._)({},z.template,(0,qt.sp)(s))},[s]),Cr=(0,r.useMemo)(function(){return ue()},[]),Er=Cr({config:Ge,onFetchSymbolsInfo:Xe,onFetchBars:He,onSubscribeBars:ur,onFetchServerTimeApi:Tr,onTransformSymbolInfo:_r}),we=Er.datafeed,Dr=Er.symbolsInfo;return(0,Ut.A)(function(){return Te()},[w]),{datafeed:we,symbolsInfo:Dr}},Pt="trd_chart_tv_load_timeout",hn=function(){var t=(0,We.PM)({id:"chartReady"}),n=(0,We.PM)({id:"getBarsStart",timeout:3}),a=(0,We.PM)({id:"getBarsEnd",timeout:6}),u=(0,We.PM)({id:"chartResponsive",timeout:3});We.HP.init({step:t._(n)._(a)._(u),eventId:Pt}),(0,We.HP)({eventId:Pt,stepId:"chartReady",catch:function(g){var S=g.error,K=g.track;K({CHART_READY_TYPE:S,connection:JSON.stringify(navigator.connection)})},log:function(g){var S=g.track;S({CHART_READY_TYPE:"SUCCESS",connection:JSON.stringify(navigator.connection)})}})},gn=function(t){(0,We.HP)({eventId:Pt,stepId:"chartResponsive",catch:function(a){var u=a.error,s=a.track;s({CHART_RESPONSIVE_TYPE:u,connection:JSON.stringify(navigator.connection)})},log:function(a){var u=a.track;u({CHART_RESPONSIVE_TYPE:"SUCCESS",duration_:(0,We.Pu)(Date.now()-t)})}})},Tn=[Qe.ZE,Qe.F3],Dt=function(t){var n=t.symbol,a=t.namespace,u=t.useMiniTicker,s=u===void 0?!1:u,g=c.y$(a),S=k.y$(a),K=(0,v._)(g(c.A8),1),Y=K[0],H=(0,v._)(g(c.Ir),2),j=H[0],$=H[1],f=(0,v._)(S(k.yj),1),l=f[0],T=(0,v._)(g(c.e6),1),W=T[0],_=(0,v._)(g(c._b),1),O=_[0],G=(0,he.ot)(),te=(0,pr.K7)(),J=te.pathname.includes(Ae.Q2),oe=te.pathname.includes(Ae.eq),Te=(0,r.useMemo)(function(){return J?(0,d._)(B.oT).concat(["hide_left_toolbar_by_default"]):B.oT},[J]),N=(0,se.Ye)(),w=(0,se.v3)({symbol:n}),re=(0,Fe.es)(n),ie=yn({namespace:a,useMiniTicker:s,isCM:re}),le=ie.datafeed,de=ie.symbolsInfo,Be=(0,Je.DP)(),ve=Be.theme,Ce=ve==="light"||ve==="light_glacier"?"light":"dark",ce=(0,yr.Z)(Ce),Ke=(0,r.useRef)(-1),Ze=Ht({symbol:n}).positionLines,Ge=Nt({symbol:n,namespace:a}).orderLines,Xe=ir({symbol:n}).executionOrders,Ie=pt({namespace:a,symbol:n}),He=(0,se.y0)(),ur=(0,r.useRef)(He),Tr=(0,r.useRef)(N),_r=(0,r.useRef)(l),Cr=(0,r.useRef)(J),Er=(0,r.useRef)(oe),we=(0,r.useRef)(de);(0,r.useEffect)(function(){we.current=de},[de]),(0,r.useEffect)(function(){ur.current=He},[He]),(0,r.useEffect)(function(){Tr.current=N},[N]),(0,r.useEffect)(function(){_r.current=l},[l]),(0,r.useEffect)(function(){Cr.current=J},[J]),(0,r.useEffect)(function(){Er.current=oe},[oe]),(0,Ve.a)(n,s),(0,sr.A)({symbol:n,interval:Y,priceType:j});var Dr=(0,r.useCallback)(function(){Ke.current=Date.now()},[]),z=(0,r.useCallback)((0,o._)(function(){var De,ze,qe,be,vr,fr,er;return(0,i._)(this,function(nr){switch(nr.label){case 0:if(Cr.current||!_r.current)return[2,Promise.resolve()];nr.label=1;case 1:return nr.trys.push([1,5,,6]),De=h().createInstance({name:a}),[4,De.keys()];case 2:return ze=nr.sent(),qe=ze.filter(function(mr){return/^((#TV_SYMBOL-)|(myTradingView))/g.test(mr)}),be=qe.map((function(){var mr=(0,o._)(function(ar){return(0,i._)(this,function(Sr){return[2,De.getItem(ar)]})});return function(ar){return mr.apply(this,arguments)}})()),[4,Promise.all(be)];case 3:return vr=nr.sent(),fr=U()(qe,vr),[4,Promise.all(Tn.map((function(){var mr=(0,o._)(function(ar){var Sr,Wr;return(0,i._)(this,function(Gr){switch(Gr.label){case 0:return a===ar?[2,Promise.resolve()]:(Sr=h().createInstance({name:ar}),Wr=fr.map((function(){var Sn=(0,o._)(function(Lt){var _t,Ot,At;return(0,i._)(this,function(Mn){return _t=(0,v._)(Lt,2),Ot=_t[0],At=_t[1],Ot?[2,Sr.setItem(Ot,At)]:[2,Promise.resolve()]})});return function(Lt){return Sn.apply(this,arguments)}})()),[4,Promise.all(Wr)]);case 1:return Gr.sent(),[2,Promise.resolve()]}})});return function(ar){return mr.apply(this,arguments)}})()))];case 4:return nr.sent(),[2,Promise.resolve()];case 5:return er=nr.sent(),console.warn(typeof er=="string"?er:er.message),[2,Promise.resolve()];case 6:return[2]}})}),[a]),Q=(0,r.useMemo)(function(){return z},[z]),ee=(0,or.R)(),ae=(0,Ne.fQ)(a),Se=(0,Ne.m3)(a),xe=(0,Ne.S_)(a),ne=(0,Ne.Ds)(a),ke=(0,r.useRef)(n),Me=(0,r.useRef)(Y),fe=(0,r.useCallback)(function(){return ee("tradingView"),xe({panel_index:0,symbol:n,interval:Y}),hn()},[Y,ee,n,xe]),Pe=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be,vr,fr,er=(fr=(vr=(be=qe.symbolExt())===null||be===void 0?void 0:be.name)!==null&&vr!==void 0?vr:qe.symbol())!==null&&fr!==void 0?fr:"";ae({panel_index:ze,symbol:er,previous_symbol:ke.current}),ke.current=er},[ae]),_e=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be=qe.resolution();Se({panel_index:ze,interval:be,previous_interval:Me.current}),Me.current=be},[Se]),je=(0,r.useCallback)(function(){gn(Ke.current)},[]),Ee=(0,r.useCallback)(function(){},[]),me=(0,r.useMemo)(function(){return J?Ee:void 0},[Ee,J]),ye=(0,r.useCallback)((function(){var De=(0,o._)(function(ze){var qe,be,vr,fr,er,nr,mr,ar,Sr;return(0,i._)(this,function(Wr){switch(Wr.label){case 0:return qe=ze.chart,be=qe.symbolExt(),be?(vr=b()(we.current,function(Gr){return Gr.ticker===be.ticker||Gr.symbol===be.ticker}),fr=vr||{},er=fr.params,nr=er===void 0?{}:er,mr=nr.priceType,ar=mr===void 0?"":mr,ar&&$(ar),Sr=Tr.current[be.name],Sr?Er.current||Cr.current?[2]:[4,z()]:[2]):[2];case 1:return Wr.sent(),G({stateSymbol:Sr.symbol,isDeliveryColumn:Sr.isDelivery}),[2]}})});return function(ze){return De.apply(this,arguments)}})(),[G,z,$]),$e=qr(),Oe=$e.onCrosshairMoved,lr=$e.crosshairPrice,Or=wr({namespace:a,symbol:n,priceType:j,crosshairPrice:lr}),dr=lt(),Lr=(0,r.useMemo)(function(){return L({symbol:n,params:{priceType:j}})},[n,j]),Ur=(0,r.useMemo)(function(){return{symbol:Lr,theme:ce,limitOrders:Ge,positionOrders:Ze,executionOrders:Xe,labelLines:Ie,initialConfig:{tvConfig:{symbol:Lr,library_path:"/".concat(Ye.u_,"/trading-platform-30/"),theme:ce,datafeed:le,enabled_features:Te,context_menu:Or,custom_formatters:dr},onCrosshairMoved:Oe,onScriptLoaded:Dr,onSave:Q,onChartReadyDone:fe,onFirstTimeDataLoadedDone:je,onInitChart:me,onActiveChartChanged:ye,onSymbolChanged:Pe,onIntervalChanged:_e}}},[Lr,le,Te,Xe,Ie,Or,dr,Oe,ye,fe,je,me,_e,Q,Dr,Pe,Ge,Ze,ce]),Yr=Le({namespace:a,type:"tv"}),ct=(0,r.useCallback)(function(){ne({panel_index:0,symbol:n}),Yr()},[Yr,n,ne]);return(0,p.jsxs)(D.t,{name:"trading_view",onReset:ct,children:[(0,p.jsx)(Ue.A,{namespace:a,tradingViewProps:Ur}),w&&j==="index"&&O===c.Ev.Single&&W===c.tU.TradingView&&(0,p.jsx)(rr.B,{})]})};const Cn=Dt}';
+ function createMirrorFactory(__dispatchChartMirror) {
+ if (typeof __dispatchChartMirror !== "function") throw new Error("Mirror dispatch must be a function");
+ return { 70940(Z, x, e) {
+ "use strict";
+ e.r(x), e.d(x, { TradingView: () => Dt, default: () => Cn });
+ var o = e(49253), v = e(77135), d = e(75510), i = e(42288), p = e(31085), r = e(41594), M = e(43917), h = e.n(M), P = e(81936), b = e.n(P), V = e(7980), U = e.n(V), D = e(76469), c = e(4260), k = e(53837), B = e(27571), C = e(45250), y = e(87646), A = e.n(y), q = e(80817);
+ const E = (0, C.range)(1, 1380), m = (0, C.range)(1, 365).map((t) => `${t}D`), R = (0, C.range)(1, 52).map((t) => `${t}W`), I = (0, C.range)(1, 12).map((t) => `${t}M`), F = ["1S", ...E, ...m, ...R, ...I], L = ({ symbol: t, params: n }) => {
+ const a = new URLSearchParams(n);
+ return `${t}@${a.toString()}`;
+ }, X = (t) => {
+ const [n, a] = t.split("@"), s = new URLSearchParams(a).entries(), g = A()(Array.from(s));
+ return { symbol: n, params: g };
+ }, ue = () => {
+ const t = /* @__PURE__ */ new Map();
+ let n = {}, a = [], u = { onReady: () => null, resolveSymbol: () => null, searchSymbols: () => null, getBars: () => null, subscribeBars: () => null, unsubscribeBars: () => null };
+ return ({ config: s, onSymbolResolving: g, onSymbolResolved: S, onFetchSymbolsInfo: K, onFetchBars: Y, onSubscribeBars: H, onUnsubscribeBars: j, onFetchServerTimeApi: $, onTransformSymbolInfo: f = ({ template: T }) => T, onSearchSymbols: l = () => true }) => (u = { onReady: (T) => {
+ setTimeout(async () => {
+ const _ = (await K()).map((O) => ({ ...O, ticker: L({ symbol: O.symbol, params: O.params || {} }) }));
+ a = _.map(({ ticker: O, symbol: G, description: te = "", exchange: J = "", type: oe = "" }) => ({ symbol: G, ticker: O, description: te, exchange: J, type: oe })), n = (0, C.keyBy)(_, "ticker"), T({ exchanges: [], symbols_types: [], supported_resolutions: F, supports_marks: false, supports_timescale_marks: false, supports_time: true, ...s });
+ });
+ }, resolveSymbol: async (T, W, _) => {
+ const O = n[T];
+ if (!O) {
+ _(`cannot resolve symbol - ${T}`);
+ return;
+ }
+ const G = f({ info: O, template: { description: O.description || "", fractional: false, has_seconds: true, seconds_multipliers: ["1"], has_intraday: true, intraday_multipliers: ["1", "3", "5", "15", "30", "60", "120", "240", "360", "480", "720"], has_daily: true, daily_multipliers: ["1"], has_weekly_and_monthly: true, weekly_multipliers: ["1"], monthly_multipliers: ["1"], minmov: 1, minmove2: 0, name: O.symbol, ticker: O.ticker, timezone: "Etc/UTC", pricescale: O.tickSize, session: "24x7", type: O.type || "", exchange: O.exchange || "", listed_exchange: O.exchange || "", format: "price", supported_resolutions: F, visible_plots_set: "ohlcv", volume_precision: 3 } });
+ if (g) {
+ const te = X(G.ticker || "");
+ if (!await g({ symbol: T, info: G, tickerInfo: te })) {
+ _(`symbol resolving error - ${T}`);
+ return;
+ }
+ }
+ setTimeout(() => {
+ W(G), S?.(G);
+ });
+ }, searchSymbols: (T, W, _, O) => {
+ const G = new RegExp(T, "i"), te = new RegExp(W, "i"), J = new RegExp(_, "i"), oe = a.filter((Te) => {
+ const { symbol: N, ticker: w, description: re = "", exchange: ie = "", type: le = "" } = Te;
+ return [N, w, re].some((de) => G.test(de || "")) && te.test(ie) && J.test(le) && l?.({ item: Te });
+ });
+ O(oe);
+ }, getBars: async (T, W, _, O, G) => {
+ const { from: te, to: J, firstDataRequest: oe } = _, Te = (0, q.Cz)(W);
+ try {
+ if (J >= 0) {
+ const N = X(T.ticker || ""), w = await Y({ symbolInfo: T, resolution: W, rangeStartDate: te, rangeEndDate: J, tickerInfo: N, binanceInterval: Te, firstDataRequest: oe });
+ O(w, { noData: w.length === 0 });
+ } else O([], { noData: true });
+ } catch (N) {
+ G(N.toString());
+ }
+ }, subscribeBars: (T, W, _, O, G) => {
+ const te = X(T.ticker || ""), J = (0, q.Cz)(W), oe = H({ symbolInfo: T, resolution: W, callback: _, onResetCacheNeededCallback: G, tickerInfo: te, binanceInterval: J });
+ t.set(O, oe);
+ }, unsubscribeBars: (T) => {
+ t?.get(T)?.(), t?.delete(T), j?.(T);
+ }, getServerTime: async (T) => {
+ try {
+ const W = await $();
+ T(W);
+ } catch (W) {
+ console.warn(W), T(Date.now() / 1e3);
+ }
+ } }, { datafeed: u, observers: t, symbolsInfo: n });
+ }, Le = ({ namespace: t, type: n }) => {
+ const a = (0, r.useMemo)(() => h().createInstance({ name: t }), [t]);
+ return (0, r.useCallback)(() => {
+ n === "tv" && a.removeItem("myTradingView");
+ }, [a, n]);
+ };
+ var Ue = e(76535), pr = e(26531), Ye = e(17409), Qe = e(94652), Ae = e(76830), he = e(48651), Fe = e(79344), se = e(51029), Je = e(36307), or = e(25379), yr = e(44745), Ne = e(15727), rr = e(45358), sr = e(82071), Ve = e(85411), ge = e(94889), hr = e(94547), tr = e(80686), Ar = e(61875);
+ function Fr(t) {
+ return (0, ge._)(t) || (0, hr._)(t) || (0, Ar._)(t) || (0, tr._)();
+ }
+ var Nr = e(92873), zr = e(35630), Vr = e(12055), Qr = e(79515), Br = e(80737), Jr = e(40545), Zr = e(98107), Kr = e(51846), Xr = "Chart.Crosshair.PlusButton.DrawHorizontalLine", wr = function(t) {
+ var n = t.namespace, a = t.symbol, u = t.priceType, s = t.crosshairPrice, g = (0, Nr.o)("", "kline-ui").getI18n, S = (0, Vr.r)().format, K = k.y$(n), Y = (0, Br.S)({ namespace: n }), H = (0, Jr.hw)(), j = (0, Zr.c)(), $ = (0, Qr.nH)(), f = (0, se.Ye)(), l = (0, Kr.E)({ symbol: a }), T = (0, Fe.f0)(a), W = T.isCM, _ = (0, r.useRef)(s);
+ (0, r.useEffect)(function() {
+ _.current = s;
+ }, [s]);
+ var O = (0, r.useRef)(H);
+ (0, r.useEffect)(function() {
+ O.current = H;
+ }, [H]);
+ var G = (0, r.useRef)(j);
+ (0, r.useEffect)(function() {
+ G.current = j;
+ }, [j]);
+ var te = (0, r.useRef)(u);
+ (0, r.useEffect)(function() {
+ te.current = u;
+ }, [u]);
+ var J = (0, r.useRef)($);
+ (0, r.useEffect)(function() {
+ J.current = $;
+ }, [$]);
+ var oe = (0, r.useRef)(f);
+ (0, r.useEffect)(function() {
+ oe.current = f;
+ }, [f]);
+ var Te = (0, r.useRef)(l);
+ (0, r.useEffect)(function() {
+ Te.current = l;
+ }, [l]);
+ var N = (0, r.useRef)(W);
+ return (0, r.useEffect)(function() {
+ N.current = W;
+ }, [W]), { items_processor: (function() {
+ var w = (0, o._)(function(re, ie, le) {
+ var de, Be, ve, Ce, ce, Ke, Ze, Ge, Xe, Ie, He, ur, Tr, _r, Cr;
+ return (0, i._)(this, function(Er) {
+ if (Be = K.getState().tradingViewReference, !Be) return console.warn("[context-menu] trading view reference is not found"), [2, re];
+ if (Be.chartsCount() > 1) return [2, re];
+ if (ve = Be.activeChart().symbolExt(), !ve) return [2, re];
+ switch (Ce = ve.name, ce = _.current, Ke = le.menuName === "CrosshairMenuView" ? "chart-trading" : "context-menu", Ze = { price: "".concat(ce.value), formattedPrice: ce.formatted, source: Ke, chartType: "tradingview" }, Ge = (de = oe.current[Ce]) === null || de === void 0 ? void 0 : de.baseAsset, Xe = g("trade", { defaultValue: "Trade" }), Ie = S(ce.formatted), He = G.current && te.current === c.SJ.Last && J.current && Ge ? [ie.createAction({ actionId: "Chart.PlaceOrder.BuyLimit", label: "".concat(Xe, " ").concat(Ge, " @ ").concat(Ie, " ").concat(g("limit", { defaultValue: "Limit" })), onExecute: function() {
+ return Te.current.onClickBuyLimit(Ze);
+ } }), ie.createAction({ actionId: "Chart.PlaceOrder.BuyStop", label: "".concat(Xe, " ").concat(Ge, " @ ").concat(Ie, " ").concat(g("stop", { defaultValue: "Stop" })), onExecute: function() {
+ return Te.current.onClickBuyStop(Ze);
+ } })] : [], ur = O.current ? ie.createAction({ actionId: "Chart.PriceAlert.CreateAtCrosshair", label: g("create-alert-at", { defaultValue: "Create Alert at {{price}}", price: ce.formatted }), onExecute: function() {
+ Y({ symbol: Ce, price: ce.formatted, mode: zr.n.CREATE });
+ } }) : null, re.forEach(function(we) {
+ if (!(!("execute" in we) || !("getState" in we))) {
+ var Dr = we.getState();
+ if (Dr.actionId === Xr) {
+ var z = we.execute.bind(we);
+ we.execute = function() {
+ (0, Kr.A)({ elementId: "draw_horizontal_line", isDelivery: N.current, chartType: "tradingview" }), z();
+ };
+ }
+ }
+ }), le.menuName) {
+ case "CrosshairMenuView":
+ return ur ? [2, [ur].concat((0, d._)(He), (0, d._)(re.slice()))] : [2, (0, d._)(He).concat((0, d._)(re.slice()))];
+ case "ChartContextMenu":
+ return ur ? (Tr = Fr(re), _r = Tr[0], Cr = Tr.slice(1), [2, [_r, ur].concat((0, d._)(He), (0, d._)(Cr))]) : [2, (0, d._)(He).concat((0, d._)(re.slice()))];
+ default:
+ return [2, re];
+ }
+ return [2];
+ });
+ });
+ return function(re, ie, le) {
+ return w.apply(this, arguments);
+ };
+ })() };
+ }, qr = function() {
+ var t = (0, r.useState)({ value: NaN, formatted: "" }), n = t[0], a = t[1], u = (0, r.useCallback)(function(s) {
+ var g = s.crosshairData, S = s.activeChart;
+ S && a({ value: g.price, formatted: S.priceFormatter().format(g.price) });
+ }, []);
+ return { onCrosshairMoved: u, crosshairPrice: n };
+ };
+ const Hr = (t) => Math.floor(Math.log10(t)), et = (t) => {
+ const [n = "1"] = t.split(",");
+ return Hr(+n);
+ }, pe = ({ symbolInfo: t, minTick: n }) => {
+ const { pricescale: a = 2 } = t || {};
+ return n === "default" || n === void 0 ? Hr(a) : et(n);
+ }, br = (t) => (n, a) => {
+ try {
+ return n ? { format: (u) => {
+ const s = pe({ symbolInfo: n, minTick: a });
+ return t.format({ symbolInfo: n, minTick: a, value: u, precision: s });
+ } } : null;
+ } catch {
+ return null;
+ }
+ }, Mr = 10 ** 12, rt = 10 ** 9, tt = 10 ** 6, jr = 10 ** 3, gr = (t) => {
+ const n = t.toString(), [, a = ""] = n.split(".");
+ return Math.min(a.length, 3);
+ }, nt = ({ value: t }) => {
+ const n = Math.abs(t);
+ if (n >= Mr) {
+ const a = t / Mr;
+ return { value: a, unit: "T", precision: gr(a) };
+ }
+ if (n >= rt) {
+ const a = t / rt;
+ return { value: a, unit: "B", precision: gr(a) };
+ }
+ if (n >= tt) {
+ const a = t / tt;
+ return { value: a, unit: "M", precision: gr(a) };
+ }
+ if (n >= jr) {
+ const a = t / jr;
+ return { value: a, unit: "K", precision: gr(a) };
+ }
+ return { value: t, unit: "", precision: gr(t) };
+ }, ut = (t) => (n, a, u) => {
+ try {
+ return a && n.type === "volume" ? { format: (s) => s === void 0 ? "" : t.formatVolume({ format: n, symbolInfo: a, precision: u, value: s, volumeDetail: nt({ value: s }) }) } : null;
+ } catch {
+ return null;
+ }
+ };
+ var lt = function() {
+ var t = (0, Vr.r)(), n = t.format, a = (0, r.useMemo)(function() {
+ return br({ format: function(s) {
+ var g = s.value, S = s.precision;
+ return n(g, { precision: S });
+ } });
+ }, [n]), u = (0, r.useMemo)(function() {
+ return ut({ formatVolume: function(s) {
+ var g = s.value, S = s.volumeDetail;
+ if (g === void 0 || !S) return "";
+ var K = n(S.value, { precision: S.precision });
+ return "".concat(K).concat(S.unit);
+ } });
+ }, [n]);
+ return { priceFormatterFactory: a, studyFormatterFactory: u };
+ }, dt = e(74069), ir = function(t) {
+ var n = (0, dt.K)(t), a = (0, r.useMemo)(function() {
+ return n.map(function(u) {
+ var s = u.id, g = u.time, S = u.isBuy, K = u.price, Y = u.tooltip;
+ return { id: s, text: "", price: K, quantity: "", time: Math.round(g / 1e3), isBuy: S, tooltip: Y };
+ });
+ }, [n]);
+ return { executionOrders: a };
+ }, at = function(t) {
+ var n = t.namespace, a = t.symbol, u = (0, Br.j)({ namespace: n, symbol: a });
+ return (0, r.useMemo)(function() {
+ return u.map(function(s) {
+ var g = s.id, S = s.price, K = s.formattedPrice, Y = s.sideText, H = s.showCloseButton, j = s.isPriceChangeable, $ = s.cancelTooltipText, f = s.onClick, l = s.onCancel, T = s.onPriceChanged;
+ return { id: g, price: +S, text: "".concat(Y, " ").concat(K), editable: j, cancellable: H, cancelTooltip: $, type: c.ND.PriceAlert, onCancel: H ? l : void 0, onMove: j ? function(W) {
+ var _ = W.price;
+ T({ currentPrice: _ });
+ } : void 0, onClick: f };
+ });
+ }, [u]);
+ }, vt = e(90664), ot = function(t) {
+ var n = t.symbol, a = (0, vt.w)({ symbol: n });
+ return (0, r.useMemo)(function() {
+ return a.map(function(u) {
+ var s = u.id, g = u.text, S = u.price;
+ return { id: s, text: g, price: +S, type: c.ND.BreakEvenPrice };
+ });
+ }, [a]);
+ }, cr = e(82508), ft = e(91025), mt = e(36077), Rr = function(t) {
+ var n = t.symbol, a = (0, mt.c)({ symbol: n });
+ return (0, r.useMemo)(function() {
+ return a.map(function(u) {
+ return (0, ft._)((0, cr._)({}, u), { type: c.ND.LiquidationPrice });
+ });
+ }, [a]);
+ }, pt = function(t) {
+ var n = t.namespace, a = t.symbol, u = at({ namespace: n, symbol: a }), s = ot({ symbol: a }), g = Rr({ symbol: a });
+ return (0, r.useMemo)(function() {
+ return (0, d._)(u).concat((0, d._)(s), (0, d._)(g));
+ }, [u, s, g]);
+ }, st = e(42902), Ir = e(90170), yt = e(63345), ht = e(48210), Pr = e(51289), xr = e(24326), $r = e(90922), kr = e(34235), it = e(63762), Ft = function(t) {
+ var n = (0, Fe.f0)(t), a = n.pricePrecision, u = n.isCM, s = (0, st.yD)(t) || {}, g = s.lastPrice, S = g === void 0 ? "0" : g, K = (0, kr.Bp)(u), Y = (0, it.HY)(t), H = (0, kr.gv)(u), j = (0, kr.WF)(u);
+ return (0, r.useMemo)(function() {
+ return (0, $r.qF)({ orders: (0, d._)(K).concat((0, d._)(H), (0, d._)(j)), symbol: t }).map(function($) {
+ return Y({ order: $, lastPrice: S, pricePrecision: a });
+ });
+ }, [S, K, H, a, t, Y]);
+ }, gt = e(31542), Tt = function(t) {
+ var n = t.type;
+ (0, gt.u4)("webClick", { module: "chart", elementId: n });
+ }, Nt = function(t) {
+ var n = t.symbol, a = t.namespace, u = (0, r.useState)(0), s = u[0], g = u[1], S = (0, r.useCallback)(function() {
+ return g(function(N) {
+ return N + 1;
+ });
+ }, []), K = Ft(n), Y = (0, xr.IT)({ namespace: a }), H = (0, Fe.f0)(n), j = H.isDelivery, $ = H.contractType, f = (0, st.yD)(n) || {}, l = f.lastPrice, T = l === void 0 ? "0" : l, W = (0, yt.s)(n), _ = (0, it.YZ)({ symbol: n, afterConfirmationModalIsShown: S }), O = (0, it.u8)(n), G = (0, it.jn)(n), te = (0, r.useCallback)(function(N) {
+ return (0, ht.D)({ order: N, isDelivery: j, symbol: j ? N.symbol : "".concat(N.symbol, "_").concat($), module: "tradingview_kline" });
+ }, [$, j]), J = (0, r.useMemo)(function() {
+ return K.map(function(N) {
+ var w = N.order, re = N.price, ie = N.quantity, le = N.quantityText, de = N.isBuy, Be = N.text, ve = N.priceTooltipText, Ce = N.pnlText, ce = (0, Pr.$L)(w), Ke = (0, Pr.OX)(w), Ze = (0, Pr.xL)(w), Ge = ce === "LIMIT", Xe = (0, Ir.Y)(w);
+ return { id: String(Ke), text: Be, price: +re, quantity: le, isBuy: de, editable: Xe && G, tooltip: ve, modifyTooltip: w.closePosition || !Ge ? "" : void 0, onMove: G ? function(Ie) {
+ var He = Ie.price;
+ te(w), Tt({ type: "TV_vertical" }), _({ order: w, price: +He, quantity: +Ze, lastPrice: T });
+ } : void 0, onModify: w.closePosition || !Ge || !Xe ? void 0 : function() {
+ Tt({ type: "TV_amountclick" }), O({ order: w, price: re.toString(), quantity: ie, lastPrice: T });
+ }, onCancel: function() {
+ Tt({ type: "TV_cancel" }), W(w);
+ } };
+ });
+ }, [G, K, T, te, _, O, W, s]), oe = (0, r.useMemo)(function() {
+ return Y.map(function(N) {
+ var w = N.id, re = N.value, ie = N.text, le = N.onChange, de = N.isBuy, Be = N.type;
+ return { id: w, price: +re, quantity: "", text: ie, isBuy: de, editable: true, limitOrderType: c.aA.OrderPreview, onMove: function(ve) {
+ var Ce = ve.price;
+ (0, xr.eP)({ namespace: a, orderType: Be }), le({ id: w, newPrice: Ce });
+ }, onMoving: function(ve) {
+ var Ce = ve.price;
+ return le({ id: w, newPrice: Ce });
+ } };
+ });
+ }, [Y, a]), Te = (0, r.useMemo)(function() {
+ return (0, d._)(J).concat((0, d._)(oe));
+ }, [J, oe]);
+ return { orderLines: Te };
+ }, Vt = e(17855), Bt = function(t, n) {
+ var a = (0, r.useState)(t()), u = a[0], s = a[1];
+ return (0, Vt.A)(function() {
+ return s(t());
+ }, n), u;
+ }, Kt = e(58656), Ht = function(t) {
+ var n = t.symbol, a = (0, Kt.Q)({ symbol: n }) || [], u = Bt(function() {
+ return a.map(function(s) {
+ var g = s.position, S = s.pnl, K = s.pnlInNumber, Y = s.pnlText, H = s.price, j = s.quantity, $ = s.reverseText, f = s.isBuy, l = s.displayReverseOrderModal, T = s.displayClosePositionModal;
+ return { id: g.id || "".concat(g.symbol, "-").concat(g.positionSide), PNL: K, text: "".concat(Y, " ").concat(S), price: H, quantity: j, tooltip: "", reverseTooltip: $, isBuy: f, onReverse: l, onClose: function() {
+ T(), (0, gt.u4)("$WebClick", { item: 1, module: "xxx", $element_id: "kline_close_position" });
+ } };
+ });
+ }, [a]);
+ return { positionLines: u };
+ }, jt = e(47354), bt = e.n(jt), $t = e(87017), Ut = e(99168), Yt = e(3742), Wt = e(43335), Gt = e(15540), zt = e(20163), Qt = e(47255);
+ const Jt = (t) => ({ endTime: n }) => (n ?? 0) === t;
+ var Zt = e(36488), Ct = e(48018), Xt = e(82911), We = e(69578), wt = e(53208), Rt = e(19862), qt = e(8874), en = e(26679), rn = e(66547), It = (function() {
+ var t = (0, o._)(function(n) {
+ var a, u, s, g, S, K, Y, H, j;
+ return (0, i._)(this, function($) {
+ switch ($.label) {
+ case 0:
+ a = n.onFetchKlineData, u = n.startTime, s = n.endTime, g = n.interval, S = n.limit, K = S === void 0 ? 1e3 : S, Y = [], H = [], j = s, $.label = 1;
+ case 1:
+ return [4, a((0, cr._)({ interval: g, limit: K }, j === 0 ? {} : { endTime: j }))];
+ case 2:
+ if (H = $.sent(), H.length === 0) return [3, 4];
+ H.forEach(function(f) {
+ var l = (0, v._)(f, 8), T = l[0], W = l[1], _ = l[2], O = l[3], G = l[4], te = l[5], J = l[7];
+ Y.push({ time: +T, open: +W, high: +_, low: +O, close: +G, volume: +te, quantity: +J });
+ }), j = H[0][0] - 1, $.label = 3;
+ case 3:
+ if (j > u) return [3, 1];
+ $.label = 4;
+ case 4:
+ return Y.sort(function(f, l) {
+ return f.time - l.time;
+ }), [2, Y];
+ }
+ });
+ });
+ return function(a) {
+ return t.apply(this, arguments);
+ };
+ })(), tn = function(t) {
+ var n = t.firstDataRequest, a = t.hasRequested, u = t.rangeEndDate;
+ return n && !a ? 0 : u * 1e3;
+ }, nn = e(78142), an = e(16238), St = e.n(an), on = e(98087), xt = e.n(on), sn = e(55009), cn = e.n(sn), kt = e(56477);
+ const Et = (t) => cn()(t.chartsCount(), (n) => t.chart(n)), un = async (t) => {
+ try {
+ const { schema: n, data: a } = await t.exportData({ includedStudies: [] }), u = xt()(a)?.[n.findIndex((s) => s.type === "time")];
+ return u !== void 0 && Number.isFinite(u) ? u * 1e3 : void 0;
+ } catch {
+ return;
+ }
+ }, ln = ({ getWidget: t, ...n }) => {
+ const a = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Map(), s = () => {
+ try {
+ const f = t();
+ return f ? St()(Et(f).map((l) => l.symbolExt()?.ticker)) : [];
+ } catch {
+ return [];
+ }
+ }, g = (f) => {
+ const l = s();
+ return !l.length || l.includes(f);
+ }, S = (f) => {
+ const l = u.get(f);
+ if (l) return l;
+ const T = { controller: new AbortController(), claimedAsMain: false };
+ return u.set(f, T), T;
+ }, K = (f) => {
+ const l = u.get(f);
+ l && (l.controller.abort(), u.delete(f));
+ }, Y = () => {
+ const f = s();
+ f.length && Array.from(u.keys()).forEach((l) => {
+ const T = u.get(l);
+ if (T) {
+ if (f.includes(l)) {
+ T.claimedAsMain = true;
+ return;
+ }
+ a.has(l) || T.claimedAsMain && K(l);
+ }
+ });
+ }, H = (f, l) => {
+ f.closed || l.time < f.highWater || (f.highWater = l.time, f.options.onTick(l), f.detector?.onBar(l), g(f.options.ticker) && $.onBar(l));
+ }, j = async (f, l) => {
+ f.held = [];
+ let T = l;
+ try {
+ const _ = (await f.options.fetchSince(l)).filter((O) => O.time >= l && !f.closed);
+ _.forEach((O) => {
+ f.highWater = Math.max(f.highWater, O.time), f.options.onTick(O);
+ }), T = xt()(_)?.time ?? l;
+ } finally {
+ const W = f.held ?? [];
+ f.held = void 0, W.filter((_) => _.time >= T).forEach((_) => {
+ H(f, _);
+ });
+ }
+ }, $ = (0, kt.k)({ ...n, onCheck: Y, repair: async () => {
+ const f = t();
+ if (f) try {
+ const l = Et(f), T = St()(l.map((_) => {
+ const O = a.get(_.symbolExt()?.ticker ?? "");
+ return O && { chart: _, feed: O };
+ }));
+ if (!T.length) {
+ f.resetCache(), l.forEach((_) => {
+ _.resetData();
+ });
+ return;
+ }
+ const W = St()(await Promise.all(T.map(async ({ chart: _, feed: O }) => {
+ const G = await un(_);
+ return G === void 0 ? void 0 : { feed: O, fromMs: G };
+ })));
+ await Promise.allSettled(W.map(({ feed: _, fromMs: O }) => j(_, O)));
+ } catch {
+ }
+ } });
+ return { openFeed: (f) => {
+ const l = { options: f, held: void 0, highWater: 0, closed: false, detector: void 0 };
+ return l.detector = (0, kt.k)({ ...n, subscribeSignals: f.subscribeSignals, repair: async () => {
+ l.closed || !l.highWater || g(f.ticker) || await j(l, l.highWater);
+ } }), a.set(f.ticker, l), g(f.ticker) && $.resubscribeSignals(f.subscribeSignals), { push: (T) => {
+ l.held ? l.held.push(T) : H(l, T);
+ }, close: () => {
+ l.closed = true, l.detector?.dispose(), a.get(f.ticker) === l && a.delete(f.ticker);
+ } };
+ }, subscribeWake: $.subscribeWake, getSignal: (f) => {
+ const l = S(f);
+ return s().includes(f) && (l.claimedAsMain = true), l.controller.signal;
+ }, dispose: () => {
+ u.forEach(({ controller: f }) => {
+ f.abort();
+ }), u.clear(), a.forEach((f) => {
+ f.detector?.dispose();
+ }), a.clear(), $.dispose();
+ } };
+ };
+ var dn = e(71730), vn = e(36620), fn = function(t) {
+ var n = t.namespace, a = k.y$(n), u = (0, vn.l)().getSocketSignals, s = (0, r.useRef)();
+ (0, r.useEffect)(function() {
+ return function() {
+ var S;
+ (S = s.current) === null || S === void 0 || S.instance.dispose(), s.current = void 0;
+ };
+ }, []);
+ var g = (0, r.useCallback)(function(S) {
+ var K, Y, H = S;
+ if (((K = s.current) === null || K === void 0 ? void 0 : K.key) === H) return s.current.instance;
+ (Y = s.current) === null || Y === void 0 || Y.instance.dispose();
+ var j = nn.wr(S), $ = ln({ getWidget: function() {
+ return a.getState().tradingViewReference;
+ }, checkPeriodMs: dn.D(j) });
+ return s.current = { key: H, instance: $ }, $;
+ }, [a]);
+ return (0, r.useMemo)(function() {
+ return { ensureRecovery: g, getSocketSignals: u };
+ }, [g, u]);
+ }, mn = "tv_web_error", pn = [Ye.bt.TRADING, Ye.bt.DELIVERING], Re;
+ (function(t) {
+ t.Futures = "futures", t.FuturesMark = "futures-mark", t.FuturesIndex = "futures-index", t.Delivery = "delivery", t.DeliveryMark = "delivery-mark", t.DeliveryIndex = "delivery-index";
+ })(Re || (Re = {}));
+ var Mt = function(t) {
+ var n = t.symbol, a = t.description, u = t.productType, s = t.pricePrecision, g = t.params, S = t.supportedResolutions, K = S === void 0 ? F : S;
+ return { symbol: n, description: a, tickSize: Math.pow(10, s), exchange: "Binance", supportedResolutions: K, type: u, params: g };
+ }, yn = function(t) {
+ var n = t.namespace, a = t.useMiniTicker, u = a === void 0 ? false : a, s = t.isCM, g = c.y$(n), S = k.y$(n), K = (0, $t.useQueryClient)(), Y = fn({ namespace: n }), H = Y.ensureRecovery, j = Y.getSocketSignals, $ = (0, r.useRef)(0), f = (0, r.useRef)(false), l = (0, r.useMemo)(function() {
+ return bt()(function() {
+ return (0, We.HP)({ eventId: "trd_chart_tv_load_timeout", stepId: "getBarsStart", catch: function(Q) {
+ var ee = Q.error, ae = Q.track;
+ ae({ GET_BAR_START_TYPE: ee });
+ }, log: function(Q) {
+ var ee = Q.track;
+ ee({ GET_BAR_START_TYPE: "SUCCESS" });
+ } });
+ });
+ }, []), T = (0, r.useMemo)(function() {
+ return bt()(function(z) {
+ return (0, We.HP)({ eventId: "trd_chart_tv_load_timeout", stepId: "getBarsEnd", catch: function(ee) {
+ var ae = ee.error, Se = ee.track;
+ Se({ GET_BAR_END_TYPE: ae, duration_: (0, We.Pu)(z - $.current) });
+ }, log: function(ee) {
+ var ae = ee.track;
+ ae({ GET_BAR_END_TYPE: "SUCCESS", duration_: (0, We.Pu)(z - $.current) });
+ } });
+ });
+ }, []), W = (0, Nr.o)("trd-chart", "trade-ui"), _ = W.getI18n, O = (0, he.ud)(), G = O.getI18n, te = (0, v._)(g(c.Ir), 1), J = te[0], oe = (0, v._)(S(k.A7), 2), Te = oe[1], N = (0, se.Ye)(), w = (0, Zt.JD)(s).isContUnit, re = (0, se.y_)(), ie = (0, r.useRef)(N), le = (0, r.useRef)(J), de = (0, r.useRef)(re);
+ (0, r.useEffect)(function() {
+ ie.current = N;
+ }, [N]), (0, r.useEffect)(function() {
+ le.current = J;
+ }, [J]), (0, r.useEffect)(function() {
+ de.current = re;
+ }, [re]);
+ var Be = (0, r.useMemo)(function() {
+ return w ? Rt.b : Rt.S;
+ }, [w]), ve = (0, r.useMemo)(function() {
+ return G("usdtFutures-13", { defaultValue: "Futures" }) || "";
+ }, [G]), Ce = (0, r.useMemo)(function() {
+ return G("inverseFutures-13", { defaultValue: "Delivery" }) || "";
+ }, [G]), ce = (0, r.useMemo)(function() {
+ return _("indexPrice", { defaultValue: "Index Price" }) || "";
+ }, [_]), Ke = (0, r.useMemo)(function() {
+ return _("markPrice", { defaultValue: "Mark Price" }) || "";
+ }, [_]), Ze = (0, r.useMemo)(function() {
+ return _("lastPrice", { defaultValue: "Last Price" }) || "";
+ }, [_]), Ge = (0, r.useMemo)(function() {
+ return { exchanges: [{ name: "Binance", value: "Binance", desc: "" }], symbols_types: [{ name: ve, value: Re.Futures }, { name: "".concat(ve, " ").concat(Ke), value: Re.FuturesMark }, { name: "".concat(ve, " ").concat(ce), value: Re.FuturesIndex }, { name: Ce, value: Re.Delivery }, { name: "".concat(Ce, " ").concat(Ke), value: Re.DeliveryMark }, { name: "".concat(Ce, " ").concat(ce), value: Re.DeliveryIndex }] };
+ }, [Ce, ve, ce, Ke]), Xe = (0, r.useCallback)((0, o._)(function() {
+ var z, Q, ee;
+ return (0, i._)(this, function(ae) {
+ switch (ae.label) {
+ case 0:
+ return ae.trys.push([0, 2, , 3]), [4, (0, en.c)(K)];
+ case 1:
+ return ae.sent(), [3, 3];
+ case 2:
+ return z = ae.sent(), (0, gt.u4)(mn, { type: "onFetchSymbolsInfo", message: z.message, business: "futures" }), [3, 3];
+ case 3:
+ return Q = ie.current, ee = Object.keys(Q).reduce(function(Se, xe) {
+ var ne = Q[xe] || Ct.o, ke = ne.symbol, Me = ne.pricePrecision, fe = ne.isDelivery, Pe = ne.contractStatus;
+ if (!pn.includes(Pe)) return Se;
+ var _e = (0, Yt.S)(ne, G), je = _e.symbol, Ee = _e.contractTypeI18n, me = _e.deliveryDateFormat, ye = "".concat(je, " ").concat(Ee).concat(me ? " ".concat(me) : ""), $e = "".concat(ye, " ").concat(Ze), Oe = "".concat(ye, " ").concat(ce), lr = "".concat(ye, " ").concat(Ke);
+ return Se.concat([Mt({ symbol: ke, description: $e, productType: fe ? Re.Delivery : Re.Futures, pricePrecision: Me, params: { priceType: c.SJ.Last } }), Mt({ symbol: ke, description: Oe, productType: fe ? Re.DeliveryIndex : Re.FuturesIndex, pricePrecision: Me, params: { priceType: c.SJ.Index } }), Mt({ symbol: ke, description: lr, productType: fe ? Re.DeliveryMark : Re.FuturesMark, pricePrecision: (0, Xt.X)(Me), params: { priceType: c.SJ.Mark } })]);
+ }, []), [2, ee];
+ }
+ });
+ }), [G, ce, Ke, Ze, K]), Ie = (0, r.useCallback)(function(z) {
+ var Q = z.ticker, ee = z.symbol, ae = z.interval, Se = z.priceType, xe = z.retryUntil, ne = ie.current[ee] || Ct.o, ke = ne.pair, Me = ne.contractType, fe = ne.contractVal, Pe = ne.isDelivery, _e = Pe ? Be(fe) : function(Oe) {
+ return Oe;
+ }, je = (0, wt.h)({ symbol: ee, pair: ke, contractType: Me, isDelivery: Pe, priceType: Se }), Ee, me, ye = function(Oe) {
+ return zt.y("tv", "".concat(Pe ? "cm" : "um", "|").concat(ee, "|").concat(Se, "|").concat(Oe.interval, "|").concat((Ee = Oe.limit) !== null && Ee !== void 0 ? Ee : "", "|").concat((me = Oe.endTime) !== null && me !== void 0 ? me : ""));
+ };
+ if (xe === void 0) return { transform: _e, onFetchKlineData: je };
+ var $e = H(ae);
+ return { transform: _e, onFetchKlineData: Qt.b(je, { enabled: Jt(xe), key: ye, signal: $e.getSignal(Q), subscribeWake: $e.subscribeWake }) };
+ }, [H, Be]), He = (0, r.useCallback)((function() {
+ var z = (0, o._)(function(Q) {
+ var ee, ae, Se, xe, ne, ke, Me, fe, Pe, _e, je, Ee, me, ye, $e, Oe, lr, Or;
+ return (0, i._)(this, function(dr) {
+ switch (dr.label) {
+ case 0:
+ return ee = Q.binanceInterval, ae = Q.rangeStartDate, Se = Q.rangeEndDate, xe = Q.tickerInfo, ne = Q.symbolInfo, ke = Q.firstDataRequest, $.current = Date.now(), l(), Me = xe.symbol, fe = Me === void 0 ? "" : Me, Pe = xe.params, _e = Pe.priceType, je = _e === void 0 ? le.current : _e, de.current({ symbol: fe, priceType: je }) ? (T(Date.now()), [2, []]) : (Ee = Math.max(0, ae * 1e3), me = tn({ firstDataRequest: ke, hasRequested: f.current, rangeEndDate: Se }), f.current = true, $e = Ie({ ticker: (ye = ne.ticker) !== null && ye !== void 0 ? ye : "", symbol: fe, interval: ee, priceType: je, retryUntil: ke ? me : void 0 }), Oe = $e.transform, lr = $e.onFetchKlineData, [4, It({ onFetchKlineData: lr, startTime: Ee, endTime: me, interval: ee })]);
+ case 1:
+ return Or = dr.sent(), T(Date.now()), [2, Or.map(Oe)];
+ }
+ });
+ });
+ return function(Q) {
+ return z.apply(this, arguments);
+ };
+ })(), [Ie, T, l]), ur = (0, r.useCallback)(function(z) {
+ var Q = z.binanceInterval, ee = z.callback, ae = z.tickerInfo, Se = z.symbolInfo, xe = ae.symbol, ne = xe === void 0 ? "" : xe, ke = ae.params, Me = ke.priceType, fe = Me === void 0 ? le.current : Me;
+ if (de.current({ symbol: ne, priceType: fe })) return function() {
+ return null;
+ };
+ var Pe = ie.current[ne] || Ct.o, _e = Pe.pair, je = Pe.contractType, Ee = Pe.isDelivery, me, ye = (me = Se.ticker) !== null && me !== void 0 ? me : "", $e = Ie({ ticker: ye, symbol: ne, interval: Q, priceType: fe }).transform, Oe = H(Q), lr = Oe.openFeed({ ticker: ye, onTick: ee, subscribeSignals: j(Ee), fetchSince: (function() {
+ var dr = (0, o._)(function(Lr) {
+ var Ur, Yr, ct, De;
+ return (0, i._)(this, function(ze) {
+ switch (ze.label) {
+ case 0:
+ return Ur = Ie({ ticker: ye, symbol: ne, interval: Q, priceType: fe, retryUntil: 0 }), Yr = Ur.transform, ct = Ur.onFetchKlineData, [4, It({ onFetchKlineData: ct, startTime: Lr, endTime: 0, interval: Q })];
+ case 1:
+ return De = ze.sent(), [2, De.map(Yr)];
+ }
+ });
+ });
+ return function(Lr) {
+ return dr.apply(this, arguments);
+ };
+ })() }), Or = (0, rn.B)({ symbol: ne, pair: _e, contractType: je, interval: Q, priceType: fe, isDelivery: Ee, useMiniTicker: u, callback: function(dr) {
+ lr.push($e(dr));
+ } });
+ return function() {
+ lr.close(), Or();
+ };
+ }, [Ie, H, j, u]), Tr = (0, r.useCallback)((0, o._)(function() {
+ var z;
+ return (0, i._)(this, function(Q) {
+ switch (Q.label) {
+ case 0:
+ return [4, K.fetchQuery(Wt.Bz.SERVER_TIME(s), function() {
+ return (0, Gt.l)(s);
+ }, {})];
+ case 1:
+ return z = Q.sent(), [2, Math.floor(z / 1e3)];
+ }
+ });
+ }), [K, s]), _r = (0, r.useCallback)(function(z) {
+ return (0, cr._)({}, z.template, (0, qt.sp)(s));
+ }, [s]), Cr = (0, r.useMemo)(function() {
+ return ue();
+ }, []), Er = Cr({ config: Ge, onFetchSymbolsInfo: Xe, onFetchBars: He, onSubscribeBars: ur, onFetchServerTimeApi: Tr, onTransformSymbolInfo: _r }), we = Er.datafeed, Dr = Er.symbolsInfo;
+ return (0, Ut.A)(function() {
+ return Te();
+ }, [w]), { datafeed: we, symbolsInfo: Dr };
+ }, Pt = "trd_chart_tv_load_timeout", hn = function() {
+ var t = (0, We.PM)({ id: "chartReady" }), n = (0, We.PM)({ id: "getBarsStart", timeout: 3 }), a = (0, We.PM)({ id: "getBarsEnd", timeout: 6 }), u = (0, We.PM)({ id: "chartResponsive", timeout: 3 });
+ We.HP.init({ step: t._(n)._(a)._(u), eventId: Pt }), (0, We.HP)({ eventId: Pt, stepId: "chartReady", catch: function(g) {
+ var S = g.error, K = g.track;
+ K({ CHART_READY_TYPE: S, connection: JSON.stringify(navigator.connection) });
+ }, log: function(g) {
+ var S = g.track;
+ S({ CHART_READY_TYPE: "SUCCESS", connection: JSON.stringify(navigator.connection) });
+ } });
+ }, gn = function(t) {
+ (0, We.HP)({ eventId: Pt, stepId: "chartResponsive", catch: function(a) {
+ var u = a.error, s = a.track;
+ s({ CHART_RESPONSIVE_TYPE: u, connection: JSON.stringify(navigator.connection) });
+ }, log: function(a) {
+ var u = a.track;
+ u({ CHART_RESPONSIVE_TYPE: "SUCCESS", duration_: (0, We.Pu)(Date.now() - t) });
+ } });
+ }, Tn = [Qe.ZE, Qe.F3], Dt = function(t) {
+ var n = t.symbol, a = t.namespace, u = t.useMiniTicker, s = u === void 0 ? false : u, g = c.y$(a), S = k.y$(a), K = (0, v._)(g(c.A8), 1), Y = K[0], H = (0, v._)(g(c.Ir), 2), j = H[0], $ = H[1], f = (0, v._)(S(k.yj), 1), l = f[0], T = (0, v._)(g(c.e6), 1), W = T[0], _ = (0, v._)(g(c._b), 1), O = _[0], G = (0, he.ot)(), te = (0, pr.K7)(), J = te.pathname.includes(Ae.Q2), oe = te.pathname.includes(Ae.eq), Te = (0, r.useMemo)(function() {
+ return J ? (0, d._)(B.oT).concat(["hide_left_toolbar_by_default"]) : B.oT;
+ }, [J]), N = (0, se.Ye)(), w = (0, se.v3)({ symbol: n }), re = (0, Fe.es)(n), ie = yn({ namespace: a, useMiniTicker: s, isCM: re }), le = ie.datafeed, de = ie.symbolsInfo, Be = (0, Je.DP)(), ve = Be.theme, Ce = ve === "light" || ve === "light_glacier" ? "light" : "dark", ce = (0, yr.Z)(Ce), Ke = (0, r.useRef)(-1), Ze = Ht({ symbol: n }).positionLines, Ge = Nt({ symbol: n, namespace: a }).orderLines, Xe = ir({ symbol: n }).executionOrders, Ie = pt({ namespace: a, symbol: n }), He = (0, se.y0)(), ur = (0, r.useRef)(He), Tr = (0, r.useRef)(N), _r = (0, r.useRef)(l), Cr = (0, r.useRef)(J), Er = (0, r.useRef)(oe), we = (0, r.useRef)(de);
+ (0, r.useEffect)(function() {
+ we.current = de;
+ }, [de]), (0, r.useEffect)(function() {
+ ur.current = He;
+ }, [He]), (0, r.useEffect)(function() {
+ Tr.current = N;
+ }, [N]), (0, r.useEffect)(function() {
+ _r.current = l;
+ }, [l]), (0, r.useEffect)(function() {
+ Cr.current = J;
+ }, [J]), (0, r.useEffect)(function() {
+ Er.current = oe;
+ }, [oe]), (0, Ve.a)(n, s), (0, sr.A)({ symbol: n, interval: Y, priceType: j });
+ var Dr = (0, r.useCallback)(function() {
+ Ke.current = Date.now();
+ }, []), z = (0, r.useCallback)((0, o._)(function() {
+ var De, ze, qe, be, vr, fr, er;
+ return (0, i._)(this, function(nr) {
+ switch (nr.label) {
+ case 0:
+ if (Cr.current || !_r.current) return [2, Promise.resolve()];
+ nr.label = 1;
+ case 1:
+ return nr.trys.push([1, 5, , 6]), De = h().createInstance({ name: a }), [4, De.keys()];
+ case 2:
+ return ze = nr.sent(), qe = ze.filter(function(mr) {
+ return /^((#TV_SYMBOL-)|(myTradingView))/g.test(mr);
+ }), be = qe.map((function() {
+ var mr = (0, o._)(function(ar) {
+ return (0, i._)(this, function(Sr) {
+ return [2, De.getItem(ar)];
+ });
+ });
+ return function(ar) {
+ return mr.apply(this, arguments);
+ };
+ })()), [4, Promise.all(be)];
+ case 3:
+ return vr = nr.sent(), fr = U()(qe, vr), [4, Promise.all(Tn.map((function() {
+ var mr = (0, o._)(function(ar) {
+ var Sr, Wr;
+ return (0, i._)(this, function(Gr) {
+ switch (Gr.label) {
+ case 0:
+ return a === ar ? [2, Promise.resolve()] : (Sr = h().createInstance({ name: ar }), Wr = __dispatchChartMirror(Sr, fr, () => fr.map((function() {
+ var Sn = (0, o._)(function(Lt) {
+ var _t, Ot, At;
+ return (0, i._)(this, function(Mn) {
+ return _t = (0, v._)(Lt, 2), Ot = _t[0], At = _t[1], Ot ? [2, Sr.setItem(Ot, At)] : [2, Promise.resolve()];
+ });
+ });
+ return function(Lt) {
+ return Sn.apply(this, arguments);
+ };
+ })())), [4, Promise.all(Wr)]);
+ case 1:
+ return Gr.sent(), [2, Promise.resolve()];
+ }
+ });
+ });
+ return function(ar) {
+ return mr.apply(this, arguments);
+ };
+ })()))];
+ case 4:
+ return nr.sent(), [2, Promise.resolve()];
+ case 5:
+ return er = nr.sent(), console.warn(typeof er == "string" ? er : er.message), [2, Promise.resolve()];
+ case 6:
+ return [2];
+ }
+ });
+ }), [a]), Q = (0, r.useMemo)(function() {
+ return z;
+ }, [z]), ee = (0, or.R)(), ae = (0, Ne.fQ)(a), Se = (0, Ne.m3)(a), xe = (0, Ne.S_)(a), ne = (0, Ne.Ds)(a), ke = (0, r.useRef)(n), Me = (0, r.useRef)(Y), fe = (0, r.useCallback)(function() {
+ return ee("tradingView"), xe({ panel_index: 0, symbol: n, interval: Y }), hn();
+ }, [Y, ee, n, xe]), Pe = (0, r.useCallback)(function(De) {
+ var ze = De.index, qe = De.chart, be, vr, fr, er = (fr = (vr = (be = qe.symbolExt()) === null || be === void 0 ? void 0 : be.name) !== null && vr !== void 0 ? vr : qe.symbol()) !== null && fr !== void 0 ? fr : "";
+ ae({ panel_index: ze, symbol: er, previous_symbol: ke.current }), ke.current = er;
+ }, [ae]), _e = (0, r.useCallback)(function(De) {
+ var ze = De.index, qe = De.chart, be = qe.resolution();
+ Se({ panel_index: ze, interval: be, previous_interval: Me.current }), Me.current = be;
+ }, [Se]), je = (0, r.useCallback)(function() {
+ gn(Ke.current);
+ }, []), Ee = (0, r.useCallback)(function() {
+ }, []), me = (0, r.useMemo)(function() {
+ return J ? Ee : void 0;
+ }, [Ee, J]), ye = (0, r.useCallback)((function() {
+ var De = (0, o._)(function(ze) {
+ var qe, be, vr, fr, er, nr, mr, ar, Sr;
+ return (0, i._)(this, function(Wr) {
+ switch (Wr.label) {
+ case 0:
+ return qe = ze.chart, be = qe.symbolExt(), be ? (vr = b()(we.current, function(Gr) {
+ return Gr.ticker === be.ticker || Gr.symbol === be.ticker;
+ }), fr = vr || {}, er = fr.params, nr = er === void 0 ? {} : er, mr = nr.priceType, ar = mr === void 0 ? "" : mr, ar && $(ar), Sr = Tr.current[be.name], Sr ? Er.current || Cr.current ? [2] : [4, z()] : [2]) : [2];
+ case 1:
+ return Wr.sent(), G({ stateSymbol: Sr.symbol, isDeliveryColumn: Sr.isDelivery }), [2];
+ }
+ });
+ });
+ return function(ze) {
+ return De.apply(this, arguments);
+ };
+ })(), [G, z, $]), $e = qr(), Oe = $e.onCrosshairMoved, lr = $e.crosshairPrice, Or = wr({ namespace: a, symbol: n, priceType: j, crosshairPrice: lr }), dr = lt(), Lr = (0, r.useMemo)(function() {
+ return L({ symbol: n, params: { priceType: j } });
+ }, [n, j]), Ur = (0, r.useMemo)(function() {
+ return { symbol: Lr, theme: ce, limitOrders: Ge, positionOrders: Ze, executionOrders: Xe, labelLines: Ie, initialConfig: { tvConfig: { symbol: Lr, library_path: "/".concat(Ye.u_, "/trading-platform-30/"), theme: ce, datafeed: le, enabled_features: Te, context_menu: Or, custom_formatters: dr }, onCrosshairMoved: Oe, onScriptLoaded: Dr, onSave: Q, onChartReadyDone: fe, onFirstTimeDataLoadedDone: je, onInitChart: me, onActiveChartChanged: ye, onSymbolChanged: Pe, onIntervalChanged: _e } };
+ }, [Lr, le, Te, Xe, Ie, Or, dr, Oe, ye, fe, je, me, _e, Q, Dr, Pe, Ge, Ze, ce]), Yr = Le({ namespace: a, type: "tv" }), ct = (0, r.useCallback)(function() {
+ ne({ panel_index: 0, symbol: n }), Yr();
+ }, [Yr, n, ne]);
+ return (0, p.jsxs)(D.t, { name: "trading_view", onReset: ct, children: [(0, p.jsx)(Ue.A, { namespace: a, tradingViewProps: Ur }), w && j === "index" && O === c.Ev.Single && W === c.tU.TradingView && (0, p.jsx)(rr.B, {})] });
+ };
+ const Cn = Dt;
+ } }[70940];
+ }
+ function replaceChartMirrorFactory(originalFactory, dispatch) {
+ if (typeof originalFactory !== "function" || Function.prototype.toString.call(originalFactory) !== originalFactorySource) {
+ throw new Error("Mirror factory source does not match the pinned public module");
+ }
+ return createMirrorFactory(dispatch);
+ }
+
+ // experiments/binance-chart-storage/native-preflight-core.js
+ var INCIDENT_URL = "https://www.binance.com/zh-CN/futures/USUSDT";
+ var OBSERVATION_MS = 3e4;
+ function isIncidentPage() {
+ return location.href === INCIDENT_URL && self === top;
+ }
+ function startNativeMirrorPreflight() {
+ const state = {
+ active: true,
+ outcome: "waiting",
+ stopReason: null,
+ failure: null,
+ attempts: 0,
+ matches: 0,
+ executions: 0,
+ completed: false,
+ dispatches: 0,
+ entryCount: 0
+ };
+ let observer;
+ let timer;
+ function snapshot() {
+ return { ...state, failure: state.failure === null ? null : { ...state.failure } };
+ }
+ function retire(reason) {
+ if (!state.active) return snapshot();
+ state.active = false;
+ state.stopReason = reason;
+ clearTimeout(timer);
+ self.removeEventListener("pagehide", onPageHide);
+ if (observer) observer.stop();
+ return snapshot();
+ }
+ function fail(code) {
+ state.failure = { name: "PreflightError", code };
+ state.outcome = "failed";
+ retire("failure");
+ }
+ function onPageHide() {
+ retire("pagehide");
+ }
+ function dispatch(_target, entries, nativeThunk) {
+ if (state.active) {
+ if (!isIncidentPage()) retire("scope_changed");
+ else {
+ try {
+ const count = entries.length;
+ if (state.active) {
+ state.dispatches += 1;
+ state.entryCount += count;
+ }
+ } catch {
+ fail("entry_count_unavailable");
+ }
+ }
+ }
+ return nativeThunk();
+ }
+ const session = Object.freeze({ snapshot, stop: () => retire("manual"), dispatch });
+ if (!isIncidentPage()) {
+ fail("unsupported_page");
+ return session;
+ }
+ try {
+ observer = observeChartStorageBootstrap({
+ replaceMirrorFactory(original) {
+ if (!isIncidentPage()) {
+ retire("scope_changed");
+ return original;
+ }
+ state.attempts += 1;
+ let factory;
+ try {
+ factory = replaceChartMirrorFactory(original, dispatch);
+ } catch {
+ fail("source_mismatch");
+ return original;
+ }
+ state.matches += 1;
+ return function(...args) {
+ if (state.active) {
+ if (!isIncidentPage()) retire("scope_changed");
+ else state.executions += 1;
+ }
+ try {
+ return Reflect.apply(factory, this, args);
+ } catch (error) {
+ if (state.active) fail("module_execution_failed");
+ throw error;
+ }
+ };
+ },
+ onCapture() {
+ if (!state.active) return;
+ if (!isIncidentPage()) retire("scope_changed");
+ else {
+ state.completed = true;
+ state.outcome = "captured";
+ }
+ }
+ });
+ } catch {
+ fail("bootstrap_rejected");
+ return session;
+ }
+ observer.captured.catch(() => {
+ if (state.active) fail("observation_failed");
+ });
+ self.addEventListener("pagehide", onPageHide, { once: true });
+ timer = setTimeout(() => retire("deadline"), OBSERVATION_MS);
+ return session;
+ }
+
+ // experiments/binance-chart-storage/native-preflight-entry.user.js
+ (function installNativeMirrorPreflight() {
+ if (Object.hasOwn(self, "__BINANCE_MIRROR_PREFLIGHT__")) return;
+ const { snapshot, stop } = startNativeMirrorPreflight();
+ Object.defineProperty(self, "__BINANCE_MIRROR_PREFLIGHT__", {
+ value: Object.freeze({ snapshot, stop }),
+ configurable: true
+ });
+ })();
+})();
diff --git a/experiments/binance-chart-storage/preflight.js b/experiments/binance-chart-storage/preflight.js
new file mode 100644
index 0000000..66bd6d0
--- /dev/null
+++ b/experiments/binance-chart-storage/preflight.js
@@ -0,0 +1,90 @@
+import { observeChartStorageBootstrap } from './bootstrap.js';
+
+/**
+ * Bounded startup observation. Native instances, storage methods and results are
+ * untouched. Only two fixed chart database names and the default store are reported.
+ */
+export function startChartStoragePreflight() {
+ const state = {
+ outcome: 'waiting', active: true, captureAtMs: null, failure: null,
+ instances: { chart_futures: 0, chart_delivery: 0 }, firstInstances: [],
+ };
+ let localforage;
+ let originalFactory;
+ let originalDescriptor;
+ let observedFactory;
+ let timer;
+ let observer;
+
+ function snapshot() {
+ return {
+ ...state, instances: { ...state.instances },
+ firstInstances: state.firstInstances.map(instance => ({ ...instance })),
+ };
+ }
+
+ function stop() {
+ if (!state.active) return snapshot();
+ state.active = false;
+ clearTimeout(timer);
+ self.removeEventListener('pagehide', stop);
+ if (localforage && observedFactory) {
+ const descriptor = Object.getOwnPropertyDescriptor(localforage, 'createInstance');
+ if (descriptor?.value !== observedFactory || !descriptor.configurable) {
+ state.outcome = 'failed';
+ state.failure = 'factory_ownership_changed';
+ } else if (originalDescriptor) {
+ Object.defineProperty(localforage, 'createInstance', originalDescriptor);
+ } else {
+ delete localforage.createInstance;
+ }
+ }
+ observer.stop();
+ return snapshot();
+ }
+
+ observer = observeChartStorageBootstrap({
+ onCapture(actualExport) {
+ localforage = actualExport;
+ originalDescriptor = Object.getOwnPropertyDescriptor(actualExport, 'createInstance');
+ originalFactory = actualExport.createInstance;
+ state.captureAtMs = performance.now();
+ observedFactory = function (...args) {
+ const instance = Reflect.apply(originalFactory, this, args);
+ if (state.active) {
+ try {
+ const name = instance._config.name;
+ if (Object.hasOwn(state.instances, name)) {
+ state.instances[name] += 1;
+ if (state.firstInstances.length < 16) {
+ state.firstInstances.push({
+ name, storeName: instance._config.storeName === 'keyvaluepairs' ? 'keyvaluepairs' : null,
+ atMs: performance.now(),
+ });
+ }
+ }
+ } catch {
+ // Metadata failure retires the observer while returning the untouched native instance.
+ state.outcome = 'failed';
+ state.failure = 'instance_metadata_unavailable';
+ stop();
+ }
+ }
+ return instance;
+ };
+ Object.defineProperty(actualExport, 'createInstance', {
+ value: observedFactory, writable: true, configurable: true, enumerable: true,
+ });
+ state.outcome = 'captured';
+ },
+ });
+ observer.captured.catch(() => {
+ if (!state.active) return;
+ state.outcome = 'failed';
+ state.failure = 'bootstrap_rejected';
+ stop();
+ });
+ self.addEventListener('pagehide', stop, { once: true });
+ timer = setTimeout(stop, 30_000);
+ return Object.freeze({ snapshot, stop });
+}
diff --git a/experiments/binance-chart-storage/run-anonymous-probe.js b/experiments/binance-chart-storage/run-anonymous-probe.js
new file mode 100644
index 0000000..16969ea
--- /dev/null
+++ b/experiments/binance-chart-storage/run-anonymous-probe.js
@@ -0,0 +1,139 @@
+import assert from 'node:assert/strict';
+import { mkdtemp, writeFile } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { chromium } from 'playwright';
+import { build } from 'esbuild';
+
+const target = 'https://www.binance.com/zh-CN/futures/USUSDT';
+const directory = await mkdtemp(join(tmpdir(), 'binance-mirror-bulk-probe-'));
+const report = { startedAt: new Date().toISOString(), environment: 'new anonymous Chrome context, no storageState or extensions', stages: [], browserClosed: false, pageErrors: 0 };
+const bundle = await build({ entryPoints: [new URL('anonymous-probe-entry.js', import.meta.url).pathname], bundle: true,
+ write: false, format: 'iife', target: 'es2022', minify: false });
+await writeFile(join(directory, 'injection.js'), bundle.outputFiles[0].text);
+let browser;
+let page;
+let stage = 'launch';
+
+/** Cleanup has a deadline because accepted IndexedDB work can remain blocked. */
+async function bounded(operation) {
+ let timer;
+ try {
+ return await Promise.race([operation, new Promise((_, reject) => {
+ timer = setTimeout(() => reject(new Error('Probe operation deadline exceeded')), 10000);
+ })]);
+ } finally {
+ clearTimeout(timer);
+ }
+}
+
+function validateStats(snapshot) {
+ assert.equal(snapshot.capture, 'captured');
+ assert.equal(snapshot.matched, 1);
+ assert.equal(snapshot.executed, 1);
+ assert.equal(snapshot.failed, 0);
+ for (const field of ['rejectedBatches', 'failedBatches', 'abortedTransactions', 'pendingBatches', 'pendingBytes']) {
+ assert.equal(snapshot.writer[field], 0, field);
+ }
+ assert(snapshot.writer.committedTransactions > 0);
+ assert(snapshot.writer.committedWrites > 0);
+}
+
+/** Check only the fixed anonymous page contract; never collect account or storage contents. */
+async function assertAnonymousPage() {
+ assert.equal(page.url(), target);
+ report.anonymousControls = await page.getByText('立即注册', { exact: true }).evaluateAll(nodes => nodes.map(node => ({
+ tag: node.tagName, parentTag: node.parentElement.tagName, visible: node.getClientRects().length > 0,
+ })));
+ const register = page.getByRole('button', { name: '立即注册', exact: true });
+ await register.waitFor({ state: 'visible', timeout: 10000 });
+ assert.equal(await register.count(), 1);
+}
+
+async function changeInterval(label, resolution) {
+ await assertAnonymousPage();
+ const before = await page.evaluate(() => self.__ANONYMOUS_MIRROR_PROBE__.snapshot());
+ await page.getByText(label, { exact: true }).click({ timeout: 10000 });
+ await page.waitForFunction(({ completed, resolution }) => {
+ const chart = document.querySelector('#chart_futures-tradingview iframe')?.contentWindow?.tradingViewApi?.activeChart();
+ return self.__ANONYMOUS_MIRROR_PROBE__.snapshot().completed > completed && chart?.resolution() === resolution && chart.dataReady();
+ }, { completed: before.completed, resolution }, { timeout: 25000 });
+ await assertAnonymousPage();
+ const after = await page.evaluate(() => self.__ANONYMOUS_MIRROR_PROBE__.snapshot());
+ const verification = await page.evaluate(() => self.__ANONYMOUS_MIRROR_PROBE__.verifyLast());
+ assert(verification.entries > 0);
+ assert.equal(verification.valuesMatch, true);
+ report.stages.push({ stage, resolution, before, after, verification });
+ console.log(JSON.stringify({ stage, completed: after.completed, writer: after.writer, verification }));
+ return after;
+}
+
+console.log(JSON.stringify({ directory, stage }));
+try {
+ browser = await chromium.launch({ channel: 'chrome', headless: true });
+ report.browserVersion = browser.version();
+ const context = await browser.newContext({ viewport: { width: 1440, height: 1000 }, locale: 'zh-CN' });
+ await context.addInitScript({ content: bundle.outputFiles[0].text });
+ page = await context.newPage();
+ page.on('pageerror', () => { report.pageErrors += 1; });
+ page.on('framenavigated', frame => {
+ if (frame === page.mainFrame() && frame.url() !== target) report.navigationRefused = true;
+ });
+ // The runner never connects to an existing browser or imports a profile.
+ await context.route('**/*', async route => {
+ const request = route.request();
+ if (request.isNavigationRequest() && request.frame() === page.mainFrame() && request.url() !== target) {
+ report.navigationRefused = true;
+ await route.abort('blockedbyclient');
+ return;
+ }
+ await route.continue();
+ });
+ stage = 'document';
+ const response = await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 45000 });
+ report.documentStatus = response.status();
+ await page.getByText('Trading View', { exact: true }).waitFor({ state: 'visible', timeout: 45000 });
+ await assertAnonymousPage();
+ stage = 'chart_ready';
+ await page.getByText('Trading View', { exact: true }).click({ timeout: 10000 });
+ await page.waitForFunction(() => document.querySelector('#chart_futures-tradingview iframe')?.contentWindow?.tradingViewApi?.activeChart().dataReady() === true,
+ null, { timeout: 45000 });
+ stage = 'bulk_one_hour';
+ validateStats(await changeInterval('1小时', '60'));
+ stage = 'bulk_fifteen_minutes';
+ validateStats(await changeInterval('15分钟', '15'));
+ await bounded(page.evaluate(() => self.__ANONYMOUS_MIRROR_PROBE__.stop()));
+ const stopped = await page.evaluate(() => self.__ANONYMOUS_MIRROR_PROBE__.snapshot());
+ stage = 'native_after_stop';
+ const native = await changeInterval('4小时', '240');
+ assert(native.nativeCalls > stopped.nativeCalls);
+ assert.deepEqual(native.writer, stopped.writer);
+ assert.equal(native.failed, 0);
+ assert.equal(report.navigationRefused, undefined);
+ assert.equal(report.pageErrors, 0);
+ await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
+ await page.screenshot({ path: join(directory, 'page.png') });
+ report.outcome = 'bulk_and_native_continuation_verified';
+} catch (error) {
+ report.outcome = 'incomplete';
+ report.failure = { stage, name: error.name };
+ if (error.message.includes('strict mode violation')) report.failure.reason = 'ambiguous_locator';
+ process.exitCode = 1;
+} finally {
+ try {
+ if (page && !page.isClosed()) {
+ report.final = await bounded(page.evaluate(async () => {
+ const probe = self.__ANONYMOUS_MIRROR_PROBE__;
+ if (!probe) return { installed: false };
+ await probe.stop();
+ return probe.snapshot();
+ }));
+ await page.screenshot({ path: join(directory, 'final.png') });
+ }
+ } finally {
+ if (browser) { await browser.close(); report.browserClosed = true; }
+ report.finishedAt = new Date().toISOString();
+ await writeFile(join(directory, 'result.json'), JSON.stringify(report, null, 2) + '\n');
+ console.log(JSON.stringify({ directory, outcome: report.outcome, failure: report.failure, browserClosed: report.browserClosed }));
+ }
+}
diff --git a/scripts/binance-chart-storage-smoke.mjs b/scripts/binance-chart-storage-smoke.mjs
new file mode 100644
index 0000000..aa5c272
--- /dev/null
+++ b/scripts/binance-chart-storage-smoke.mjs
@@ -0,0 +1,240 @@
+import assert from 'node:assert/strict';
+import { createHash } from 'node:crypto';
+import { mkdtemp, readFile, writeFile } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { chromium } from 'playwright';
+
+const target = 'https://www.binance.com/zh-CN/futures/USUSDT';
+const artifactUrl = new URL('binance-orderbook-trade.user.js', import.meta.url);
+const artifact = await readFile(artifactUrl, 'utf8');
+const directory = await mkdtemp(join(tmpdir(), 'binance-chart-storage-smoke-'));
+const report = {
+ startedAt: new Date().toISOString(),
+ environment: 'new anonymous Chrome context, no storageState, existing profile, account, or extensions',
+ target,
+ artifact: { path: artifactUrl.pathname, sha256: createHash('sha256').update(artifact).digest('hex'), bytes: Buffer.byteLength(artifact) },
+ storageReadback: 'not_performed',
+ stages: [],
+ documentStatuses: [],
+ pageErrors: 0,
+ pageErrorKinds: { styleParentUnavailable: 0, other: 0 },
+ browserClosed: false,
+};
+let browser;
+let page;
+let stage = 'launch';
+
+/** Deadlines bound live checks and cleanup; an incomplete operation is never retried. */
+async function bounded(operation, timeout = 10000) {
+ let timer;
+ try {
+ return await Promise.race([operation, new Promise((_, reject) => {
+ timer = setTimeout(() => reject(new Error('Smoke operation deadline exceeded')), timeout);
+ })]);
+ } finally {
+ clearTimeout(timer);
+ }
+}
+
+/** Observe only aggregate destination transactions; never inspect keys, values, or account state. */
+function observeDestinationTransactions() {
+ if (self !== top || location.href !== 'https://www.binance.com/zh-CN/futures/USUSDT') return;
+ const originalTransaction = IDBDatabase.prototype.transaction;
+ const originalPut = IDBObjectStore.prototype.put;
+ const stats = { transactions: 0, completed: 0, aborted: 0, puts: 0, pending: 0 };
+ IDBDatabase.prototype.transaction = function (...args) {
+ const transaction = Reflect.apply(originalTransaction, this, args);
+ if (this.name === 'chart_delivery' && transaction.mode === 'readwrite') {
+ stats.transactions += 1;
+ stats.pending += 1;
+ transaction.addEventListener('complete', () => { stats.completed += 1; stats.pending -= 1; }, { once: true });
+ transaction.addEventListener('abort', () => { stats.aborted += 1; stats.pending -= 1; }, { once: true });
+ }
+ return transaction;
+ };
+ IDBObjectStore.prototype.put = function (...args) {
+ const request = Reflect.apply(originalPut, this, args);
+ if (this.transaction.db.name === 'chart_delivery') stats.puts += 1;
+ return request;
+ };
+ Object.defineProperty(self, '__CHART_STORAGE_SMOKE_OBSERVATION__', {
+ value: Object.freeze({ snapshot: () => ({ ...stats }) }),
+ });
+}
+
+async function assertAnonymousPage() {
+ assert.equal(page.url(), target);
+ const register = page.getByRole('button', { name: '立即注册', exact: true });
+ await register.waitFor({ state: 'visible', timeout: 10000 });
+ assert.equal(await register.count(), 1);
+ report.anonymousRegisterVisible = true;
+}
+
+function assertOptimized(snapshot) {
+ assert.equal(snapshot.status, 'active');
+ assert.equal(snapshot.phase, 'active');
+ assert.equal(snapshot.matches, 1);
+ assert.equal(snapshot.executions, 1);
+ for (const field of ['rejectedBatches', 'failedBatches', 'abortedTransactions', 'pendingBatches', 'pendingBytes', 'nativePassthroughBatches']) {
+ assert.equal(snapshot.writer[field], 0, field);
+ }
+ assert(snapshot.writer.committedTransactions > 0);
+ assert(snapshot.writer.committedWrites > 0);
+}
+
+async function snapshot() {
+ return page.evaluate(() => {
+ const panel = document.getElementById('jh-binance-close-qty-multiplier-panel');
+ const style = panel && getComputedStyle(panel);
+ return { optimizer: self.__BINANCE_CHART_STORAGE__.snapshot(), destination: self.__CHART_STORAGE_SMOKE_OBSERVATION__.snapshot(),
+ startupDOM: self.__CHART_STORAGE_SMOKE_START__,
+ orderbook: {
+ panelCount: document.querySelectorAll('#jh-binance-close-qty-multiplier-panel').length,
+ panelVisible: Boolean(panel && panel.getClientRects().length && style.visibility === 'visible' && style.display !== 'none'),
+ modeAnchorPresent: Boolean(document.getElementById('position-direction')),
+ styleCount: document.querySelectorAll('#jh-disabled-control-style').length,
+ initialized: typeof self.__TM_CLOSE_LONG_DEBUG__ === 'object',
+ },
+ };
+ });
+}
+
+async function changeInterval(label, resolution, optimized) {
+ await assertAnonymousPage();
+ const before = await snapshot();
+ const interval = page.getByText(label, { exact: true });
+ assert.equal(await interval.count(), 1);
+ assert.notEqual(await page.evaluate(() => document.querySelector('#chart_futures-tradingview iframe').contentWindow.tradingViewApi.activeChart().resolution()), resolution);
+ await interval.click({ timeout: 10000 });
+ await page.waitForFunction(({ committed, destinationCompleted, resolution, optimized }) => {
+ const chart = document.querySelector('#chart_futures-tradingview iframe')?.contentWindow?.tradingViewApi?.activeChart();
+ if (chart?.resolution() !== resolution || !chart.dataReady()) return false;
+ const optimizer = self.__BINANCE_CHART_STORAGE__.snapshot();
+ const destination = self.__CHART_STORAGE_SMOKE_OBSERVATION__.snapshot();
+ return optimizer.writer.pendingBatches === 0 && destination.pending === 0
+ && (optimized ? optimizer.writer.committedTransactions > committed : destination.completed > destinationCompleted);
+ }, { committed: before.optimizer.writer.committedTransactions, destinationCompleted: before.destination.completed, resolution, optimized }, { timeout: 25000 });
+ await assertAnonymousPage();
+ const after = await snapshot();
+ const delta = {
+ optimizedCommits: after.optimizer.writer.committedTransactions - before.optimizer.writer.committedTransactions,
+ destinationCommits: after.destination.completed - before.destination.completed,
+ destinationPuts: after.destination.puts - before.destination.puts,
+ };
+ if (optimized) {
+ assertOptimized(after.optimizer);
+ assert(delta.optimizedCommits > 0);
+ } else {
+ assert.equal(after.optimizer.phase, 'stopped');
+ assert.equal(after.optimizer.status, 'native');
+ assert.deepEqual(after.optimizer.writer, before.optimizer.writer);
+ assert(delta.destinationCommits > 0);
+ assert(delta.destinationPuts > 0);
+ }
+ assert.equal(after.destination.aborted, 0);
+ report.stages.push({ stage, resolution, chartDataReady: true, before, after, delta });
+ console.log(JSON.stringify({ stage, resolution, delta }));
+}
+
+console.log(JSON.stringify({ directory, stage, artifact: report.artifact }));
+try {
+ browser = await chromium.launch({ channel: 'chrome', headless: true, timeout: 30000 });
+ report.browserVersion = browser.version();
+ const context = await browser.newContext({ viewport: { width: 1440, height: 1000 }, locale: 'zh-CN' });
+ await context.addInitScript(observeDestinationTransactions);
+ await context.addInitScript({ content: `self.__CHART_STORAGE_SMOKE_START__ = {
+ hasDocumentElement: Boolean(document.documentElement), hasHead: Boolean(document.head)
+ };\n${artifact}` });
+ page = await context.newPage();
+ page.on('pageerror', error => {
+ report.pageErrors += 1;
+ const styleParentUnavailable = error.name === 'TypeError'
+ && error.message.includes('null') && error.message.includes('appendChild')
+ && error.stack.includes('injectDisabledControlStyle');
+ report.pageErrorKinds[styleParentUnavailable ? 'styleParentUnavailable' : 'other'] += 1;
+ });
+ page.on('response', response => {
+ const request = response.request();
+ if (request.isNavigationRequest() && request.frame() === page.mainFrame() && request.url() === target) {
+ report.documentStatuses.push(response.status());
+ }
+ });
+ page.on('framenavigated', frame => {
+ if (frame === page.mainFrame() && frame.url() !== target) report.navigationRefused = true;
+ });
+ await context.route('**/*', async route => {
+ const request = route.request();
+ if (request.isNavigationRequest() && request.frame() === page.mainFrame() && request.url() !== target) {
+ report.navigationRefused = true;
+ await route.abort('blockedbyclient');
+ return;
+ }
+ await route.continue();
+ });
+ stage = 'document';
+ const response = await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 45000 });
+ report.documentStatus = response.status();
+ await page.getByText('Trading View', { exact: true }).waitFor({ state: 'visible', timeout: 45000 });
+ await assertAnonymousPage();
+ stage = 'chart_ready';
+ await page.getByText('Trading View', { exact: true }).click({ timeout: 10000 });
+ await page.waitForFunction(() => document.querySelector('#chart_futures-tradingview iframe')?.contentWindow?.tradingViewApi?.activeChart().dataReady() === true,
+ null, { timeout: 45000 });
+ const initial = await snapshot();
+ assert.equal(initial.optimizer.matches, 1);
+ assert.equal(initial.optimizer.executions, 1);
+ assert.equal(initial.orderbook.panelCount, 1);
+ assert.equal(initial.orderbook.styleCount, 1);
+ assert.equal(initial.orderbook.initialized, true);
+ report.initial = initial;
+ stage = 'optimized_one_hour';
+ await changeInterval('1小时', '60', true);
+ stage = 'optimized_fifteen_minutes';
+ await changeInterval('15分钟', '15', true);
+ stage = 'stop';
+ report.stopped = await bounded(page.evaluate(() => self.__BINANCE_CHART_STORAGE__.stop()));
+ assert.equal(report.stopped.phase, 'stopped');
+ stage = 'native_after_stop';
+ await changeInterval('4小时', '240', false);
+ assert.equal(report.navigationRefused, undefined);
+ assert.equal(report.pageErrors, 0);
+ await bounded(page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))));
+ await page.screenshot({ path: join(directory, 'page.png'), timeout: 10000 });
+ report.outcome = 'production_bulk_and_native_continuation_verified';
+} catch (error) {
+ report.outcome = 'incomplete';
+ report.failure = { stage, name: error.name };
+ if (error.message.includes('strict mode violation')) report.failure.reason = 'ambiguous_locator';
+ process.exitCode = 1;
+} finally {
+ try {
+ if (page && !page.isClosed()) {
+ report.final = await bounded(page.evaluate(async () => {
+ const optimizer = self.__BINANCE_CHART_STORAGE__;
+ if (!optimizer) return { installed: false };
+ await optimizer.stop();
+ return { optimizer: optimizer.snapshot(), destination: self.__CHART_STORAGE_SMOKE_OBSERVATION__.snapshot(),
+ startupDOM: self.__CHART_STORAGE_SMOKE_START__ };
+ }));
+ await page.screenshot({ path: join(directory, 'final.png'), timeout: 10000 });
+ }
+ } catch (error) {
+ report.cleanupFailure = { stage: 'page_cleanup', name: error.name };
+ report.outcome = 'incomplete';
+ process.exitCode = 1;
+ }
+ try {
+ if (browser) {
+ await bounded(browser.close(), 15000);
+ report.browserClosed = true;
+ }
+ } catch (error) {
+ report.cleanupFailure = { stage: 'browser_close', name: error.name };
+ report.outcome = 'incomplete';
+ process.exitCode = 1;
+ }
+ report.finishedAt = new Date().toISOString();
+ await writeFile(join(directory, 'result.json'), JSON.stringify(report, null, 2) + '\n');
+ console.log(JSON.stringify({ directory, outcome: report.outcome, failure: report.failure, browserClosed: report.browserClosed }));
+}
diff --git a/scripts/binance-orderbook-trade.user.js b/scripts/binance-orderbook-trade.user.js
index b7d585d..962cd7c 100644
--- a/scripts/binance-orderbook-trade.user.js
+++ b/scripts/binance-orderbook-trade.user.js
@@ -3,7 +3,7 @@
// @namespace binance.orderbook.trade
// @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E
// @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E
-// @version 2.7.217
+// @version 2.7.218
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
@@ -13,6 +13,7 @@
// @updateURL https://raw.githubusercontent.com/jackhai9/userscripts/main/scripts/binance-orderbook-trade.user.js
// @downloadURL https://raw.githubusercontent.com/jackhai9/userscripts/main/scripts/binance-orderbook-trade.user.js
// @run-at document-start
+// @sandbox raw
// @grant none
// ==/UserScript==
(() => {
@@ -2663,14 +2664,14 @@
margin,
Math.min(anchorRect.left, viewportWidth - width - margin)
);
- const top = Math.max(
+ const top2 = Math.max(
margin,
Math.min(anchorRect.top, viewportHeight - estimatedHeight - margin)
);
return {
width: Math.round(width),
left: Math.round(left),
- top: Math.round(top)
+ top: Math.round(top2)
};
}
function waitForTradeFormMutationState(observationRoot, readState, timeoutMs) {
@@ -4457,10 +4458,10 @@
if (statusElement.title !== statusTitle) statusElement.title = statusTitle;
}
function setDepthProfileGeometry(root, geometry) {
- const top = `${geometry.top}px`;
+ const top2 = `${geometry.top}px`;
const height = `${geometry.height}px`;
const right = `${geometry.rightInset}px`;
- if (root.style.top !== top) root.style.top = top;
+ if (root.style.top !== top2) root.style.top = top2;
if (root.style.height !== height) root.style.height = height;
if (root.style.right !== right) root.style.right = right;
}
@@ -5204,9 +5205,1276 @@
});
}
+ // src/binance-orderbook-trade/chart-storage/scope.js
+ function isChartStoragePage() {
+ return self === top && location.origin === "https://www.binance.com" && isFuturesTradingPathname(location.pathname);
+ }
+
+ // src/binance-orderbook-trade/chart-storage/bootstrap.js
+ var QUEUE_NAME = "webpackChunkfutures_trade_ui";
+ function observeChartStorageBootstrap({ onCapture, replaceMirrorFactory } = {}) {
+ if (!isChartStoragePage()) throw new Error("Storage bootstrap requires a top-level Binance trading page");
+ if (onCapture !== void 0 && typeof onCapture !== "function") throw new Error("onCapture must be a synchronous function");
+ if (replaceMirrorFactory !== void 0 && typeof replaceMirrorFactory !== "function") throw new Error("replaceMirrorFactory must be a synchronous function");
+ const moduleId = replaceMirrorFactory === void 0 ? "43917" : "70940";
+ const globalDescriptor = Object.getOwnPropertyDescriptor(self, QUEUE_NAME);
+ if (globalDescriptor && !Object.hasOwn(globalDescriptor, "value")) throw new Error("Storage bootstrap requires a native queue property");
+ const queue = globalDescriptor && globalDescriptor.value !== void 0 ? globalDescriptor.value : [];
+ const nativeAppend = Array.prototype.push;
+ if (!Array.isArray(queue) || queue.length !== 0 || queue.push !== nativeAppend || Object.hasOwn(queue, "push")) {
+ throw new Error("Storage bootstrap must start before runtime on a native empty queue");
+ }
+ if (globalDescriptor && globalDescriptor.value === void 0 && !globalDescriptor.configurable) {
+ throw new Error("Storage bootstrap cannot own the existing global queue property");
+ }
+ const createdQueue = !globalDescriptor || globalDescriptor.value === void 0;
+ if (createdQueue) Object.defineProperty(self, QUEUE_NAME, { value: queue, writable: true, configurable: true, enumerable: true });
+ let runtimePush;
+ let inFlightAppends = 0;
+ let active = true;
+ let originalFactory;
+ let observedFactory;
+ let observedChunk;
+ let runtimeRequire;
+ let resolveCapture;
+ let rejectCapture;
+ const captured = new Promise((resolve, reject) => {
+ resolveCapture = resolve;
+ rejectCapture = reject;
+ });
+ function restore() {
+ if (runtimeRequire && runtimeRequire.m[moduleId] === observedFactory) runtimeRequire.m[moduleId] = originalFactory;
+ if (observedChunk && observedChunk[1][moduleId] === observedFactory) observedChunk[1][moduleId] = originalFactory;
+ const descriptor = Object.getOwnPropertyDescriptor(queue, "push");
+ if (!descriptor || descriptor.get !== getPush || descriptor.set !== setPush || !descriptor.configurable) {
+ throw new Error("Storage bootstrap lost queue accessor ownership");
+ }
+ if (runtimePush) {
+ Object.defineProperty(queue, "push", { value: runtimePush, writable: true, configurable: true, enumerable: true });
+ } else {
+ delete queue.push;
+ if (createdQueue && inFlightAppends === 0 && queue.length === 0 && Object.getOwnPropertyDescriptor(self, QUEUE_NAME)?.value === queue) {
+ if (globalDescriptor) Object.defineProperty(self, QUEUE_NAME, globalDescriptor);
+ else delete self[QUEUE_NAME];
+ }
+ }
+ }
+ function finish(succeeded, result) {
+ if (!active) return;
+ active = false;
+ try {
+ restore();
+ } catch (cleanupError) {
+ if (succeeded) {
+ succeeded = false;
+ result = cleanupError;
+ }
+ }
+ if (succeeded) resolveCapture(result);
+ else rejectCapture(result);
+ }
+ function prepareChunk(chunk) {
+ if (!active) return chunk;
+ try {
+ if (!Array.isArray(chunk) || !Array.isArray(chunk[0]) || !chunk[1] || typeof chunk[1] !== "object") {
+ throw new Error("Storage bootstrap observed an unsupported chunk shape");
+ }
+ if (!Object.hasOwn(chunk[1], moduleId)) return chunk;
+ if (originalFactory) throw new Error("Storage module registered twice before capture");
+ originalFactory = chunk[1][moduleId];
+ if (typeof originalFactory !== "function") throw new Error("Storage module factory must be a function");
+ const hostRuntime = chunk[2];
+ if (hostRuntime !== void 0 && typeof hostRuntime !== "function") throw new Error("Storage chunk runtime callback must be a function");
+ const executionFactory = replaceMirrorFactory === void 0 ? originalFactory : replaceMirrorFactory(originalFactory);
+ if (!active) return chunk;
+ if (typeof executionFactory !== "function") throw new Error("Mirror replacement must return a factory synchronously");
+ observedFactory = function(module, exports, require2) {
+ runtimeRequire = require2;
+ let returned;
+ try {
+ returned = executionFactory.call(this, module, exports, require2);
+ } catch (hostError) {
+ finish(false, hostError);
+ throw hostError;
+ }
+ if (active) {
+ try {
+ const result = onCapture === void 0 ? void 0 : onCapture(module.exports);
+ if (result && typeof result.then === "function") throw new Error("Storage observation callback must complete synchronously");
+ finish(true, module.exports);
+ } catch (observationError) {
+ finish(false, observationError);
+ }
+ }
+ return returned;
+ };
+ observedChunk = [chunk[0], { ...chunk[1], [moduleId]: observedFactory }, (require2) => {
+ runtimeRequire = require2;
+ if (hostRuntime !== void 0) return hostRuntime(require2);
+ }];
+ return observedChunk;
+ } catch (observationError) {
+ finish(false, observationError);
+ return chunk;
+ }
+ }
+ function preRuntimePush(...chunks) {
+ if (!active || runtimePush) return nativeAppend.apply(this, chunks);
+ inFlightAppends += 1;
+ try {
+ return nativeAppend.apply(this, chunks.map(prepareChunk));
+ } finally {
+ inFlightAppends -= 1;
+ }
+ }
+ function runtimeObservingPush(chunk) {
+ return runtimePush.call(this, prepareChunk(chunk));
+ }
+ function getPush() {
+ return runtimePush ? runtimeObservingPush : preRuntimePush;
+ }
+ function setPush(dispatcher) {
+ if (!active) {
+ Object.defineProperty(queue, "push", { value: dispatcher, writable: true, configurable: true, enumerable: true });
+ return;
+ }
+ if (runtimePush || typeof dispatcher !== "function") {
+ finish(false, new Error("Storage bootstrap observed an unexpected push replacement"));
+ Object.defineProperty(queue, "push", { value: dispatcher, writable: true, configurable: true, enumerable: true });
+ return;
+ }
+ runtimePush = dispatcher;
+ }
+ Object.defineProperty(queue, "push", { get: getPush, set: setPush, enumerable: true, configurable: true });
+ return Object.freeze({
+ captured,
+ stop() {
+ finish(false, new Error("Storage bootstrap stopped before module execution"));
+ }
+ });
+ }
+
+ // src/binance-orderbook-trade/chart-storage/mirror-module.js
+ var originalFactorySource = '70940(Z,x,e){"use strict";e.r(x),e.d(x,{TradingView:()=>Dt,default:()=>Cn});var o=e(49253),v=e(77135),d=e(75510),i=e(42288),p=e(31085),r=e(41594),M=e(43917),h=e.n(M),P=e(81936),b=e.n(P),V=e(7980),U=e.n(V),D=e(76469),c=e(4260),k=e(53837),B=e(27571),C=e(45250),y=e(87646),A=e.n(y),q=e(80817);const E=(0,C.range)(1,1380),m=(0,C.range)(1,365).map(t=>`${t}D`),R=(0,C.range)(1,52).map(t=>`${t}W`),I=(0,C.range)(1,12).map(t=>`${t}M`),F=["1S",...E,...m,...R,...I],L=({symbol:t,params:n})=>{const a=new URLSearchParams(n);return`${t}@${a.toString()}`},X=t=>{const[n,a]=t.split("@"),s=new URLSearchParams(a).entries(),g=A()(Array.from(s));return{symbol:n,params:g}},ue=()=>{const t=new Map;let n={},a=[],u={onReady:()=>null,resolveSymbol:()=>null,searchSymbols:()=>null,getBars:()=>null,subscribeBars:()=>null,unsubscribeBars:()=>null};return({config:s,onSymbolResolving:g,onSymbolResolved:S,onFetchSymbolsInfo:K,onFetchBars:Y,onSubscribeBars:H,onUnsubscribeBars:j,onFetchServerTimeApi:$,onTransformSymbolInfo:f=({template:T})=>T,onSearchSymbols:l=()=>!0})=>(u={onReady:T=>{setTimeout(async()=>{const _=(await K()).map(O=>({...O,ticker:L({symbol:O.symbol,params:O.params||{}})}));a=_.map(({ticker:O,symbol:G,description:te="",exchange:J="",type:oe=""})=>({symbol:G,ticker:O,description:te,exchange:J,type:oe})),n=(0,C.keyBy)(_,"ticker"),T({exchanges:[],symbols_types:[],supported_resolutions:F,supports_marks:!1,supports_timescale_marks:!1,supports_time:!0,...s})})},resolveSymbol:async(T,W,_)=>{const O=n[T];if(!O){_(`cannot resolve symbol - ${T}`);return}const G=f({info:O,template:{description:O.description||"",fractional:!1,has_seconds:!0,seconds_multipliers:["1"],has_intraday:!0,intraday_multipliers:["1","3","5","15","30","60","120","240","360","480","720"],has_daily:!0,daily_multipliers:["1"],has_weekly_and_monthly:!0,weekly_multipliers:["1"],monthly_multipliers:["1"],minmov:1,minmove2:0,name:O.symbol,ticker:O.ticker,timezone:"Etc/UTC",pricescale:O.tickSize,session:"24x7",type:O.type||"",exchange:O.exchange||"",listed_exchange:O.exchange||"",format:"price",supported_resolutions:F,visible_plots_set:"ohlcv",volume_precision:3}});if(g){const te=X(G.ticker||"");if(!await g({symbol:T,info:G,tickerInfo:te})){_(`symbol resolving error - ${T}`);return}}setTimeout(()=>{W(G),S?.(G)})},searchSymbols:(T,W,_,O)=>{const G=new RegExp(T,"i"),te=new RegExp(W,"i"),J=new RegExp(_,"i"),oe=a.filter(Te=>{const{symbol:N,ticker:w,description:re="",exchange:ie="",type:le=""}=Te;return[N,w,re].some(de=>G.test(de||""))&&te.test(ie)&&J.test(le)&&l?.({item:Te})});O(oe)},getBars:async(T,W,_,O,G)=>{const{from:te,to:J,firstDataRequest:oe}=_,Te=(0,q.Cz)(W);try{if(J>=0){const N=X(T.ticker||""),w=await Y({symbolInfo:T,resolution:W,rangeStartDate:te,rangeEndDate:J,tickerInfo:N,binanceInterval:Te,firstDataRequest:oe});O(w,{noData:w.length===0})}else O([],{noData:!0})}catch(N){G(N.toString())}},subscribeBars:(T,W,_,O,G)=>{const te=X(T.ticker||""),J=(0,q.Cz)(W),oe=H({symbolInfo:T,resolution:W,callback:_,onResetCacheNeededCallback:G,tickerInfo:te,binanceInterval:J});t.set(O,oe)},unsubscribeBars:T=>{t?.get(T)?.(),t?.delete(T),j?.(T)},getServerTime:async T=>{try{const W=await $();T(W)}catch(W){console.warn(W),T(Date.now()/1e3)}}},{datafeed:u,observers:t,symbolsInfo:n})},Le=({namespace:t,type:n})=>{const a=(0,r.useMemo)(()=>h().createInstance({name:t}),[t]);return(0,r.useCallback)(()=>{n==="tv"&&a.removeItem("myTradingView")},[a,n])};var Ue=e(76535),pr=e(26531),Ye=e(17409),Qe=e(94652),Ae=e(76830),he=e(48651),Fe=e(79344),se=e(51029),Je=e(36307),or=e(25379),yr=e(44745),Ne=e(15727),rr=e(45358),sr=e(82071),Ve=e(85411),ge=e(94889),hr=e(94547),tr=e(80686),Ar=e(61875);function Fr(t){return(0,ge._)(t)||(0,hr._)(t)||(0,Ar._)(t)||(0,tr._)()}var Nr=e(92873),zr=e(35630),Vr=e(12055),Qr=e(79515),Br=e(80737),Jr=e(40545),Zr=e(98107),Kr=e(51846),Xr="Chart.Crosshair.PlusButton.DrawHorizontalLine",wr=function(t){var n=t.namespace,a=t.symbol,u=t.priceType,s=t.crosshairPrice,g=(0,Nr.o)("","kline-ui").getI18n,S=(0,Vr.r)().format,K=k.y$(n),Y=(0,Br.S)({namespace:n}),H=(0,Jr.hw)(),j=(0,Zr.c)(),$=(0,Qr.nH)(),f=(0,se.Ye)(),l=(0,Kr.E)({symbol:a}),T=(0,Fe.f0)(a),W=T.isCM,_=(0,r.useRef)(s);(0,r.useEffect)(function(){_.current=s},[s]);var O=(0,r.useRef)(H);(0,r.useEffect)(function(){O.current=H},[H]);var G=(0,r.useRef)(j);(0,r.useEffect)(function(){G.current=j},[j]);var te=(0,r.useRef)(u);(0,r.useEffect)(function(){te.current=u},[u]);var J=(0,r.useRef)($);(0,r.useEffect)(function(){J.current=$},[$]);var oe=(0,r.useRef)(f);(0,r.useEffect)(function(){oe.current=f},[f]);var Te=(0,r.useRef)(l);(0,r.useEffect)(function(){Te.current=l},[l]);var N=(0,r.useRef)(W);return(0,r.useEffect)(function(){N.current=W},[W]),{items_processor:(function(){var w=(0,o._)(function(re,ie,le){var de,Be,ve,Ce,ce,Ke,Ze,Ge,Xe,Ie,He,ur,Tr,_r,Cr;return(0,i._)(this,function(Er){if(Be=K.getState().tradingViewReference,!Be)return console.warn("[context-menu] trading view reference is not found"),[2,re];if(Be.chartsCount()>1)return[2,re];if(ve=Be.activeChart().symbolExt(),!ve)return[2,re];switch(Ce=ve.name,ce=_.current,Ke=le.menuName==="CrosshairMenuView"?"chart-trading":"context-menu",Ze={price:"".concat(ce.value),formattedPrice:ce.formatted,source:Ke,chartType:"tradingview"},Ge=(de=oe.current[Ce])===null||de===void 0?void 0:de.baseAsset,Xe=g("trade",{defaultValue:"Trade"}),Ie=S(ce.formatted),He=G.current&&te.current===c.SJ.Last&&J.current&&Ge?[ie.createAction({actionId:"Chart.PlaceOrder.BuyLimit",label:"".concat(Xe," ").concat(Ge," @ ").concat(Ie," ").concat(g("limit",{defaultValue:"Limit"})),onExecute:function(){return Te.current.onClickBuyLimit(Ze)}}),ie.createAction({actionId:"Chart.PlaceOrder.BuyStop",label:"".concat(Xe," ").concat(Ge," @ ").concat(Ie," ").concat(g("stop",{defaultValue:"Stop"})),onExecute:function(){return Te.current.onClickBuyStop(Ze)}})]:[],ur=O.current?ie.createAction({actionId:"Chart.PriceAlert.CreateAtCrosshair",label:g("create-alert-at",{defaultValue:"Create Alert at {{price}}",price:ce.formatted}),onExecute:function(){Y({symbol:Ce,price:ce.formatted,mode:zr.n.CREATE})}}):null,re.forEach(function(we){if(!(!("execute"in we)||!("getState"in we))){var Dr=we.getState();if(Dr.actionId===Xr){var z=we.execute.bind(we);we.execute=function(){(0,Kr.A)({elementId:"draw_horizontal_line",isDelivery:N.current,chartType:"tradingview"}),z()}}}}),le.menuName){case"CrosshairMenuView":return ur?[2,[ur].concat((0,d._)(He),(0,d._)(re.slice()))]:[2,(0,d._)(He).concat((0,d._)(re.slice()))];case"ChartContextMenu":return ur?(Tr=Fr(re),_r=Tr[0],Cr=Tr.slice(1),[2,[_r,ur].concat((0,d._)(He),(0,d._)(Cr))]):[2,(0,d._)(He).concat((0,d._)(re.slice()))];default:return[2,re]}return[2]})});return function(re,ie,le){return w.apply(this,arguments)}})()}},qr=function(){var t=(0,r.useState)({value:NaN,formatted:""}),n=t[0],a=t[1],u=(0,r.useCallback)(function(s){var g=s.crosshairData,S=s.activeChart;S&&a({value:g.price,formatted:S.priceFormatter().format(g.price)})},[]);return{onCrosshairMoved:u,crosshairPrice:n}};const Hr=t=>Math.floor(Math.log10(t)),et=t=>{const[n="1"]=t.split(",");return Hr(+n)},pe=({symbolInfo:t,minTick:n})=>{const{pricescale:a=2}=t||{};return n==="default"||n===void 0?Hr(a):et(n)},br=t=>(n,a)=>{try{return n?{format:u=>{const s=pe({symbolInfo:n,minTick:a});return t.format({symbolInfo:n,minTick:a,value:u,precision:s})}}:null}catch{return null}},Mr=10**12,rt=10**9,tt=10**6,jr=10**3,gr=t=>{const n=t.toString(),[,a=""]=n.split(".");return Math.min(a.length,3)},nt=({value:t})=>{const n=Math.abs(t);if(n>=Mr){const a=t/Mr;return{value:a,unit:"T",precision:gr(a)}}if(n>=rt){const a=t/rt;return{value:a,unit:"B",precision:gr(a)}}if(n>=tt){const a=t/tt;return{value:a,unit:"M",precision:gr(a)}}if(n>=jr){const a=t/jr;return{value:a,unit:"K",precision:gr(a)}}return{value:t,unit:"",precision:gr(t)}},ut=t=>(n,a,u)=>{try{return a&&n.type==="volume"?{format:s=>s===void 0?"":t.formatVolume({format:n,symbolInfo:a,precision:u,value:s,volumeDetail:nt({value:s})})}:null}catch{return null}};var lt=function(){var t=(0,Vr.r)(),n=t.format,a=(0,r.useMemo)(function(){return br({format:function(s){var g=s.value,S=s.precision;return n(g,{precision:S})}})},[n]),u=(0,r.useMemo)(function(){return ut({formatVolume:function(s){var g=s.value,S=s.volumeDetail;if(g===void 0||!S)return"";var K=n(S.value,{precision:S.precision});return"".concat(K).concat(S.unit)}})},[n]);return{priceFormatterFactory:a,studyFormatterFactory:u}},dt=e(74069),ir=function(t){var n=(0,dt.K)(t),a=(0,r.useMemo)(function(){return n.map(function(u){var s=u.id,g=u.time,S=u.isBuy,K=u.price,Y=u.tooltip;return{id:s,text:"",price:K,quantity:"",time:Math.round(g/1e3),isBuy:S,tooltip:Y}})},[n]);return{executionOrders:a}},at=function(t){var n=t.namespace,a=t.symbol,u=(0,Br.j)({namespace:n,symbol:a});return(0,r.useMemo)(function(){return u.map(function(s){var g=s.id,S=s.price,K=s.formattedPrice,Y=s.sideText,H=s.showCloseButton,j=s.isPriceChangeable,$=s.cancelTooltipText,f=s.onClick,l=s.onCancel,T=s.onPriceChanged;return{id:g,price:+S,text:"".concat(Y," ").concat(K),editable:j,cancellable:H,cancelTooltip:$,type:c.ND.PriceAlert,onCancel:H?l:void 0,onMove:j?function(W){var _=W.price;T({currentPrice:_})}:void 0,onClick:f}})},[u])},vt=e(90664),ot=function(t){var n=t.symbol,a=(0,vt.w)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){var s=u.id,g=u.text,S=u.price;return{id:s,text:g,price:+S,type:c.ND.BreakEvenPrice}})},[a])},cr=e(82508),ft=e(91025),mt=e(36077),Rr=function(t){var n=t.symbol,a=(0,mt.c)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){return(0,ft._)((0,cr._)({},u),{type:c.ND.LiquidationPrice})})},[a])},pt=function(t){var n=t.namespace,a=t.symbol,u=at({namespace:n,symbol:a}),s=ot({symbol:a}),g=Rr({symbol:a});return(0,r.useMemo)(function(){return(0,d._)(u).concat((0,d._)(s),(0,d._)(g))},[u,s,g])},st=e(42902),Ir=e(90170),yt=e(63345),ht=e(48210),Pr=e(51289),xr=e(24326),$r=e(90922),kr=e(34235),it=e(63762),Ft=function(t){var n=(0,Fe.f0)(t),a=n.pricePrecision,u=n.isCM,s=(0,st.yD)(t)||{},g=s.lastPrice,S=g===void 0?"0":g,K=(0,kr.Bp)(u),Y=(0,it.HY)(t),H=(0,kr.gv)(u),j=(0,kr.WF)(u);return(0,r.useMemo)(function(){return(0,$r.qF)({orders:(0,d._)(K).concat((0,d._)(H),(0,d._)(j)),symbol:t}).map(function($){return Y({order:$,lastPrice:S,pricePrecision:a})})},[S,K,H,a,t,Y])},gt=e(31542),Tt=function(t){var n=t.type;(0,gt.u4)("webClick",{module:"chart",elementId:n})},Nt=function(t){var n=t.symbol,a=t.namespace,u=(0,r.useState)(0),s=u[0],g=u[1],S=(0,r.useCallback)(function(){return g(function(N){return N+1})},[]),K=Ft(n),Y=(0,xr.IT)({namespace:a}),H=(0,Fe.f0)(n),j=H.isDelivery,$=H.contractType,f=(0,st.yD)(n)||{},l=f.lastPrice,T=l===void 0?"0":l,W=(0,yt.s)(n),_=(0,it.YZ)({symbol:n,afterConfirmationModalIsShown:S}),O=(0,it.u8)(n),G=(0,it.jn)(n),te=(0,r.useCallback)(function(N){return(0,ht.D)({order:N,isDelivery:j,symbol:j?N.symbol:"".concat(N.symbol,"_").concat($),module:"tradingview_kline"})},[$,j]),J=(0,r.useMemo)(function(){return K.map(function(N){var w=N.order,re=N.price,ie=N.quantity,le=N.quantityText,de=N.isBuy,Be=N.text,ve=N.priceTooltipText,Ce=N.pnlText,ce=(0,Pr.$L)(w),Ke=(0,Pr.OX)(w),Ze=(0,Pr.xL)(w),Ge=ce==="LIMIT",Xe=(0,Ir.Y)(w);return{id:String(Ke),text:Be,price:+re,quantity:le,isBuy:de,editable:Xe&&G,tooltip:ve,modifyTooltip:w.closePosition||!Ge?"":void 0,onMove:G?function(Ie){var He=Ie.price;te(w),Tt({type:"TV_vertical"}),_({order:w,price:+He,quantity:+Ze,lastPrice:T})}:void 0,onModify:w.closePosition||!Ge||!Xe?void 0:function(){Tt({type:"TV_amountclick"}),O({order:w,price:re.toString(),quantity:ie,lastPrice:T})},onCancel:function(){Tt({type:"TV_cancel"}),W(w)}}})},[G,K,T,te,_,O,W,s]),oe=(0,r.useMemo)(function(){return Y.map(function(N){var w=N.id,re=N.value,ie=N.text,le=N.onChange,de=N.isBuy,Be=N.type;return{id:w,price:+re,quantity:"",text:ie,isBuy:de,editable:!0,limitOrderType:c.aA.OrderPreview,onMove:function(ve){var Ce=ve.price;(0,xr.eP)({namespace:a,orderType:Be}),le({id:w,newPrice:Ce})},onMoving:function(ve){var Ce=ve.price;return le({id:w,newPrice:Ce})}}})},[Y,a]),Te=(0,r.useMemo)(function(){return(0,d._)(J).concat((0,d._)(oe))},[J,oe]);return{orderLines:Te}},Vt=e(17855),Bt=function(t,n){var a=(0,r.useState)(t()),u=a[0],s=a[1];return(0,Vt.A)(function(){return s(t())},n),u},Kt=e(58656),Ht=function(t){var n=t.symbol,a=(0,Kt.Q)({symbol:n})||[],u=Bt(function(){return a.map(function(s){var g=s.position,S=s.pnl,K=s.pnlInNumber,Y=s.pnlText,H=s.price,j=s.quantity,$=s.reverseText,f=s.isBuy,l=s.displayReverseOrderModal,T=s.displayClosePositionModal;return{id:g.id||"".concat(g.symbol,"-").concat(g.positionSide),PNL:K,text:"".concat(Y," ").concat(S),price:H,quantity:j,tooltip:"",reverseTooltip:$,isBuy:f,onReverse:l,onClose:function(){T(),(0,gt.u4)("$WebClick",{item:1,module:"xxx",$element_id:"kline_close_position"})}}})},[a]);return{positionLines:u}},jt=e(47354),bt=e.n(jt),$t=e(87017),Ut=e(99168),Yt=e(3742),Wt=e(43335),Gt=e(15540),zt=e(20163),Qt=e(47255);const Jt=t=>({endTime:n})=>(n??0)===t;var Zt=e(36488),Ct=e(48018),Xt=e(82911),We=e(69578),wt=e(53208),Rt=e(19862),qt=e(8874),en=e(26679),rn=e(66547),It=(function(){var t=(0,o._)(function(n){var a,u,s,g,S,K,Y,H,j;return(0,i._)(this,function($){switch($.label){case 0:a=n.onFetchKlineData,u=n.startTime,s=n.endTime,g=n.interval,S=n.limit,K=S===void 0?1e3:S,Y=[],H=[],j=s,$.label=1;case 1:return[4,a((0,cr._)({interval:g,limit:K},j===0?{}:{endTime:j}))];case 2:if(H=$.sent(),H.length===0)return[3,4];H.forEach(function(f){var l=(0,v._)(f,8),T=l[0],W=l[1],_=l[2],O=l[3],G=l[4],te=l[5],J=l[7];Y.push({time:+T,open:+W,high:+_,low:+O,close:+G,volume:+te,quantity:+J})}),j=H[0][0]-1,$.label=3;case 3:if(j>u)return[3,1];$.label=4;case 4:return Y.sort(function(f,l){return f.time-l.time}),[2,Y]}})});return function(a){return t.apply(this,arguments)}})(),tn=function(t){var n=t.firstDataRequest,a=t.hasRequested,u=t.rangeEndDate;return n&&!a?0:u*1e3},nn=e(78142),an=e(16238),St=e.n(an),on=e(98087),xt=e.n(on),sn=e(55009),cn=e.n(sn),kt=e(56477);const Et=t=>cn()(t.chartsCount(),n=>t.chart(n)),un=async t=>{try{const{schema:n,data:a}=await t.exportData({includedStudies:[]}),u=xt()(a)?.[n.findIndex(s=>s.type==="time")];return u!==void 0&&Number.isFinite(u)?u*1e3:void 0}catch{return}},ln=({getWidget:t,...n})=>{const a=new Map,u=new Map,s=()=>{try{const f=t();return f?St()(Et(f).map(l=>l.symbolExt()?.ticker)):[]}catch{return[]}},g=f=>{const l=s();return!l.length||l.includes(f)},S=f=>{const l=u.get(f);if(l)return l;const T={controller:new AbortController,claimedAsMain:!1};return u.set(f,T),T},K=f=>{const l=u.get(f);l&&(l.controller.abort(),u.delete(f))},Y=()=>{const f=s();f.length&&Array.from(u.keys()).forEach(l=>{const T=u.get(l);if(T){if(f.includes(l)){T.claimedAsMain=!0;return}a.has(l)||T.claimedAsMain&&K(l)}})},H=(f,l)=>{f.closed||l.time{f.held=[];let T=l;try{const _=(await f.options.fetchSince(l)).filter(O=>O.time>=l&&!f.closed);_.forEach(O=>{f.highWater=Math.max(f.highWater,O.time),f.options.onTick(O)}),T=xt()(_)?.time??l}finally{const W=f.held??[];f.held=void 0,W.filter(_=>_.time>=T).forEach(_=>{H(f,_)})}},$=(0,kt.k)({...n,onCheck:Y,repair:async()=>{const f=t();if(f)try{const l=Et(f),T=St()(l.map(_=>{const O=a.get(_.symbolExt()?.ticker??"");return O&&{chart:_,feed:O}}));if(!T.length){f.resetCache(),l.forEach(_=>{_.resetData()});return}const W=St()(await Promise.all(T.map(async({chart:_,feed:O})=>{const G=await un(_);return G===void 0?void 0:{feed:O,fromMs:G}})));await Promise.allSettled(W.map(({feed:_,fromMs:O})=>j(_,O)))}catch{}}});return{openFeed:f=>{const l={options:f,held:void 0,highWater:0,closed:!1,detector:void 0};return l.detector=(0,kt.k)({...n,subscribeSignals:f.subscribeSignals,repair:async()=>{l.closed||!l.highWater||g(f.ticker)||await j(l,l.highWater)}}),a.set(f.ticker,l),g(f.ticker)&&$.resubscribeSignals(f.subscribeSignals),{push:T=>{l.held?l.held.push(T):H(l,T)},close:()=>{l.closed=!0,l.detector?.dispose(),a.get(f.ticker)===l&&a.delete(f.ticker)}}},subscribeWake:$.subscribeWake,getSignal:f=>{const l=S(f);return s().includes(f)&&(l.claimedAsMain=!0),l.controller.signal},dispose:()=>{u.forEach(({controller:f})=>{f.abort()}),u.clear(),a.forEach(f=>{f.detector?.dispose()}),a.clear(),$.dispose()}}};var dn=e(71730),vn=e(36620),fn=function(t){var n=t.namespace,a=k.y$(n),u=(0,vn.l)().getSocketSignals,s=(0,r.useRef)();(0,r.useEffect)(function(){return function(){var S;(S=s.current)===null||S===void 0||S.instance.dispose(),s.current=void 0}},[]);var g=(0,r.useCallback)(function(S){var K,Y,H=S;if(((K=s.current)===null||K===void 0?void 0:K.key)===H)return s.current.instance;(Y=s.current)===null||Y===void 0||Y.instance.dispose();var j=nn.wr(S),$=ln({getWidget:function(){return a.getState().tradingViewReference},checkPeriodMs:dn.D(j)});return s.current={key:H,instance:$},$},[a]);return(0,r.useMemo)(function(){return{ensureRecovery:g,getSocketSignals:u}},[g,u])},mn="tv_web_error",pn=[Ye.bt.TRADING,Ye.bt.DELIVERING],Re;(function(t){t.Futures="futures",t.FuturesMark="futures-mark",t.FuturesIndex="futures-index",t.Delivery="delivery",t.DeliveryMark="delivery-mark",t.DeliveryIndex="delivery-index"})(Re||(Re={}));var Mt=function(t){var n=t.symbol,a=t.description,u=t.productType,s=t.pricePrecision,g=t.params,S=t.supportedResolutions,K=S===void 0?F:S;return{symbol:n,description:a,tickSize:Math.pow(10,s),exchange:"Binance",supportedResolutions:K,type:u,params:g}},yn=function(t){var n=t.namespace,a=t.useMiniTicker,u=a===void 0?!1:a,s=t.isCM,g=c.y$(n),S=k.y$(n),K=(0,$t.useQueryClient)(),Y=fn({namespace:n}),H=Y.ensureRecovery,j=Y.getSocketSignals,$=(0,r.useRef)(0),f=(0,r.useRef)(!1),l=(0,r.useMemo)(function(){return bt()(function(){return(0,We.HP)({eventId:"trd_chart_tv_load_timeout",stepId:"getBarsStart",catch:function(Q){var ee=Q.error,ae=Q.track;ae({GET_BAR_START_TYPE:ee})},log:function(Q){var ee=Q.track;ee({GET_BAR_START_TYPE:"SUCCESS"})}})})},[]),T=(0,r.useMemo)(function(){return bt()(function(z){return(0,We.HP)({eventId:"trd_chart_tv_load_timeout",stepId:"getBarsEnd",catch:function(ee){var ae=ee.error,Se=ee.track;Se({GET_BAR_END_TYPE:ae,duration_:(0,We.Pu)(z-$.current)})},log:function(ee){var ae=ee.track;ae({GET_BAR_END_TYPE:"SUCCESS",duration_:(0,We.Pu)(z-$.current)})}})})},[]),W=(0,Nr.o)("trd-chart","trade-ui"),_=W.getI18n,O=(0,he.ud)(),G=O.getI18n,te=(0,v._)(g(c.Ir),1),J=te[0],oe=(0,v._)(S(k.A7),2),Te=oe[1],N=(0,se.Ye)(),w=(0,Zt.JD)(s).isContUnit,re=(0,se.y_)(),ie=(0,r.useRef)(N),le=(0,r.useRef)(J),de=(0,r.useRef)(re);(0,r.useEffect)(function(){ie.current=N},[N]),(0,r.useEffect)(function(){le.current=J},[J]),(0,r.useEffect)(function(){de.current=re},[re]);var Be=(0,r.useMemo)(function(){return w?Rt.b:Rt.S},[w]),ve=(0,r.useMemo)(function(){return G("usdtFutures-13",{defaultValue:"Futures"})||""},[G]),Ce=(0,r.useMemo)(function(){return G("inverseFutures-13",{defaultValue:"Delivery"})||""},[G]),ce=(0,r.useMemo)(function(){return _("indexPrice",{defaultValue:"Index Price"})||""},[_]),Ke=(0,r.useMemo)(function(){return _("markPrice",{defaultValue:"Mark Price"})||""},[_]),Ze=(0,r.useMemo)(function(){return _("lastPrice",{defaultValue:"Last Price"})||""},[_]),Ge=(0,r.useMemo)(function(){return{exchanges:[{name:"Binance",value:"Binance",desc:""}],symbols_types:[{name:ve,value:Re.Futures},{name:"".concat(ve," ").concat(Ke),value:Re.FuturesMark},{name:"".concat(ve," ").concat(ce),value:Re.FuturesIndex},{name:Ce,value:Re.Delivery},{name:"".concat(Ce," ").concat(Ke),value:Re.DeliveryMark},{name:"".concat(Ce," ").concat(ce),value:Re.DeliveryIndex}]}},[Ce,ve,ce,Ke]),Xe=(0,r.useCallback)((0,o._)(function(){var z,Q,ee;return(0,i._)(this,function(ae){switch(ae.label){case 0:return ae.trys.push([0,2,,3]),[4,(0,en.c)(K)];case 1:return ae.sent(),[3,3];case 2:return z=ae.sent(),(0,gt.u4)(mn,{type:"onFetchSymbolsInfo",message:z.message,business:"futures"}),[3,3];case 3:return Q=ie.current,ee=Object.keys(Q).reduce(function(Se,xe){var ne=Q[xe]||Ct.o,ke=ne.symbol,Me=ne.pricePrecision,fe=ne.isDelivery,Pe=ne.contractStatus;if(!pn.includes(Pe))return Se;var _e=(0,Yt.S)(ne,G),je=_e.symbol,Ee=_e.contractTypeI18n,me=_e.deliveryDateFormat,ye="".concat(je," ").concat(Ee).concat(me?" ".concat(me):""),$e="".concat(ye," ").concat(Ze),Oe="".concat(ye," ").concat(ce),lr="".concat(ye," ").concat(Ke);return Se.concat([Mt({symbol:ke,description:$e,productType:fe?Re.Delivery:Re.Futures,pricePrecision:Me,params:{priceType:c.SJ.Last}}),Mt({symbol:ke,description:Oe,productType:fe?Re.DeliveryIndex:Re.FuturesIndex,pricePrecision:Me,params:{priceType:c.SJ.Index}}),Mt({symbol:ke,description:lr,productType:fe?Re.DeliveryMark:Re.FuturesMark,pricePrecision:(0,Xt.X)(Me),params:{priceType:c.SJ.Mark}})])},[]),[2,ee]}})}),[G,ce,Ke,Ze,K]),Ie=(0,r.useCallback)(function(z){var Q=z.ticker,ee=z.symbol,ae=z.interval,Se=z.priceType,xe=z.retryUntil,ne=ie.current[ee]||Ct.o,ke=ne.pair,Me=ne.contractType,fe=ne.contractVal,Pe=ne.isDelivery,_e=Pe?Be(fe):function(Oe){return Oe},je=(0,wt.h)({symbol:ee,pair:ke,contractType:Me,isDelivery:Pe,priceType:Se}),Ee,me,ye=function(Oe){return zt.y("tv","".concat(Pe?"cm":"um","|").concat(ee,"|").concat(Se,"|").concat(Oe.interval,"|").concat((Ee=Oe.limit)!==null&&Ee!==void 0?Ee:"","|").concat((me=Oe.endTime)!==null&&me!==void 0?me:""))};if(xe===void 0)return{transform:_e,onFetchKlineData:je};var $e=H(ae);return{transform:_e,onFetchKlineData:Qt.b(je,{enabled:Jt(xe),key:ye,signal:$e.getSignal(Q),subscribeWake:$e.subscribeWake})}},[H,Be]),He=(0,r.useCallback)((function(){var z=(0,o._)(function(Q){var ee,ae,Se,xe,ne,ke,Me,fe,Pe,_e,je,Ee,me,ye,$e,Oe,lr,Or;return(0,i._)(this,function(dr){switch(dr.label){case 0:return ee=Q.binanceInterval,ae=Q.rangeStartDate,Se=Q.rangeEndDate,xe=Q.tickerInfo,ne=Q.symbolInfo,ke=Q.firstDataRequest,$.current=Date.now(),l(),Me=xe.symbol,fe=Me===void 0?"":Me,Pe=xe.params,_e=Pe.priceType,je=_e===void 0?le.current:_e,de.current({symbol:fe,priceType:je})?(T(Date.now()),[2,[]]):(Ee=Math.max(0,ae*1e3),me=tn({firstDataRequest:ke,hasRequested:f.current,rangeEndDate:Se}),f.current=!0,$e=Ie({ticker:(ye=ne.ticker)!==null&&ye!==void 0?ye:"",symbol:fe,interval:ee,priceType:je,retryUntil:ke?me:void 0}),Oe=$e.transform,lr=$e.onFetchKlineData,[4,It({onFetchKlineData:lr,startTime:Ee,endTime:me,interval:ee})]);case 1:return Or=dr.sent(),T(Date.now()),[2,Or.map(Oe)]}})});return function(Q){return z.apply(this,arguments)}})(),[Ie,T,l]),ur=(0,r.useCallback)(function(z){var Q=z.binanceInterval,ee=z.callback,ae=z.tickerInfo,Se=z.symbolInfo,xe=ae.symbol,ne=xe===void 0?"":xe,ke=ae.params,Me=ke.priceType,fe=Me===void 0?le.current:Me;if(de.current({symbol:ne,priceType:fe}))return function(){return null};var Pe=ie.current[ne]||Ct.o,_e=Pe.pair,je=Pe.contractType,Ee=Pe.isDelivery,me,ye=(me=Se.ticker)!==null&&me!==void 0?me:"",$e=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe}).transform,Oe=H(Q),lr=Oe.openFeed({ticker:ye,onTick:ee,subscribeSignals:j(Ee),fetchSince:(function(){var dr=(0,o._)(function(Lr){var Ur,Yr,ct,De;return(0,i._)(this,function(ze){switch(ze.label){case 0:return Ur=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe,retryUntil:0}),Yr=Ur.transform,ct=Ur.onFetchKlineData,[4,It({onFetchKlineData:ct,startTime:Lr,endTime:0,interval:Q})];case 1:return De=ze.sent(),[2,De.map(Yr)]}})});return function(Lr){return dr.apply(this,arguments)}})()}),Or=(0,rn.B)({symbol:ne,pair:_e,contractType:je,interval:Q,priceType:fe,isDelivery:Ee,useMiniTicker:u,callback:function(dr){lr.push($e(dr))}});return function(){lr.close(),Or()}},[Ie,H,j,u]),Tr=(0,r.useCallback)((0,o._)(function(){var z;return(0,i._)(this,function(Q){switch(Q.label){case 0:return[4,K.fetchQuery(Wt.Bz.SERVER_TIME(s),function(){return(0,Gt.l)(s)},{})];case 1:return z=Q.sent(),[2,Math.floor(z/1e3)]}})}),[K,s]),_r=(0,r.useCallback)(function(z){return(0,cr._)({},z.template,(0,qt.sp)(s))},[s]),Cr=(0,r.useMemo)(function(){return ue()},[]),Er=Cr({config:Ge,onFetchSymbolsInfo:Xe,onFetchBars:He,onSubscribeBars:ur,onFetchServerTimeApi:Tr,onTransformSymbolInfo:_r}),we=Er.datafeed,Dr=Er.symbolsInfo;return(0,Ut.A)(function(){return Te()},[w]),{datafeed:we,symbolsInfo:Dr}},Pt="trd_chart_tv_load_timeout",hn=function(){var t=(0,We.PM)({id:"chartReady"}),n=(0,We.PM)({id:"getBarsStart",timeout:3}),a=(0,We.PM)({id:"getBarsEnd",timeout:6}),u=(0,We.PM)({id:"chartResponsive",timeout:3});We.HP.init({step:t._(n)._(a)._(u),eventId:Pt}),(0,We.HP)({eventId:Pt,stepId:"chartReady",catch:function(g){var S=g.error,K=g.track;K({CHART_READY_TYPE:S,connection:JSON.stringify(navigator.connection)})},log:function(g){var S=g.track;S({CHART_READY_TYPE:"SUCCESS",connection:JSON.stringify(navigator.connection)})}})},gn=function(t){(0,We.HP)({eventId:Pt,stepId:"chartResponsive",catch:function(a){var u=a.error,s=a.track;s({CHART_RESPONSIVE_TYPE:u,connection:JSON.stringify(navigator.connection)})},log:function(a){var u=a.track;u({CHART_RESPONSIVE_TYPE:"SUCCESS",duration_:(0,We.Pu)(Date.now()-t)})}})},Tn=[Qe.ZE,Qe.F3],Dt=function(t){var n=t.symbol,a=t.namespace,u=t.useMiniTicker,s=u===void 0?!1:u,g=c.y$(a),S=k.y$(a),K=(0,v._)(g(c.A8),1),Y=K[0],H=(0,v._)(g(c.Ir),2),j=H[0],$=H[1],f=(0,v._)(S(k.yj),1),l=f[0],T=(0,v._)(g(c.e6),1),W=T[0],_=(0,v._)(g(c._b),1),O=_[0],G=(0,he.ot)(),te=(0,pr.K7)(),J=te.pathname.includes(Ae.Q2),oe=te.pathname.includes(Ae.eq),Te=(0,r.useMemo)(function(){return J?(0,d._)(B.oT).concat(["hide_left_toolbar_by_default"]):B.oT},[J]),N=(0,se.Ye)(),w=(0,se.v3)({symbol:n}),re=(0,Fe.es)(n),ie=yn({namespace:a,useMiniTicker:s,isCM:re}),le=ie.datafeed,de=ie.symbolsInfo,Be=(0,Je.DP)(),ve=Be.theme,Ce=ve==="light"||ve==="light_glacier"?"light":"dark",ce=(0,yr.Z)(Ce),Ke=(0,r.useRef)(-1),Ze=Ht({symbol:n}).positionLines,Ge=Nt({symbol:n,namespace:a}).orderLines,Xe=ir({symbol:n}).executionOrders,Ie=pt({namespace:a,symbol:n}),He=(0,se.y0)(),ur=(0,r.useRef)(He),Tr=(0,r.useRef)(N),_r=(0,r.useRef)(l),Cr=(0,r.useRef)(J),Er=(0,r.useRef)(oe),we=(0,r.useRef)(de);(0,r.useEffect)(function(){we.current=de},[de]),(0,r.useEffect)(function(){ur.current=He},[He]),(0,r.useEffect)(function(){Tr.current=N},[N]),(0,r.useEffect)(function(){_r.current=l},[l]),(0,r.useEffect)(function(){Cr.current=J},[J]),(0,r.useEffect)(function(){Er.current=oe},[oe]),(0,Ve.a)(n,s),(0,sr.A)({symbol:n,interval:Y,priceType:j});var Dr=(0,r.useCallback)(function(){Ke.current=Date.now()},[]),z=(0,r.useCallback)((0,o._)(function(){var De,ze,qe,be,vr,fr,er;return(0,i._)(this,function(nr){switch(nr.label){case 0:if(Cr.current||!_r.current)return[2,Promise.resolve()];nr.label=1;case 1:return nr.trys.push([1,5,,6]),De=h().createInstance({name:a}),[4,De.keys()];case 2:return ze=nr.sent(),qe=ze.filter(function(mr){return/^((#TV_SYMBOL-)|(myTradingView))/g.test(mr)}),be=qe.map((function(){var mr=(0,o._)(function(ar){return(0,i._)(this,function(Sr){return[2,De.getItem(ar)]})});return function(ar){return mr.apply(this,arguments)}})()),[4,Promise.all(be)];case 3:return vr=nr.sent(),fr=U()(qe,vr),[4,Promise.all(Tn.map((function(){var mr=(0,o._)(function(ar){var Sr,Wr;return(0,i._)(this,function(Gr){switch(Gr.label){case 0:return a===ar?[2,Promise.resolve()]:(Sr=h().createInstance({name:ar}),Wr=fr.map((function(){var Sn=(0,o._)(function(Lt){var _t,Ot,At;return(0,i._)(this,function(Mn){return _t=(0,v._)(Lt,2),Ot=_t[0],At=_t[1],Ot?[2,Sr.setItem(Ot,At)]:[2,Promise.resolve()]})});return function(Lt){return Sn.apply(this,arguments)}})()),[4,Promise.all(Wr)]);case 1:return Gr.sent(),[2,Promise.resolve()]}})});return function(ar){return mr.apply(this,arguments)}})()))];case 4:return nr.sent(),[2,Promise.resolve()];case 5:return er=nr.sent(),console.warn(typeof er=="string"?er:er.message),[2,Promise.resolve()];case 6:return[2]}})}),[a]),Q=(0,r.useMemo)(function(){return z},[z]),ee=(0,or.R)(),ae=(0,Ne.fQ)(a),Se=(0,Ne.m3)(a),xe=(0,Ne.S_)(a),ne=(0,Ne.Ds)(a),ke=(0,r.useRef)(n),Me=(0,r.useRef)(Y),fe=(0,r.useCallback)(function(){return ee("tradingView"),xe({panel_index:0,symbol:n,interval:Y}),hn()},[Y,ee,n,xe]),Pe=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be,vr,fr,er=(fr=(vr=(be=qe.symbolExt())===null||be===void 0?void 0:be.name)!==null&&vr!==void 0?vr:qe.symbol())!==null&&fr!==void 0?fr:"";ae({panel_index:ze,symbol:er,previous_symbol:ke.current}),ke.current=er},[ae]),_e=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be=qe.resolution();Se({panel_index:ze,interval:be,previous_interval:Me.current}),Me.current=be},[Se]),je=(0,r.useCallback)(function(){gn(Ke.current)},[]),Ee=(0,r.useCallback)(function(){},[]),me=(0,r.useMemo)(function(){return J?Ee:void 0},[Ee,J]),ye=(0,r.useCallback)((function(){var De=(0,o._)(function(ze){var qe,be,vr,fr,er,nr,mr,ar,Sr;return(0,i._)(this,function(Wr){switch(Wr.label){case 0:return qe=ze.chart,be=qe.symbolExt(),be?(vr=b()(we.current,function(Gr){return Gr.ticker===be.ticker||Gr.symbol===be.ticker}),fr=vr||{},er=fr.params,nr=er===void 0?{}:er,mr=nr.priceType,ar=mr===void 0?"":mr,ar&&$(ar),Sr=Tr.current[be.name],Sr?Er.current||Cr.current?[2]:[4,z()]:[2]):[2];case 1:return Wr.sent(),G({stateSymbol:Sr.symbol,isDeliveryColumn:Sr.isDelivery}),[2]}})});return function(ze){return De.apply(this,arguments)}})(),[G,z,$]),$e=qr(),Oe=$e.onCrosshairMoved,lr=$e.crosshairPrice,Or=wr({namespace:a,symbol:n,priceType:j,crosshairPrice:lr}),dr=lt(),Lr=(0,r.useMemo)(function(){return L({symbol:n,params:{priceType:j}})},[n,j]),Ur=(0,r.useMemo)(function(){return{symbol:Lr,theme:ce,limitOrders:Ge,positionOrders:Ze,executionOrders:Xe,labelLines:Ie,initialConfig:{tvConfig:{symbol:Lr,library_path:"/".concat(Ye.u_,"/trading-platform-30/"),theme:ce,datafeed:le,enabled_features:Te,context_menu:Or,custom_formatters:dr},onCrosshairMoved:Oe,onScriptLoaded:Dr,onSave:Q,onChartReadyDone:fe,onFirstTimeDataLoadedDone:je,onInitChart:me,onActiveChartChanged:ye,onSymbolChanged:Pe,onIntervalChanged:_e}}},[Lr,le,Te,Xe,Ie,Or,dr,Oe,ye,fe,je,me,_e,Q,Dr,Pe,Ge,Ze,ce]),Yr=Le({namespace:a,type:"tv"}),ct=(0,r.useCallback)(function(){ne({panel_index:0,symbol:n}),Yr()},[Yr,n,ne]);return(0,p.jsxs)(D.t,{name:"trading_view",onReset:ct,children:[(0,p.jsx)(Ue.A,{namespace:a,tradingViewProps:Ur}),w&&j==="index"&&O===c.Ev.Single&&W===c.tU.TradingView&&(0,p.jsx)(rr.B,{})]})};const Cn=Dt}';
+ function createMirrorFactory(__dispatchChartMirror) {
+ if (typeof __dispatchChartMirror !== "function") throw new Error("Mirror dispatch must be a function");
+ return { 70940(Z, x, e) {
+ "use strict";
+ e.r(x), e.d(x, { TradingView: () => Dt, default: () => Cn });
+ var o = e(49253), v = e(77135), d = e(75510), i = e(42288), p = e(31085), r = e(41594), M = e(43917), h = e.n(M), P = e(81936), b = e.n(P), V = e(7980), U = e.n(V), D = e(76469), c = e(4260), k = e(53837), B = e(27571), C = e(45250), y = e(87646), A = e.n(y), q = e(80817);
+ const E = (0, C.range)(1, 1380), m = (0, C.range)(1, 365).map((t) => `${t}D`), R = (0, C.range)(1, 52).map((t) => `${t}W`), I = (0, C.range)(1, 12).map((t) => `${t}M`), F = ["1S", ...E, ...m, ...R, ...I], L = ({ symbol: t, params: n }) => {
+ const a = new URLSearchParams(n);
+ return `${t}@${a.toString()}`;
+ }, X = (t) => {
+ const [n, a] = t.split("@"), s = new URLSearchParams(a).entries(), g = A()(Array.from(s));
+ return { symbol: n, params: g };
+ }, ue = () => {
+ const t = /* @__PURE__ */ new Map();
+ let n = {}, a = [], u = { onReady: () => null, resolveSymbol: () => null, searchSymbols: () => null, getBars: () => null, subscribeBars: () => null, unsubscribeBars: () => null };
+ return ({ config: s, onSymbolResolving: g, onSymbolResolved: S, onFetchSymbolsInfo: K, onFetchBars: Y, onSubscribeBars: H, onUnsubscribeBars: j, onFetchServerTimeApi: $, onTransformSymbolInfo: f = ({ template: T }) => T, onSearchSymbols: l = () => true }) => (u = { onReady: (T) => {
+ setTimeout(async () => {
+ const _ = (await K()).map((O) => ({ ...O, ticker: L({ symbol: O.symbol, params: O.params || {} }) }));
+ a = _.map(({ ticker: O, symbol: G, description: te = "", exchange: J = "", type: oe = "" }) => ({ symbol: G, ticker: O, description: te, exchange: J, type: oe })), n = (0, C.keyBy)(_, "ticker"), T({ exchanges: [], symbols_types: [], supported_resolutions: F, supports_marks: false, supports_timescale_marks: false, supports_time: true, ...s });
+ });
+ }, resolveSymbol: async (T, W, _) => {
+ const O = n[T];
+ if (!O) {
+ _(`cannot resolve symbol - ${T}`);
+ return;
+ }
+ const G = f({ info: O, template: { description: O.description || "", fractional: false, has_seconds: true, seconds_multipliers: ["1"], has_intraday: true, intraday_multipliers: ["1", "3", "5", "15", "30", "60", "120", "240", "360", "480", "720"], has_daily: true, daily_multipliers: ["1"], has_weekly_and_monthly: true, weekly_multipliers: ["1"], monthly_multipliers: ["1"], minmov: 1, minmove2: 0, name: O.symbol, ticker: O.ticker, timezone: "Etc/UTC", pricescale: O.tickSize, session: "24x7", type: O.type || "", exchange: O.exchange || "", listed_exchange: O.exchange || "", format: "price", supported_resolutions: F, visible_plots_set: "ohlcv", volume_precision: 3 } });
+ if (g) {
+ const te = X(G.ticker || "");
+ if (!await g({ symbol: T, info: G, tickerInfo: te })) {
+ _(`symbol resolving error - ${T}`);
+ return;
+ }
+ }
+ setTimeout(() => {
+ W(G), S?.(G);
+ });
+ }, searchSymbols: (T, W, _, O) => {
+ const G = new RegExp(T, "i"), te = new RegExp(W, "i"), J = new RegExp(_, "i"), oe = a.filter((Te) => {
+ const { symbol: N, ticker: w, description: re = "", exchange: ie = "", type: le = "" } = Te;
+ return [N, w, re].some((de) => G.test(de || "")) && te.test(ie) && J.test(le) && l?.({ item: Te });
+ });
+ O(oe);
+ }, getBars: async (T, W, _, O, G) => {
+ const { from: te, to: J, firstDataRequest: oe } = _, Te = (0, q.Cz)(W);
+ try {
+ if (J >= 0) {
+ const N = X(T.ticker || ""), w = await Y({ symbolInfo: T, resolution: W, rangeStartDate: te, rangeEndDate: J, tickerInfo: N, binanceInterval: Te, firstDataRequest: oe });
+ O(w, { noData: w.length === 0 });
+ } else O([], { noData: true });
+ } catch (N) {
+ G(N.toString());
+ }
+ }, subscribeBars: (T, W, _, O, G) => {
+ const te = X(T.ticker || ""), J = (0, q.Cz)(W), oe = H({ symbolInfo: T, resolution: W, callback: _, onResetCacheNeededCallback: G, tickerInfo: te, binanceInterval: J });
+ t.set(O, oe);
+ }, unsubscribeBars: (T) => {
+ t?.get(T)?.(), t?.delete(T), j?.(T);
+ }, getServerTime: async (T) => {
+ try {
+ const W = await $();
+ T(W);
+ } catch (W) {
+ console.warn(W), T(Date.now() / 1e3);
+ }
+ } }, { datafeed: u, observers: t, symbolsInfo: n });
+ }, Le = ({ namespace: t, type: n }) => {
+ const a = (0, r.useMemo)(() => h().createInstance({ name: t }), [t]);
+ return (0, r.useCallback)(() => {
+ n === "tv" && a.removeItem("myTradingView");
+ }, [a, n]);
+ };
+ var Ue = e(76535), pr = e(26531), Ye = e(17409), Qe = e(94652), Ae = e(76830), he = e(48651), Fe = e(79344), se = e(51029), Je = e(36307), or = e(25379), yr = e(44745), Ne = e(15727), rr = e(45358), sr = e(82071), Ve = e(85411), ge = e(94889), hr = e(94547), tr = e(80686), Ar = e(61875);
+ function Fr(t) {
+ return (0, ge._)(t) || (0, hr._)(t) || (0, Ar._)(t) || (0, tr._)();
+ }
+ var Nr = e(92873), zr = e(35630), Vr = e(12055), Qr = e(79515), Br = e(80737), Jr = e(40545), Zr = e(98107), Kr = e(51846), Xr = "Chart.Crosshair.PlusButton.DrawHorizontalLine", wr = function(t) {
+ var n = t.namespace, a = t.symbol, u = t.priceType, s = t.crosshairPrice, g = (0, Nr.o)("", "kline-ui").getI18n, S = (0, Vr.r)().format, K = k.y$(n), Y = (0, Br.S)({ namespace: n }), H = (0, Jr.hw)(), j = (0, Zr.c)(), $ = (0, Qr.nH)(), f = (0, se.Ye)(), l = (0, Kr.E)({ symbol: a }), T = (0, Fe.f0)(a), W = T.isCM, _ = (0, r.useRef)(s);
+ (0, r.useEffect)(function() {
+ _.current = s;
+ }, [s]);
+ var O = (0, r.useRef)(H);
+ (0, r.useEffect)(function() {
+ O.current = H;
+ }, [H]);
+ var G = (0, r.useRef)(j);
+ (0, r.useEffect)(function() {
+ G.current = j;
+ }, [j]);
+ var te = (0, r.useRef)(u);
+ (0, r.useEffect)(function() {
+ te.current = u;
+ }, [u]);
+ var J = (0, r.useRef)($);
+ (0, r.useEffect)(function() {
+ J.current = $;
+ }, [$]);
+ var oe = (0, r.useRef)(f);
+ (0, r.useEffect)(function() {
+ oe.current = f;
+ }, [f]);
+ var Te = (0, r.useRef)(l);
+ (0, r.useEffect)(function() {
+ Te.current = l;
+ }, [l]);
+ var N = (0, r.useRef)(W);
+ return (0, r.useEffect)(function() {
+ N.current = W;
+ }, [W]), { items_processor: (function() {
+ var w = (0, o._)(function(re, ie, le) {
+ var de, Be, ve, Ce, ce, Ke, Ze, Ge, Xe, Ie, He, ur, Tr, _r, Cr;
+ return (0, i._)(this, function(Er) {
+ if (Be = K.getState().tradingViewReference, !Be) return console.warn("[context-menu] trading view reference is not found"), [2, re];
+ if (Be.chartsCount() > 1) return [2, re];
+ if (ve = Be.activeChart().symbolExt(), !ve) return [2, re];
+ switch (Ce = ve.name, ce = _.current, Ke = le.menuName === "CrosshairMenuView" ? "chart-trading" : "context-menu", Ze = { price: "".concat(ce.value), formattedPrice: ce.formatted, source: Ke, chartType: "tradingview" }, Ge = (de = oe.current[Ce]) === null || de === void 0 ? void 0 : de.baseAsset, Xe = g("trade", { defaultValue: "Trade" }), Ie = S(ce.formatted), He = G.current && te.current === c.SJ.Last && J.current && Ge ? [ie.createAction({ actionId: "Chart.PlaceOrder.BuyLimit", label: "".concat(Xe, " ").concat(Ge, " @ ").concat(Ie, " ").concat(g("limit", { defaultValue: "Limit" })), onExecute: function() {
+ return Te.current.onClickBuyLimit(Ze);
+ } }), ie.createAction({ actionId: "Chart.PlaceOrder.BuyStop", label: "".concat(Xe, " ").concat(Ge, " @ ").concat(Ie, " ").concat(g("stop", { defaultValue: "Stop" })), onExecute: function() {
+ return Te.current.onClickBuyStop(Ze);
+ } })] : [], ur = O.current ? ie.createAction({ actionId: "Chart.PriceAlert.CreateAtCrosshair", label: g("create-alert-at", { defaultValue: "Create Alert at {{price}}", price: ce.formatted }), onExecute: function() {
+ Y({ symbol: Ce, price: ce.formatted, mode: zr.n.CREATE });
+ } }) : null, re.forEach(function(we) {
+ if (!(!("execute" in we) || !("getState" in we))) {
+ var Dr = we.getState();
+ if (Dr.actionId === Xr) {
+ var z = we.execute.bind(we);
+ we.execute = function() {
+ (0, Kr.A)({ elementId: "draw_horizontal_line", isDelivery: N.current, chartType: "tradingview" }), z();
+ };
+ }
+ }
+ }), le.menuName) {
+ case "CrosshairMenuView":
+ return ur ? [2, [ur].concat((0, d._)(He), (0, d._)(re.slice()))] : [2, (0, d._)(He).concat((0, d._)(re.slice()))];
+ case "ChartContextMenu":
+ return ur ? (Tr = Fr(re), _r = Tr[0], Cr = Tr.slice(1), [2, [_r, ur].concat((0, d._)(He), (0, d._)(Cr))]) : [2, (0, d._)(He).concat((0, d._)(re.slice()))];
+ default:
+ return [2, re];
+ }
+ return [2];
+ });
+ });
+ return function(re, ie, le) {
+ return w.apply(this, arguments);
+ };
+ })() };
+ }, qr = function() {
+ var t = (0, r.useState)({ value: NaN, formatted: "" }), n = t[0], a = t[1], u = (0, r.useCallback)(function(s) {
+ var g = s.crosshairData, S = s.activeChart;
+ S && a({ value: g.price, formatted: S.priceFormatter().format(g.price) });
+ }, []);
+ return { onCrosshairMoved: u, crosshairPrice: n };
+ };
+ const Hr = (t) => Math.floor(Math.log10(t)), et = (t) => {
+ const [n = "1"] = t.split(",");
+ return Hr(+n);
+ }, pe = ({ symbolInfo: t, minTick: n }) => {
+ const { pricescale: a = 2 } = t || {};
+ return n === "default" || n === void 0 ? Hr(a) : et(n);
+ }, br = (t) => (n, a) => {
+ try {
+ return n ? { format: (u) => {
+ const s = pe({ symbolInfo: n, minTick: a });
+ return t.format({ symbolInfo: n, minTick: a, value: u, precision: s });
+ } } : null;
+ } catch {
+ return null;
+ }
+ }, Mr = 10 ** 12, rt = 10 ** 9, tt = 10 ** 6, jr = 10 ** 3, gr = (t) => {
+ const n = t.toString(), [, a = ""] = n.split(".");
+ return Math.min(a.length, 3);
+ }, nt = ({ value: t }) => {
+ const n = Math.abs(t);
+ if (n >= Mr) {
+ const a = t / Mr;
+ return { value: a, unit: "T", precision: gr(a) };
+ }
+ if (n >= rt) {
+ const a = t / rt;
+ return { value: a, unit: "B", precision: gr(a) };
+ }
+ if (n >= tt) {
+ const a = t / tt;
+ return { value: a, unit: "M", precision: gr(a) };
+ }
+ if (n >= jr) {
+ const a = t / jr;
+ return { value: a, unit: "K", precision: gr(a) };
+ }
+ return { value: t, unit: "", precision: gr(t) };
+ }, ut = (t) => (n, a, u) => {
+ try {
+ return a && n.type === "volume" ? { format: (s) => s === void 0 ? "" : t.formatVolume({ format: n, symbolInfo: a, precision: u, value: s, volumeDetail: nt({ value: s }) }) } : null;
+ } catch {
+ return null;
+ }
+ };
+ var lt = function() {
+ var t = (0, Vr.r)(), n = t.format, a = (0, r.useMemo)(function() {
+ return br({ format: function(s) {
+ var g = s.value, S = s.precision;
+ return n(g, { precision: S });
+ } });
+ }, [n]), u = (0, r.useMemo)(function() {
+ return ut({ formatVolume: function(s) {
+ var g = s.value, S = s.volumeDetail;
+ if (g === void 0 || !S) return "";
+ var K = n(S.value, { precision: S.precision });
+ return "".concat(K).concat(S.unit);
+ } });
+ }, [n]);
+ return { priceFormatterFactory: a, studyFormatterFactory: u };
+ }, dt = e(74069), ir = function(t) {
+ var n = (0, dt.K)(t), a = (0, r.useMemo)(function() {
+ return n.map(function(u) {
+ var s = u.id, g = u.time, S = u.isBuy, K = u.price, Y = u.tooltip;
+ return { id: s, text: "", price: K, quantity: "", time: Math.round(g / 1e3), isBuy: S, tooltip: Y };
+ });
+ }, [n]);
+ return { executionOrders: a };
+ }, at = function(t) {
+ var n = t.namespace, a = t.symbol, u = (0, Br.j)({ namespace: n, symbol: a });
+ return (0, r.useMemo)(function() {
+ return u.map(function(s) {
+ var g = s.id, S = s.price, K = s.formattedPrice, Y = s.sideText, H = s.showCloseButton, j = s.isPriceChangeable, $ = s.cancelTooltipText, f = s.onClick, l = s.onCancel, T = s.onPriceChanged;
+ return { id: g, price: +S, text: "".concat(Y, " ").concat(K), editable: j, cancellable: H, cancelTooltip: $, type: c.ND.PriceAlert, onCancel: H ? l : void 0, onMove: j ? function(W) {
+ var _ = W.price;
+ T({ currentPrice: _ });
+ } : void 0, onClick: f };
+ });
+ }, [u]);
+ }, vt = e(90664), ot = function(t) {
+ var n = t.symbol, a = (0, vt.w)({ symbol: n });
+ return (0, r.useMemo)(function() {
+ return a.map(function(u) {
+ var s = u.id, g = u.text, S = u.price;
+ return { id: s, text: g, price: +S, type: c.ND.BreakEvenPrice };
+ });
+ }, [a]);
+ }, cr = e(82508), ft = e(91025), mt = e(36077), Rr = function(t) {
+ var n = t.symbol, a = (0, mt.c)({ symbol: n });
+ return (0, r.useMemo)(function() {
+ return a.map(function(u) {
+ return (0, ft._)((0, cr._)({}, u), { type: c.ND.LiquidationPrice });
+ });
+ }, [a]);
+ }, pt = function(t) {
+ var n = t.namespace, a = t.symbol, u = at({ namespace: n, symbol: a }), s = ot({ symbol: a }), g = Rr({ symbol: a });
+ return (0, r.useMemo)(function() {
+ return (0, d._)(u).concat((0, d._)(s), (0, d._)(g));
+ }, [u, s, g]);
+ }, st = e(42902), Ir = e(90170), yt = e(63345), ht = e(48210), Pr = e(51289), xr = e(24326), $r = e(90922), kr = e(34235), it = e(63762), Ft = function(t) {
+ var n = (0, Fe.f0)(t), a = n.pricePrecision, u = n.isCM, s = (0, st.yD)(t) || {}, g = s.lastPrice, S = g === void 0 ? "0" : g, K = (0, kr.Bp)(u), Y = (0, it.HY)(t), H = (0, kr.gv)(u), j = (0, kr.WF)(u);
+ return (0, r.useMemo)(function() {
+ return (0, $r.qF)({ orders: (0, d._)(K).concat((0, d._)(H), (0, d._)(j)), symbol: t }).map(function($) {
+ return Y({ order: $, lastPrice: S, pricePrecision: a });
+ });
+ }, [S, K, H, a, t, Y]);
+ }, gt = e(31542), Tt = function(t) {
+ var n = t.type;
+ (0, gt.u4)("webClick", { module: "chart", elementId: n });
+ }, Nt = function(t) {
+ var n = t.symbol, a = t.namespace, u = (0, r.useState)(0), s = u[0], g = u[1], S = (0, r.useCallback)(function() {
+ return g(function(N) {
+ return N + 1;
+ });
+ }, []), K = Ft(n), Y = (0, xr.IT)({ namespace: a }), H = (0, Fe.f0)(n), j = H.isDelivery, $ = H.contractType, f = (0, st.yD)(n) || {}, l = f.lastPrice, T = l === void 0 ? "0" : l, W = (0, yt.s)(n), _ = (0, it.YZ)({ symbol: n, afterConfirmationModalIsShown: S }), O = (0, it.u8)(n), G = (0, it.jn)(n), te = (0, r.useCallback)(function(N) {
+ return (0, ht.D)({ order: N, isDelivery: j, symbol: j ? N.symbol : "".concat(N.symbol, "_").concat($), module: "tradingview_kline" });
+ }, [$, j]), J = (0, r.useMemo)(function() {
+ return K.map(function(N) {
+ var w = N.order, re = N.price, ie = N.quantity, le = N.quantityText, de = N.isBuy, Be = N.text, ve = N.priceTooltipText, Ce = N.pnlText, ce = (0, Pr.$L)(w), Ke = (0, Pr.OX)(w), Ze = (0, Pr.xL)(w), Ge = ce === "LIMIT", Xe = (0, Ir.Y)(w);
+ return { id: String(Ke), text: Be, price: +re, quantity: le, isBuy: de, editable: Xe && G, tooltip: ve, modifyTooltip: w.closePosition || !Ge ? "" : void 0, onMove: G ? function(Ie) {
+ var He = Ie.price;
+ te(w), Tt({ type: "TV_vertical" }), _({ order: w, price: +He, quantity: +Ze, lastPrice: T });
+ } : void 0, onModify: w.closePosition || !Ge || !Xe ? void 0 : function() {
+ Tt({ type: "TV_amountclick" }), O({ order: w, price: re.toString(), quantity: ie, lastPrice: T });
+ }, onCancel: function() {
+ Tt({ type: "TV_cancel" }), W(w);
+ } };
+ });
+ }, [G, K, T, te, _, O, W, s]), oe = (0, r.useMemo)(function() {
+ return Y.map(function(N) {
+ var w = N.id, re = N.value, ie = N.text, le = N.onChange, de = N.isBuy, Be = N.type;
+ return { id: w, price: +re, quantity: "", text: ie, isBuy: de, editable: true, limitOrderType: c.aA.OrderPreview, onMove: function(ve) {
+ var Ce = ve.price;
+ (0, xr.eP)({ namespace: a, orderType: Be }), le({ id: w, newPrice: Ce });
+ }, onMoving: function(ve) {
+ var Ce = ve.price;
+ return le({ id: w, newPrice: Ce });
+ } };
+ });
+ }, [Y, a]), Te = (0, r.useMemo)(function() {
+ return (0, d._)(J).concat((0, d._)(oe));
+ }, [J, oe]);
+ return { orderLines: Te };
+ }, Vt = e(17855), Bt = function(t, n) {
+ var a = (0, r.useState)(t()), u = a[0], s = a[1];
+ return (0, Vt.A)(function() {
+ return s(t());
+ }, n), u;
+ }, Kt = e(58656), Ht = function(t) {
+ var n = t.symbol, a = (0, Kt.Q)({ symbol: n }) || [], u = Bt(function() {
+ return a.map(function(s) {
+ var g = s.position, S = s.pnl, K = s.pnlInNumber, Y = s.pnlText, H = s.price, j = s.quantity, $ = s.reverseText, f = s.isBuy, l = s.displayReverseOrderModal, T = s.displayClosePositionModal;
+ return { id: g.id || "".concat(g.symbol, "-").concat(g.positionSide), PNL: K, text: "".concat(Y, " ").concat(S), price: H, quantity: j, tooltip: "", reverseTooltip: $, isBuy: f, onReverse: l, onClose: function() {
+ T(), (0, gt.u4)("$WebClick", { item: 1, module: "xxx", $element_id: "kline_close_position" });
+ } };
+ });
+ }, [a]);
+ return { positionLines: u };
+ }, jt = e(47354), bt = e.n(jt), $t = e(87017), Ut = e(99168), Yt = e(3742), Wt = e(43335), Gt = e(15540), zt = e(20163), Qt = e(47255);
+ const Jt = (t) => ({ endTime: n }) => (n ?? 0) === t;
+ var Zt = e(36488), Ct = e(48018), Xt = e(82911), We = e(69578), wt = e(53208), Rt = e(19862), qt = e(8874), en = e(26679), rn = e(66547), It = (function() {
+ var t = (0, o._)(function(n) {
+ var a, u, s, g, S, K, Y, H, j;
+ return (0, i._)(this, function($) {
+ switch ($.label) {
+ case 0:
+ a = n.onFetchKlineData, u = n.startTime, s = n.endTime, g = n.interval, S = n.limit, K = S === void 0 ? 1e3 : S, Y = [], H = [], j = s, $.label = 1;
+ case 1:
+ return [4, a((0, cr._)({ interval: g, limit: K }, j === 0 ? {} : { endTime: j }))];
+ case 2:
+ if (H = $.sent(), H.length === 0) return [3, 4];
+ H.forEach(function(f) {
+ var l = (0, v._)(f, 8), T = l[0], W = l[1], _ = l[2], O = l[3], G = l[4], te = l[5], J = l[7];
+ Y.push({ time: +T, open: +W, high: +_, low: +O, close: +G, volume: +te, quantity: +J });
+ }), j = H[0][0] - 1, $.label = 3;
+ case 3:
+ if (j > u) return [3, 1];
+ $.label = 4;
+ case 4:
+ return Y.sort(function(f, l) {
+ return f.time - l.time;
+ }), [2, Y];
+ }
+ });
+ });
+ return function(a) {
+ return t.apply(this, arguments);
+ };
+ })(), tn = function(t) {
+ var n = t.firstDataRequest, a = t.hasRequested, u = t.rangeEndDate;
+ return n && !a ? 0 : u * 1e3;
+ }, nn = e(78142), an = e(16238), St = e.n(an), on = e(98087), xt = e.n(on), sn = e(55009), cn = e.n(sn), kt = e(56477);
+ const Et = (t) => cn()(t.chartsCount(), (n) => t.chart(n)), un = async (t) => {
+ try {
+ const { schema: n, data: a } = await t.exportData({ includedStudies: [] }), u = xt()(a)?.[n.findIndex((s) => s.type === "time")];
+ return u !== void 0 && Number.isFinite(u) ? u * 1e3 : void 0;
+ } catch {
+ return;
+ }
+ }, ln = ({ getWidget: t, ...n }) => {
+ const a = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Map(), s = () => {
+ try {
+ const f = t();
+ return f ? St()(Et(f).map((l) => l.symbolExt()?.ticker)) : [];
+ } catch {
+ return [];
+ }
+ }, g = (f) => {
+ const l = s();
+ return !l.length || l.includes(f);
+ }, S = (f) => {
+ const l = u.get(f);
+ if (l) return l;
+ const T = { controller: new AbortController(), claimedAsMain: false };
+ return u.set(f, T), T;
+ }, K = (f) => {
+ const l = u.get(f);
+ l && (l.controller.abort(), u.delete(f));
+ }, Y = () => {
+ const f = s();
+ f.length && Array.from(u.keys()).forEach((l) => {
+ const T = u.get(l);
+ if (T) {
+ if (f.includes(l)) {
+ T.claimedAsMain = true;
+ return;
+ }
+ a.has(l) || T.claimedAsMain && K(l);
+ }
+ });
+ }, H = (f, l) => {
+ f.closed || l.time < f.highWater || (f.highWater = l.time, f.options.onTick(l), f.detector?.onBar(l), g(f.options.ticker) && $.onBar(l));
+ }, j = async (f, l) => {
+ f.held = [];
+ let T = l;
+ try {
+ const _ = (await f.options.fetchSince(l)).filter((O) => O.time >= l && !f.closed);
+ _.forEach((O) => {
+ f.highWater = Math.max(f.highWater, O.time), f.options.onTick(O);
+ }), T = xt()(_)?.time ?? l;
+ } finally {
+ const W = f.held ?? [];
+ f.held = void 0, W.filter((_) => _.time >= T).forEach((_) => {
+ H(f, _);
+ });
+ }
+ }, $ = (0, kt.k)({ ...n, onCheck: Y, repair: async () => {
+ const f = t();
+ if (f) try {
+ const l = Et(f), T = St()(l.map((_) => {
+ const O = a.get(_.symbolExt()?.ticker ?? "");
+ return O && { chart: _, feed: O };
+ }));
+ if (!T.length) {
+ f.resetCache(), l.forEach((_) => {
+ _.resetData();
+ });
+ return;
+ }
+ const W = St()(await Promise.all(T.map(async ({ chart: _, feed: O }) => {
+ const G = await un(_);
+ return G === void 0 ? void 0 : { feed: O, fromMs: G };
+ })));
+ await Promise.allSettled(W.map(({ feed: _, fromMs: O }) => j(_, O)));
+ } catch {
+ }
+ } });
+ return { openFeed: (f) => {
+ const l = { options: f, held: void 0, highWater: 0, closed: false, detector: void 0 };
+ return l.detector = (0, kt.k)({ ...n, subscribeSignals: f.subscribeSignals, repair: async () => {
+ l.closed || !l.highWater || g(f.ticker) || await j(l, l.highWater);
+ } }), a.set(f.ticker, l), g(f.ticker) && $.resubscribeSignals(f.subscribeSignals), { push: (T) => {
+ l.held ? l.held.push(T) : H(l, T);
+ }, close: () => {
+ l.closed = true, l.detector?.dispose(), a.get(f.ticker) === l && a.delete(f.ticker);
+ } };
+ }, subscribeWake: $.subscribeWake, getSignal: (f) => {
+ const l = S(f);
+ return s().includes(f) && (l.claimedAsMain = true), l.controller.signal;
+ }, dispose: () => {
+ u.forEach(({ controller: f }) => {
+ f.abort();
+ }), u.clear(), a.forEach((f) => {
+ f.detector?.dispose();
+ }), a.clear(), $.dispose();
+ } };
+ };
+ var dn = e(71730), vn = e(36620), fn = function(t) {
+ var n = t.namespace, a = k.y$(n), u = (0, vn.l)().getSocketSignals, s = (0, r.useRef)();
+ (0, r.useEffect)(function() {
+ return function() {
+ var S;
+ (S = s.current) === null || S === void 0 || S.instance.dispose(), s.current = void 0;
+ };
+ }, []);
+ var g = (0, r.useCallback)(function(S) {
+ var K, Y, H = S;
+ if (((K = s.current) === null || K === void 0 ? void 0 : K.key) === H) return s.current.instance;
+ (Y = s.current) === null || Y === void 0 || Y.instance.dispose();
+ var j = nn.wr(S), $ = ln({ getWidget: function() {
+ return a.getState().tradingViewReference;
+ }, checkPeriodMs: dn.D(j) });
+ return s.current = { key: H, instance: $ }, $;
+ }, [a]);
+ return (0, r.useMemo)(function() {
+ return { ensureRecovery: g, getSocketSignals: u };
+ }, [g, u]);
+ }, mn = "tv_web_error", pn = [Ye.bt.TRADING, Ye.bt.DELIVERING], Re;
+ (function(t) {
+ t.Futures = "futures", t.FuturesMark = "futures-mark", t.FuturesIndex = "futures-index", t.Delivery = "delivery", t.DeliveryMark = "delivery-mark", t.DeliveryIndex = "delivery-index";
+ })(Re || (Re = {}));
+ var Mt = function(t) {
+ var n = t.symbol, a = t.description, u = t.productType, s = t.pricePrecision, g = t.params, S = t.supportedResolutions, K = S === void 0 ? F : S;
+ return { symbol: n, description: a, tickSize: Math.pow(10, s), exchange: "Binance", supportedResolutions: K, type: u, params: g };
+ }, yn = function(t) {
+ var n = t.namespace, a = t.useMiniTicker, u = a === void 0 ? false : a, s = t.isCM, g = c.y$(n), S = k.y$(n), K = (0, $t.useQueryClient)(), Y = fn({ namespace: n }), H = Y.ensureRecovery, j = Y.getSocketSignals, $ = (0, r.useRef)(0), f = (0, r.useRef)(false), l = (0, r.useMemo)(function() {
+ return bt()(function() {
+ return (0, We.HP)({ eventId: "trd_chart_tv_load_timeout", stepId: "getBarsStart", catch: function(Q) {
+ var ee = Q.error, ae = Q.track;
+ ae({ GET_BAR_START_TYPE: ee });
+ }, log: function(Q) {
+ var ee = Q.track;
+ ee({ GET_BAR_START_TYPE: "SUCCESS" });
+ } });
+ });
+ }, []), T = (0, r.useMemo)(function() {
+ return bt()(function(z) {
+ return (0, We.HP)({ eventId: "trd_chart_tv_load_timeout", stepId: "getBarsEnd", catch: function(ee) {
+ var ae = ee.error, Se = ee.track;
+ Se({ GET_BAR_END_TYPE: ae, duration_: (0, We.Pu)(z - $.current) });
+ }, log: function(ee) {
+ var ae = ee.track;
+ ae({ GET_BAR_END_TYPE: "SUCCESS", duration_: (0, We.Pu)(z - $.current) });
+ } });
+ });
+ }, []), W = (0, Nr.o)("trd-chart", "trade-ui"), _ = W.getI18n, O = (0, he.ud)(), G = O.getI18n, te = (0, v._)(g(c.Ir), 1), J = te[0], oe = (0, v._)(S(k.A7), 2), Te = oe[1], N = (0, se.Ye)(), w = (0, Zt.JD)(s).isContUnit, re = (0, se.y_)(), ie = (0, r.useRef)(N), le = (0, r.useRef)(J), de = (0, r.useRef)(re);
+ (0, r.useEffect)(function() {
+ ie.current = N;
+ }, [N]), (0, r.useEffect)(function() {
+ le.current = J;
+ }, [J]), (0, r.useEffect)(function() {
+ de.current = re;
+ }, [re]);
+ var Be = (0, r.useMemo)(function() {
+ return w ? Rt.b : Rt.S;
+ }, [w]), ve = (0, r.useMemo)(function() {
+ return G("usdtFutures-13", { defaultValue: "Futures" }) || "";
+ }, [G]), Ce = (0, r.useMemo)(function() {
+ return G("inverseFutures-13", { defaultValue: "Delivery" }) || "";
+ }, [G]), ce = (0, r.useMemo)(function() {
+ return _("indexPrice", { defaultValue: "Index Price" }) || "";
+ }, [_]), Ke = (0, r.useMemo)(function() {
+ return _("markPrice", { defaultValue: "Mark Price" }) || "";
+ }, [_]), Ze = (0, r.useMemo)(function() {
+ return _("lastPrice", { defaultValue: "Last Price" }) || "";
+ }, [_]), Ge = (0, r.useMemo)(function() {
+ return { exchanges: [{ name: "Binance", value: "Binance", desc: "" }], symbols_types: [{ name: ve, value: Re.Futures }, { name: "".concat(ve, " ").concat(Ke), value: Re.FuturesMark }, { name: "".concat(ve, " ").concat(ce), value: Re.FuturesIndex }, { name: Ce, value: Re.Delivery }, { name: "".concat(Ce, " ").concat(Ke), value: Re.DeliveryMark }, { name: "".concat(Ce, " ").concat(ce), value: Re.DeliveryIndex }] };
+ }, [Ce, ve, ce, Ke]), Xe = (0, r.useCallback)((0, o._)(function() {
+ var z, Q, ee;
+ return (0, i._)(this, function(ae) {
+ switch (ae.label) {
+ case 0:
+ return ae.trys.push([0, 2, , 3]), [4, (0, en.c)(K)];
+ case 1:
+ return ae.sent(), [3, 3];
+ case 2:
+ return z = ae.sent(), (0, gt.u4)(mn, { type: "onFetchSymbolsInfo", message: z.message, business: "futures" }), [3, 3];
+ case 3:
+ return Q = ie.current, ee = Object.keys(Q).reduce(function(Se, xe) {
+ var ne = Q[xe] || Ct.o, ke = ne.symbol, Me = ne.pricePrecision, fe = ne.isDelivery, Pe = ne.contractStatus;
+ if (!pn.includes(Pe)) return Se;
+ var _e = (0, Yt.S)(ne, G), je = _e.symbol, Ee = _e.contractTypeI18n, me = _e.deliveryDateFormat, ye = "".concat(je, " ").concat(Ee).concat(me ? " ".concat(me) : ""), $e = "".concat(ye, " ").concat(Ze), Oe = "".concat(ye, " ").concat(ce), lr = "".concat(ye, " ").concat(Ke);
+ return Se.concat([Mt({ symbol: ke, description: $e, productType: fe ? Re.Delivery : Re.Futures, pricePrecision: Me, params: { priceType: c.SJ.Last } }), Mt({ symbol: ke, description: Oe, productType: fe ? Re.DeliveryIndex : Re.FuturesIndex, pricePrecision: Me, params: { priceType: c.SJ.Index } }), Mt({ symbol: ke, description: lr, productType: fe ? Re.DeliveryMark : Re.FuturesMark, pricePrecision: (0, Xt.X)(Me), params: { priceType: c.SJ.Mark } })]);
+ }, []), [2, ee];
+ }
+ });
+ }), [G, ce, Ke, Ze, K]), Ie = (0, r.useCallback)(function(z) {
+ var Q = z.ticker, ee = z.symbol, ae = z.interval, Se = z.priceType, xe = z.retryUntil, ne = ie.current[ee] || Ct.o, ke = ne.pair, Me = ne.contractType, fe = ne.contractVal, Pe = ne.isDelivery, _e = Pe ? Be(fe) : function(Oe) {
+ return Oe;
+ }, je = (0, wt.h)({ symbol: ee, pair: ke, contractType: Me, isDelivery: Pe, priceType: Se }), Ee, me, ye = function(Oe) {
+ return zt.y("tv", "".concat(Pe ? "cm" : "um", "|").concat(ee, "|").concat(Se, "|").concat(Oe.interval, "|").concat((Ee = Oe.limit) !== null && Ee !== void 0 ? Ee : "", "|").concat((me = Oe.endTime) !== null && me !== void 0 ? me : ""));
+ };
+ if (xe === void 0) return { transform: _e, onFetchKlineData: je };
+ var $e = H(ae);
+ return { transform: _e, onFetchKlineData: Qt.b(je, { enabled: Jt(xe), key: ye, signal: $e.getSignal(Q), subscribeWake: $e.subscribeWake }) };
+ }, [H, Be]), He = (0, r.useCallback)((function() {
+ var z = (0, o._)(function(Q) {
+ var ee, ae, Se, xe, ne, ke, Me, fe, Pe, _e, je, Ee, me, ye, $e, Oe, lr, Or;
+ return (0, i._)(this, function(dr) {
+ switch (dr.label) {
+ case 0:
+ return ee = Q.binanceInterval, ae = Q.rangeStartDate, Se = Q.rangeEndDate, xe = Q.tickerInfo, ne = Q.symbolInfo, ke = Q.firstDataRequest, $.current = Date.now(), l(), Me = xe.symbol, fe = Me === void 0 ? "" : Me, Pe = xe.params, _e = Pe.priceType, je = _e === void 0 ? le.current : _e, de.current({ symbol: fe, priceType: je }) ? (T(Date.now()), [2, []]) : (Ee = Math.max(0, ae * 1e3), me = tn({ firstDataRequest: ke, hasRequested: f.current, rangeEndDate: Se }), f.current = true, $e = Ie({ ticker: (ye = ne.ticker) !== null && ye !== void 0 ? ye : "", symbol: fe, interval: ee, priceType: je, retryUntil: ke ? me : void 0 }), Oe = $e.transform, lr = $e.onFetchKlineData, [4, It({ onFetchKlineData: lr, startTime: Ee, endTime: me, interval: ee })]);
+ case 1:
+ return Or = dr.sent(), T(Date.now()), [2, Or.map(Oe)];
+ }
+ });
+ });
+ return function(Q) {
+ return z.apply(this, arguments);
+ };
+ })(), [Ie, T, l]), ur = (0, r.useCallback)(function(z) {
+ var Q = z.binanceInterval, ee = z.callback, ae = z.tickerInfo, Se = z.symbolInfo, xe = ae.symbol, ne = xe === void 0 ? "" : xe, ke = ae.params, Me = ke.priceType, fe = Me === void 0 ? le.current : Me;
+ if (de.current({ symbol: ne, priceType: fe })) return function() {
+ return null;
+ };
+ var Pe = ie.current[ne] || Ct.o, _e = Pe.pair, je = Pe.contractType, Ee = Pe.isDelivery, me, ye = (me = Se.ticker) !== null && me !== void 0 ? me : "", $e = Ie({ ticker: ye, symbol: ne, interval: Q, priceType: fe }).transform, Oe = H(Q), lr = Oe.openFeed({ ticker: ye, onTick: ee, subscribeSignals: j(Ee), fetchSince: (function() {
+ var dr = (0, o._)(function(Lr) {
+ var Ur, Yr, ct, De;
+ return (0, i._)(this, function(ze) {
+ switch (ze.label) {
+ case 0:
+ return Ur = Ie({ ticker: ye, symbol: ne, interval: Q, priceType: fe, retryUntil: 0 }), Yr = Ur.transform, ct = Ur.onFetchKlineData, [4, It({ onFetchKlineData: ct, startTime: Lr, endTime: 0, interval: Q })];
+ case 1:
+ return De = ze.sent(), [2, De.map(Yr)];
+ }
+ });
+ });
+ return function(Lr) {
+ return dr.apply(this, arguments);
+ };
+ })() }), Or = (0, rn.B)({ symbol: ne, pair: _e, contractType: je, interval: Q, priceType: fe, isDelivery: Ee, useMiniTicker: u, callback: function(dr) {
+ lr.push($e(dr));
+ } });
+ return function() {
+ lr.close(), Or();
+ };
+ }, [Ie, H, j, u]), Tr = (0, r.useCallback)((0, o._)(function() {
+ var z;
+ return (0, i._)(this, function(Q) {
+ switch (Q.label) {
+ case 0:
+ return [4, K.fetchQuery(Wt.Bz.SERVER_TIME(s), function() {
+ return (0, Gt.l)(s);
+ }, {})];
+ case 1:
+ return z = Q.sent(), [2, Math.floor(z / 1e3)];
+ }
+ });
+ }), [K, s]), _r = (0, r.useCallback)(function(z) {
+ return (0, cr._)({}, z.template, (0, qt.sp)(s));
+ }, [s]), Cr = (0, r.useMemo)(function() {
+ return ue();
+ }, []), Er = Cr({ config: Ge, onFetchSymbolsInfo: Xe, onFetchBars: He, onSubscribeBars: ur, onFetchServerTimeApi: Tr, onTransformSymbolInfo: _r }), we = Er.datafeed, Dr = Er.symbolsInfo;
+ return (0, Ut.A)(function() {
+ return Te();
+ }, [w]), { datafeed: we, symbolsInfo: Dr };
+ }, Pt = "trd_chart_tv_load_timeout", hn = function() {
+ var t = (0, We.PM)({ id: "chartReady" }), n = (0, We.PM)({ id: "getBarsStart", timeout: 3 }), a = (0, We.PM)({ id: "getBarsEnd", timeout: 6 }), u = (0, We.PM)({ id: "chartResponsive", timeout: 3 });
+ We.HP.init({ step: t._(n)._(a)._(u), eventId: Pt }), (0, We.HP)({ eventId: Pt, stepId: "chartReady", catch: function(g) {
+ var S = g.error, K = g.track;
+ K({ CHART_READY_TYPE: S, connection: JSON.stringify(navigator.connection) });
+ }, log: function(g) {
+ var S = g.track;
+ S({ CHART_READY_TYPE: "SUCCESS", connection: JSON.stringify(navigator.connection) });
+ } });
+ }, gn = function(t) {
+ (0, We.HP)({ eventId: Pt, stepId: "chartResponsive", catch: function(a) {
+ var u = a.error, s = a.track;
+ s({ CHART_RESPONSIVE_TYPE: u, connection: JSON.stringify(navigator.connection) });
+ }, log: function(a) {
+ var u = a.track;
+ u({ CHART_RESPONSIVE_TYPE: "SUCCESS", duration_: (0, We.Pu)(Date.now() - t) });
+ } });
+ }, Tn = [Qe.ZE, Qe.F3], Dt = function(t) {
+ var n = t.symbol, a = t.namespace, u = t.useMiniTicker, s = u === void 0 ? false : u, g = c.y$(a), S = k.y$(a), K = (0, v._)(g(c.A8), 1), Y = K[0], H = (0, v._)(g(c.Ir), 2), j = H[0], $ = H[1], f = (0, v._)(S(k.yj), 1), l = f[0], T = (0, v._)(g(c.e6), 1), W = T[0], _ = (0, v._)(g(c._b), 1), O = _[0], G = (0, he.ot)(), te = (0, pr.K7)(), J = te.pathname.includes(Ae.Q2), oe = te.pathname.includes(Ae.eq), Te = (0, r.useMemo)(function() {
+ return J ? (0, d._)(B.oT).concat(["hide_left_toolbar_by_default"]) : B.oT;
+ }, [J]), N = (0, se.Ye)(), w = (0, se.v3)({ symbol: n }), re = (0, Fe.es)(n), ie = yn({ namespace: a, useMiniTicker: s, isCM: re }), le = ie.datafeed, de = ie.symbolsInfo, Be = (0, Je.DP)(), ve = Be.theme, Ce = ve === "light" || ve === "light_glacier" ? "light" : "dark", ce = (0, yr.Z)(Ce), Ke = (0, r.useRef)(-1), Ze = Ht({ symbol: n }).positionLines, Ge = Nt({ symbol: n, namespace: a }).orderLines, Xe = ir({ symbol: n }).executionOrders, Ie = pt({ namespace: a, symbol: n }), He = (0, se.y0)(), ur = (0, r.useRef)(He), Tr = (0, r.useRef)(N), _r = (0, r.useRef)(l), Cr = (0, r.useRef)(J), Er = (0, r.useRef)(oe), we = (0, r.useRef)(de);
+ (0, r.useEffect)(function() {
+ we.current = de;
+ }, [de]), (0, r.useEffect)(function() {
+ ur.current = He;
+ }, [He]), (0, r.useEffect)(function() {
+ Tr.current = N;
+ }, [N]), (0, r.useEffect)(function() {
+ _r.current = l;
+ }, [l]), (0, r.useEffect)(function() {
+ Cr.current = J;
+ }, [J]), (0, r.useEffect)(function() {
+ Er.current = oe;
+ }, [oe]), (0, Ve.a)(n, s), (0, sr.A)({ symbol: n, interval: Y, priceType: j });
+ var Dr = (0, r.useCallback)(function() {
+ Ke.current = Date.now();
+ }, []), z = (0, r.useCallback)((0, o._)(function() {
+ var De, ze, qe, be, vr, fr, er;
+ return (0, i._)(this, function(nr) {
+ switch (nr.label) {
+ case 0:
+ if (Cr.current || !_r.current) return [2, Promise.resolve()];
+ nr.label = 1;
+ case 1:
+ return nr.trys.push([1, 5, , 6]), De = h().createInstance({ name: a }), [4, De.keys()];
+ case 2:
+ return ze = nr.sent(), qe = ze.filter(function(mr) {
+ return /^((#TV_SYMBOL-)|(myTradingView))/g.test(mr);
+ }), be = qe.map((function() {
+ var mr = (0, o._)(function(ar) {
+ return (0, i._)(this, function(Sr) {
+ return [2, De.getItem(ar)];
+ });
+ });
+ return function(ar) {
+ return mr.apply(this, arguments);
+ };
+ })()), [4, Promise.all(be)];
+ case 3:
+ return vr = nr.sent(), fr = U()(qe, vr), [4, Promise.all(Tn.map((function() {
+ var mr = (0, o._)(function(ar) {
+ var Sr, Wr;
+ return (0, i._)(this, function(Gr) {
+ switch (Gr.label) {
+ case 0:
+ return a === ar ? [2, Promise.resolve()] : (Sr = h().createInstance({ name: ar }), Wr = __dispatchChartMirror(Sr, fr, () => fr.map((function() {
+ var Sn = (0, o._)(function(Lt) {
+ var _t, Ot, At;
+ return (0, i._)(this, function(Mn) {
+ return _t = (0, v._)(Lt, 2), Ot = _t[0], At = _t[1], Ot ? [2, Sr.setItem(Ot, At)] : [2, Promise.resolve()];
+ });
+ });
+ return function(Lt) {
+ return Sn.apply(this, arguments);
+ };
+ })())), [4, Promise.all(Wr)]);
+ case 1:
+ return Gr.sent(), [2, Promise.resolve()];
+ }
+ });
+ });
+ return function(ar) {
+ return mr.apply(this, arguments);
+ };
+ })()))];
+ case 4:
+ return nr.sent(), [2, Promise.resolve()];
+ case 5:
+ return er = nr.sent(), console.warn(typeof er == "string" ? er : er.message), [2, Promise.resolve()];
+ case 6:
+ return [2];
+ }
+ });
+ }), [a]), Q = (0, r.useMemo)(function() {
+ return z;
+ }, [z]), ee = (0, or.R)(), ae = (0, Ne.fQ)(a), Se = (0, Ne.m3)(a), xe = (0, Ne.S_)(a), ne = (0, Ne.Ds)(a), ke = (0, r.useRef)(n), Me = (0, r.useRef)(Y), fe = (0, r.useCallback)(function() {
+ return ee("tradingView"), xe({ panel_index: 0, symbol: n, interval: Y }), hn();
+ }, [Y, ee, n, xe]), Pe = (0, r.useCallback)(function(De) {
+ var ze = De.index, qe = De.chart, be, vr, fr, er = (fr = (vr = (be = qe.symbolExt()) === null || be === void 0 ? void 0 : be.name) !== null && vr !== void 0 ? vr : qe.symbol()) !== null && fr !== void 0 ? fr : "";
+ ae({ panel_index: ze, symbol: er, previous_symbol: ke.current }), ke.current = er;
+ }, [ae]), _e = (0, r.useCallback)(function(De) {
+ var ze = De.index, qe = De.chart, be = qe.resolution();
+ Se({ panel_index: ze, interval: be, previous_interval: Me.current }), Me.current = be;
+ }, [Se]), je = (0, r.useCallback)(function() {
+ gn(Ke.current);
+ }, []), Ee = (0, r.useCallback)(function() {
+ }, []), me = (0, r.useMemo)(function() {
+ return J ? Ee : void 0;
+ }, [Ee, J]), ye = (0, r.useCallback)((function() {
+ var De = (0, o._)(function(ze) {
+ var qe, be, vr, fr, er, nr, mr, ar, Sr;
+ return (0, i._)(this, function(Wr) {
+ switch (Wr.label) {
+ case 0:
+ return qe = ze.chart, be = qe.symbolExt(), be ? (vr = b()(we.current, function(Gr) {
+ return Gr.ticker === be.ticker || Gr.symbol === be.ticker;
+ }), fr = vr || {}, er = fr.params, nr = er === void 0 ? {} : er, mr = nr.priceType, ar = mr === void 0 ? "" : mr, ar && $(ar), Sr = Tr.current[be.name], Sr ? Er.current || Cr.current ? [2] : [4, z()] : [2]) : [2];
+ case 1:
+ return Wr.sent(), G({ stateSymbol: Sr.symbol, isDeliveryColumn: Sr.isDelivery }), [2];
+ }
+ });
+ });
+ return function(ze) {
+ return De.apply(this, arguments);
+ };
+ })(), [G, z, $]), $e = qr(), Oe = $e.onCrosshairMoved, lr = $e.crosshairPrice, Or = wr({ namespace: a, symbol: n, priceType: j, crosshairPrice: lr }), dr = lt(), Lr = (0, r.useMemo)(function() {
+ return L({ symbol: n, params: { priceType: j } });
+ }, [n, j]), Ur = (0, r.useMemo)(function() {
+ return { symbol: Lr, theme: ce, limitOrders: Ge, positionOrders: Ze, executionOrders: Xe, labelLines: Ie, initialConfig: { tvConfig: { symbol: Lr, library_path: "/".concat(Ye.u_, "/trading-platform-30/"), theme: ce, datafeed: le, enabled_features: Te, context_menu: Or, custom_formatters: dr }, onCrosshairMoved: Oe, onScriptLoaded: Dr, onSave: Q, onChartReadyDone: fe, onFirstTimeDataLoadedDone: je, onInitChart: me, onActiveChartChanged: ye, onSymbolChanged: Pe, onIntervalChanged: _e } };
+ }, [Lr, le, Te, Xe, Ie, Or, dr, Oe, ye, fe, je, me, _e, Q, Dr, Pe, Ge, Ze, ce]), Yr = Le({ namespace: a, type: "tv" }), ct = (0, r.useCallback)(function() {
+ ne({ panel_index: 0, symbol: n }), Yr();
+ }, [Yr, n, ne]);
+ return (0, p.jsxs)(D.t, { name: "trading_view", onReset: ct, children: [(0, p.jsx)(Ue.A, { namespace: a, tradingViewProps: Ur }), w && j === "index" && O === c.Ev.Single && W === c.tU.TradingView && (0, p.jsx)(rr.B, {})] });
+ };
+ const Cn = Dt;
+ } }[70940];
+ }
+ function replaceChartMirrorFactory(originalFactory, dispatch) {
+ if (typeof originalFactory !== "function" || Function.prototype.toString.call(originalFactory) !== originalFactorySource) {
+ throw new Error("Mirror factory source does not match the pinned public module");
+ }
+ return createMirrorFactory(dispatch);
+ }
+
+ // src/binance-orderbook-trade/chart-storage/json.js
+ var encoder = new TextEncoder();
+ var nativeObjectSource = Function.prototype.toString.call(Object);
+ function assert(condition, message) {
+ if (!condition) throw new Error(message);
+ }
+ function isPlainJsonObject(value) {
+ const prototype = Object.getPrototypeOf(value);
+ if (prototype === null || Object.getPrototypeOf(prototype) !== null) return false;
+ const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor");
+ if (!constructor || !Object.hasOwn(constructor, "value") || typeof constructor.value !== "function") return false;
+ if (Function.prototype.toString.call(constructor.value) !== nativeObjectSource) return false;
+ const descriptor = Object.getOwnPropertyDescriptor(constructor.value, "prototype");
+ return !!descriptor && Object.hasOwn(descriptor, "value") && descriptor.value === prototype;
+ }
+ function byteLength(value) {
+ return encoder.encode(JSON.stringify(value)).byteLength;
+ }
+ function snapshotJson(value, budget = { remaining: Infinity }, seen = /* @__PURE__ */ new Set()) {
+ function consume(bytes) {
+ budget.remaining -= bytes;
+ assert(budget.remaining >= 0, "Chart JSON byte limit exceeded");
+ }
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
+ assert(typeof value !== "number" || Number.isFinite(value), "Chart JSON numbers must be finite");
+ consume(byteLength(value));
+ return value;
+ }
+ assert(typeof value === "object", "Chart values must be JSON");
+ assert(!seen.has(value), "Chart JSON must not contain shared references or cycles");
+ const array = Array.isArray(value);
+ assert(array || isPlainJsonObject(value), "Chart values must be plain JSON");
+ const descriptors = Object.getOwnPropertyDescriptors(value);
+ assert(Object.getOwnPropertySymbols(value).length === 0, "Chart JSON must not contain symbol properties");
+ const keys = Object.keys(descriptors).filter((key) => !(array && key === "length"));
+ if (array) assert(keys.length === value.length && keys.every((key, index) => key === String(index)), "Chart JSON arrays must be dense");
+ seen.add(value);
+ consume(2 + Math.max(0, keys.length - 1));
+ const result = array ? [] : {};
+ for (const key of keys) {
+ const descriptor = descriptors[key];
+ assert(descriptor.enumerable && Object.hasOwn(descriptor, "value"), "Chart JSON requires enumerable data properties");
+ if (!array) consume(byteLength(key) + 1);
+ Object.defineProperty(result, key, {
+ value: snapshotJson(descriptor.value, budget, seen),
+ enumerable: true,
+ writable: true,
+ configurable: true
+ });
+ }
+ return result;
+ }
+ function equalJson(first, second) {
+ if (Object.is(first, second)) return true;
+ if (first === null || second === null || typeof first !== "object" || typeof second !== "object") return false;
+ if (Array.isArray(first) !== Array.isArray(second)) return false;
+ const firstKeys = Object.keys(first);
+ const secondKeys = Object.keys(second);
+ return firstKeys.length === secondKeys.length && firstKeys.every((key, index) => key === secondKeys[index] && equalJson(first[key], second[key]));
+ }
+
+ // src/binance-orderbook-trade/chart-storage/mirror-writer.js
+ var DEFAULT_LIMITS = Object.freeze({
+ maxEntries: 512,
+ maxBatchBytes: 4 * 1024 * 1024,
+ maxReadBytes: 4 * 1024 * 1024,
+ maxPendingBatches: 16,
+ maxPendingBytes: 64 * 1024 * 1024
+ });
+ function assert2(condition, message) {
+ if (!condition) throw new Error(message);
+ }
+ function createChartMirrorWriter({ limits: suppliedLimits = {} } = {}) {
+ const limits = { ...DEFAULT_LIMITS, ...suppliedLimits };
+ for (const [key, value] of Object.entries(limits)) {
+ assert2(Object.hasOwn(DEFAULT_LIMITS, key), `Unknown mirror limit: ${key}`);
+ assert2(Number.isSafeInteger(value) && value > 0, `${key} must be a positive safe integer`);
+ }
+ let phase = "active";
+ let stopping;
+ let resolveDrain;
+ const stats = {
+ acceptedBatches: 0,
+ rejectedBatches: 0,
+ failedBatches: 0,
+ pendingBatches: 0,
+ pendingBytes: 0,
+ peakPendingBatches: 0,
+ peakPendingBytes: 0,
+ transactions: 0,
+ committedTransactions: 0,
+ abortedTransactions: 0,
+ committedWrites: 0,
+ skippedWrites: 0,
+ nativePassthroughBatches: 0
+ };
+ function stop() {
+ if (phase === "active") {
+ phase = "draining";
+ const drained = stats.pendingBatches === 0 ? Promise.resolve() : new Promise((resolve) => {
+ resolveDrain = resolve;
+ });
+ stopping = drained.then(() => {
+ phase = "stopped";
+ });
+ }
+ return stopping;
+ }
+ function release(work) {
+ stats.pendingBatches -= 1;
+ stats.pendingBytes -= work.bytes;
+ if (phase === "draining" && stats.pendingBatches === 0) resolveDrain();
+ }
+ function nativeBeforeTransaction(work, nativeThunk) {
+ stats.rejectedBatches += 1;
+ stats.nativePassthroughBatches += 1;
+ stats.pendingBytes -= work.bytes;
+ work.bytes = 0;
+ let results;
+ try {
+ results = nativeThunk();
+ } catch (error) {
+ release(work);
+ return Promise.reject(error);
+ }
+ Promise.allSettled(results).then(() => release(work));
+ return Promise.all(results);
+ }
+ function transact(transaction, entries) {
+ return new Promise((resolve, reject) => {
+ let index = 0;
+ let writes = 0;
+ let skipped = 0;
+ let failure;
+ const budget = { remaining: limits.maxReadBytes };
+ transaction.oncomplete = () => {
+ stats.committedTransactions += 1;
+ stats.committedWrites += writes;
+ stats.skippedWrites += skipped;
+ resolve();
+ };
+ transaction.onabort = () => {
+ stats.abortedTransactions += 1;
+ reject(failure || transaction.error || new DOMException("Chart mirror batch aborted", "AbortError"));
+ };
+ transaction.onerror = (event) => {
+ failure = event.target.error;
+ };
+ function guard(action) {
+ try {
+ action();
+ } catch (error) {
+ failure = error;
+ try {
+ transaction.abort();
+ } catch (abortError) {
+ if (abortError instanceof DOMException && abortError.name === "InvalidStateError") return;
+ throw abortError;
+ }
+ }
+ }
+ let store;
+ function advance() {
+ if (index === entries.length) return;
+ const [key, value] = entries[index];
+ function next() {
+ index += 1;
+ advance();
+ }
+ function put() {
+ const request = store.put(value === null ? void 0 : value, key);
+ writes += 1;
+ request.onsuccess = () => guard(next);
+ }
+ if (budget.remaining <= 0) {
+ put();
+ return;
+ }
+ const read = store.get(key);
+ read.onsuccess = () => guard(() => {
+ let current;
+ try {
+ current = snapshotJson(read.result === void 0 ? null : read.result, budget);
+ } catch {
+ budget.remaining = 0;
+ put();
+ return;
+ }
+ function writeIfChanged(present) {
+ if (present && equalJson(current, value)) {
+ skipped += 1;
+ next();
+ } else put();
+ }
+ if (read.result === void 0 && value === null) {
+ const exists = store.getKey(key);
+ exists.onsuccess = () => guard(() => writeIfChanged(exists.result !== void 0));
+ } else writeIfChanged(read.result !== void 0);
+ });
+ }
+ guard(() => {
+ store = transaction.objectStore("keyvaluepairs");
+ advance();
+ });
+ });
+ }
+ async function write(target, entries, work, nativeThunk) {
+ let transaction;
+ try {
+ await target.ready();
+ assert2(target.driver() === "asyncStorage", "Chart mirror requires the native IndexedDB driver");
+ const info = target._dbInfo;
+ assert2(
+ (info.name === "chart_futures" || info.name === "chart_delivery") && info.storeName === "keyvaluepairs",
+ "Unexpected chart mirror database configuration"
+ );
+ transaction = info.db.transaction("keyvaluepairs", "readwrite");
+ } catch {
+ return nativeBeforeTransaction(work, nativeThunk);
+ }
+ stats.acceptedBatches += 1;
+ stats.transactions += 1;
+ try {
+ await transact(transaction, entries);
+ } catch (error) {
+ stats.failedBatches += 1;
+ stop();
+ throw error;
+ } finally {
+ release(work);
+ }
+ }
+ function dispatch(target, entries, nativeThunk) {
+ assert2(typeof nativeThunk === "function", "Chart mirror dispatch requires its original native expression");
+ if (phase === "stopped") return nativeThunk();
+ if (phase === "draining") return [stopping.then(() => Promise.all(nativeThunk()))];
+ const work = { bytes: 0 };
+ stats.pendingBatches += 1;
+ let copied;
+ try {
+ assert2(Array.isArray(entries), "Chart mirror entries must be an array");
+ if (entries.length === 0) {
+ release(work);
+ return [];
+ }
+ stats.peakPendingBatches = Math.max(stats.peakPendingBatches, stats.pendingBatches);
+ assert2(entries.length <= limits.maxEntries, "Chart mirror entry limit exceeded");
+ assert2(stats.pendingBatches <= limits.maxPendingBatches, "Chart mirror pending batch limit exceeded");
+ const budget = { remaining: limits.maxBatchBytes };
+ const seen = /* @__PURE__ */ new Set();
+ const keys = /* @__PURE__ */ new Set();
+ copied = entries.map((entry) => {
+ assert2(Array.isArray(entry) && entry.length === 2, "Chart mirror entry must be a key/value pair");
+ const [key, value] = entry;
+ assert2(
+ typeof key === "string" && (key.startsWith("#TV_SYMBOL-") || key.startsWith("myTradingView")),
+ "Chart mirror received a non-chart key"
+ );
+ assert2(!keys.has(key), "Chart mirror keys must be unique");
+ keys.add(key);
+ budget.remaining -= byteLength(key) + 3;
+ return [key, snapshotJson(value, budget, seen)];
+ });
+ const bytes = byteLength(copied);
+ assert2(bytes <= limits.maxBatchBytes, "Chart mirror batch byte limit exceeded");
+ assert2(stats.pendingBytes + bytes <= limits.maxPendingBytes, "Chart mirror pending byte limit exceeded");
+ work.bytes = bytes;
+ stats.pendingBytes += bytes;
+ stats.peakPendingBytes = Math.max(stats.peakPendingBytes, stats.pendingBytes);
+ } catch {
+ return [nativeBeforeTransaction(work, nativeThunk)];
+ }
+ return [write(target, copied, work, nativeThunk)];
+ }
+ return Object.freeze({ dispatch, getStats: () => ({ ...stats }), getPhase: () => phase, stop });
+ }
+
+ // src/binance-orderbook-trade/chart-storage/runtime.js
+ var CAPTURE_DEADLINE_MS = 3e4;
+ function startChartStorageOptimizer() {
+ if (!isChartStoragePage()) throw new Error("Chart storage requires a top-level Binance trading page");
+ const writer = createChartMirrorWriter();
+ const state = { status: "waiting", reason: null, attempts: 0, matches: 0, executions: 0 };
+ let observer;
+ let timer;
+ let stopping;
+ function snapshot() {
+ const stats = writer.getStats();
+ return {
+ ...state,
+ status: stats.failedBatches > 0 ? "native_after_failure" : state.status,
+ phase: writer.getPhase(),
+ writer: stats
+ };
+ }
+ function stop(reason = "manual") {
+ if (stopping) return stopping;
+ state.status = "stopping";
+ state.reason = reason;
+ clearTimeout(timer);
+ self.removeEventListener("pagehide", onPageHide);
+ if (observer) observer.stop();
+ stopping = writer.stop().then(() => {
+ state.status = "native";
+ return snapshot();
+ });
+ return stopping;
+ }
+ function onPageHide() {
+ void stop("pagehide");
+ }
+ function dispatch(target, entries, nativeThunk) {
+ if (!isChartStoragePage()) void stop("scope_changed");
+ return writer.dispatch(target, entries, nativeThunk);
+ }
+ try {
+ observer = observeChartStorageBootstrap({
+ replaceMirrorFactory(original) {
+ if (!isChartStoragePage()) {
+ void stop("scope_changed");
+ return original;
+ }
+ state.attempts += 1;
+ let replacement;
+ try {
+ replacement = replaceChartMirrorFactory(original, dispatch);
+ } catch {
+ void stop("source_mismatch");
+ return original;
+ }
+ state.matches += 1;
+ return function(...args) {
+ state.executions += 1;
+ return Reflect.apply(replacement, this, args);
+ };
+ },
+ onCapture() {
+ clearTimeout(timer);
+ state.status = "active";
+ }
+ });
+ } catch {
+ void stop("bootstrap_unavailable");
+ return Object.freeze({ snapshot, stop });
+ }
+ observer.captured.catch(() => {
+ if (!stopping) void stop("capture_failed");
+ });
+ self.addEventListener("pagehide", onPageHide, { once: true });
+ timer = setTimeout(() => {
+ void stop("capture_deadline");
+ }, CAPTURE_DEADLINE_MS);
+ return Object.freeze({ snapshot, stop });
+ }
+
+ // src/binance-orderbook-trade/chart-storage/install.js
+ function installChartStorageOptimizer() {
+ if (!isChartStoragePage() || Object.hasOwn(self, "__BINANCE_CHART_STORAGE__")) return;
+ Object.defineProperty(self, "__BINANCE_CHART_STORAGE__", {
+ value: startChartStorageOptimizer(),
+ configurable: true
+ });
+ }
+
// src/binance-orderbook-trade/index.user.js
+ installChartStorageOptimizer();
(function initializeOrderbookTrade() {
"use strict";
+ if (!document.documentElement) {
+ const rootObserver = new MutationObserver(() => {
+ if (!document.documentElement) return;
+ rootObserver.disconnect();
+ initializeOrderbookTrade();
+ });
+ rootObserver.observe(document, { childList: true });
+ return;
+ }
function isFuturesTradingPage() {
return isFuturesTradingPathname(location.pathname);
}
diff --git a/src/binance-orderbook-trade/chart-storage/bootstrap.js b/src/binance-orderbook-trade/chart-storage/bootstrap.js
new file mode 100644
index 0000000..fbded35
--- /dev/null
+++ b/src/binance-orderbook-trade/chart-storage/bootstrap.js
@@ -0,0 +1,161 @@
+import { isChartStoragePage } from './scope.js';
+
+const QUEUE_NAME = 'webpackChunkfutures_trade_ui';
+
+/**
+ * Observe first module execution before Rspack installs its chunk dispatcher.
+ * By default this only captures localForage. An explicit mirror replacer selects
+ * module 70940 and runs before registration; it never calls a storage method.
+ * The caller must enter before the host runtime on a supported trading page.
+ */
+export function observeChartStorageBootstrap({ onCapture, replaceMirrorFactory } = {}) {
+ if (!isChartStoragePage()) throw new Error('Storage bootstrap requires a top-level Binance trading page');
+ if (onCapture !== undefined && typeof onCapture !== 'function') throw new Error('onCapture must be a synchronous function');
+ if (replaceMirrorFactory !== undefined && typeof replaceMirrorFactory !== 'function') throw new Error('replaceMirrorFactory must be a synchronous function');
+ const moduleId = replaceMirrorFactory === undefined ? '43917' : '70940';
+
+ const globalDescriptor = Object.getOwnPropertyDescriptor(self, QUEUE_NAME);
+ if (globalDescriptor && !Object.hasOwn(globalDescriptor, 'value')) throw new Error('Storage bootstrap requires a native queue property');
+ const queue = globalDescriptor && globalDescriptor.value !== undefined ? globalDescriptor.value : [];
+ const nativeAppend = Array.prototype.push;
+ if (!Array.isArray(queue) || queue.length !== 0 || queue.push !== nativeAppend || Object.hasOwn(queue, 'push')) {
+ throw new Error('Storage bootstrap must start before runtime on a native empty queue');
+ }
+ if (globalDescriptor && globalDescriptor.value === undefined && !globalDescriptor.configurable) {
+ throw new Error('Storage bootstrap cannot own the existing global queue property');
+ }
+ const createdQueue = !globalDescriptor || globalDescriptor.value === undefined;
+ if (createdQueue) Object.defineProperty(self, QUEUE_NAME, { value: queue, writable: true, configurable: true, enumerable: true });
+
+ let runtimePush;
+ let inFlightAppends = 0;
+ let active = true;
+ let originalFactory;
+ let observedFactory;
+ let observedChunk;
+ let runtimeRequire;
+ let resolveCapture;
+ let rejectCapture;
+ const captured = new Promise((resolve, reject) => { resolveCapture = resolve; rejectCapture = reject; });
+
+ function restore() {
+ if (runtimeRequire && runtimeRequire.m[moduleId] === observedFactory) runtimeRequire.m[moduleId] = originalFactory;
+ if (observedChunk && observedChunk[1][moduleId] === observedFactory) observedChunk[1][moduleId] = originalFactory;
+ const descriptor = Object.getOwnPropertyDescriptor(queue, 'push');
+ if (!descriptor || descriptor.get !== getPush || descriptor.set !== setPush || !descriptor.configurable) {
+ throw new Error('Storage bootstrap lost queue accessor ownership');
+ }
+ if (runtimePush) {
+ Object.defineProperty(queue, 'push', { value: runtimePush, writable: true, configurable: true, enumerable: true });
+ } else {
+ delete queue.push;
+ if (createdQueue && inFlightAppends === 0 && queue.length === 0 && Object.getOwnPropertyDescriptor(self, QUEUE_NAME)?.value === queue) {
+ if (globalDescriptor) Object.defineProperty(self, QUEUE_NAME, globalDescriptor);
+ else delete self[QUEUE_NAME];
+ }
+ }
+ }
+
+ /** Observational failure is isolated from Rspack's persistent module-error cache. */
+ function finish(succeeded, result) {
+ if (!active) return;
+ active = false;
+ try {
+ restore();
+ } catch (cleanupError) {
+ if (succeeded) {
+ succeeded = false;
+ result = cleanupError;
+ }
+ }
+ if (succeeded) resolveCapture(result);
+ else rejectCapture(result);
+ }
+
+ function prepareChunk(chunk) {
+ if (!active) return chunk;
+ try {
+ if (!Array.isArray(chunk) || !Array.isArray(chunk[0]) || !chunk[1] || typeof chunk[1] !== 'object') {
+ throw new Error('Storage bootstrap observed an unsupported chunk shape');
+ }
+ if (!Object.hasOwn(chunk[1], moduleId)) return chunk;
+ if (originalFactory) throw new Error('Storage module registered twice before capture');
+ originalFactory = chunk[1][moduleId];
+ if (typeof originalFactory !== 'function') throw new Error('Storage module factory must be a function');
+ const hostRuntime = chunk[2];
+ if (hostRuntime !== undefined && typeof hostRuntime !== 'function') throw new Error('Storage chunk runtime callback must be a function');
+ const executionFactory = replaceMirrorFactory === undefined ? originalFactory : replaceMirrorFactory(originalFactory);
+ if (!active) return chunk;
+ if (typeof executionFactory !== 'function') throw new Error('Mirror replacement must return a factory synchronously');
+ observedFactory = function (module, exports, require) {
+ runtimeRequire = require;
+ let returned;
+ try {
+ returned = executionFactory.call(this, module, exports, require);
+ } catch (hostError) {
+ finish(false, hostError);
+ throw hostError;
+ }
+ if (active) {
+ try {
+ const result = onCapture === undefined ? undefined : onCapture(module.exports);
+ if (result && typeof result.then === 'function') throw new Error('Storage observation callback must complete synchronously');
+ finish(true, module.exports);
+ } catch (observationError) {
+ finish(false, observationError);
+ }
+ }
+ return returned;
+ };
+ observedChunk = [chunk[0], { ...chunk[1], [moduleId]: observedFactory }, require => {
+ runtimeRequire = require;
+ if (hostRuntime !== undefined) return hostRuntime(require);
+ }];
+ return observedChunk;
+ } catch (observationError) {
+ // Invalid or duplicate registrations still reach the host's original dispatcher.
+ finish(false, observationError);
+ return chunk;
+ }
+ }
+
+ /** Rspack retains this parent callback; after takeover it must only append. */
+ function preRuntimePush(...chunks) {
+ if (!active || runtimePush) return nativeAppend.apply(this, chunks);
+ inFlightAppends += 1;
+ try {
+ return nativeAppend.apply(this, chunks.map(prepareChunk));
+ } finally {
+ inFlightAppends -= 1;
+ }
+ }
+
+ function runtimeObservingPush(chunk) {
+ return runtimePush.call(this, prepareChunk(chunk));
+ }
+
+ function getPush() {
+ return runtimePush ? runtimeObservingPush : preRuntimePush;
+ }
+
+ function setPush(dispatcher) {
+ if (!active) {
+ Object.defineProperty(queue, 'push', { value: dispatcher, writable: true, configurable: true, enumerable: true });
+ return;
+ }
+ if (runtimePush || typeof dispatcher !== 'function') {
+ finish(false, new Error('Storage bootstrap observed an unexpected push replacement'));
+ Object.defineProperty(queue, 'push', { value: dispatcher, writable: true, configurable: true, enumerable: true });
+ return;
+ }
+ runtimePush = dispatcher;
+ }
+
+ Object.defineProperty(queue, 'push', { get: getPush, set: setPush, enumerable: true, configurable: true });
+ return Object.freeze({
+ captured,
+ stop() {
+ finish(false, new Error('Storage bootstrap stopped before module execution'));
+ },
+ });
+}
diff --git a/src/binance-orderbook-trade/chart-storage/install.js b/src/binance-orderbook-trade/chart-storage/install.js
new file mode 100644
index 0000000..ff71d4c
--- /dev/null
+++ b/src/binance-orderbook-trade/chart-storage/install.js
@@ -0,0 +1,10 @@
+import { startChartStorageOptimizer } from './runtime.js';
+import { isChartStoragePage } from './scope.js';
+
+/** Run before orderbook initialization without widening its frame or route policy. */
+export function installChartStorageOptimizer() {
+ if (!isChartStoragePage() || Object.hasOwn(self, '__BINANCE_CHART_STORAGE__')) return;
+ Object.defineProperty(self, '__BINANCE_CHART_STORAGE__', {
+ value: startChartStorageOptimizer(), configurable: true,
+ });
+}
diff --git a/src/binance-orderbook-trade/chart-storage/json.js b/src/binance-orderbook-trade/chart-storage/json.js
new file mode 100644
index 0000000..2181628
--- /dev/null
+++ b/src/binance-orderbook-trade/chart-storage/json.js
@@ -0,0 +1,69 @@
+const encoder = new TextEncoder();
+const nativeObjectSource = Function.prototype.toString.call(Object);
+
+function assert(condition, message) {
+ if (!condition) throw new Error(message);
+}
+
+/** TradingView snapshots may originate in an iframe with a different Object prototype. */
+function isPlainJsonObject(value) {
+ const prototype = Object.getPrototypeOf(value);
+ if (prototype === null || Object.getPrototypeOf(prototype) !== null) return false;
+ const constructor = Object.getOwnPropertyDescriptor(prototype, 'constructor');
+ if (!constructor || !Object.hasOwn(constructor, 'value') || typeof constructor.value !== 'function') return false;
+ if (Function.prototype.toString.call(constructor.value) !== nativeObjectSource) return false;
+ const descriptor = Object.getOwnPropertyDescriptor(constructor.value, 'prototype');
+ return !!descriptor && Object.hasOwn(descriptor, 'value') && descriptor.value === prototype;
+}
+
+export function byteLength(value) {
+ return encoder.encode(JSON.stringify(value)).byteLength;
+}
+
+/**
+ * Snapshot only JSON trees without invoking getters or changing structured-clone
+ * identity. A global seen set rejects aliases as well as cycles. The shared byte
+ * budget bounds traversal before cloning the rest of an oversized snapshot.
+ */
+export function snapshotJson(value, budget = { remaining: Infinity }, seen = new Set()) {
+ function consume(bytes) {
+ budget.remaining -= bytes;
+ assert(budget.remaining >= 0, 'Chart JSON byte limit exceeded');
+ }
+ if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number') {
+ assert(typeof value !== 'number' || Number.isFinite(value), 'Chart JSON numbers must be finite');
+ consume(byteLength(value));
+ return value;
+ }
+ assert(typeof value === 'object', 'Chart values must be JSON');
+ assert(!seen.has(value), 'Chart JSON must not contain shared references or cycles');
+ const array = Array.isArray(value);
+ assert(array || isPlainJsonObject(value), 'Chart values must be plain JSON');
+ const descriptors = Object.getOwnPropertyDescriptors(value);
+ assert(Object.getOwnPropertySymbols(value).length === 0, 'Chart JSON must not contain symbol properties');
+ const keys = Object.keys(descriptors).filter(key => !(array && key === 'length'));
+ if (array) assert(keys.length === value.length && keys.every((key, index) => key === String(index)), 'Chart JSON arrays must be dense');
+ seen.add(value);
+ consume(2 + Math.max(0, keys.length - 1));
+ const result = array ? [] : {};
+ for (const key of keys) {
+ const descriptor = descriptors[key];
+ assert(descriptor.enumerable && Object.hasOwn(descriptor, 'value'), 'Chart JSON requires enumerable data properties');
+ if (!array) consume(byteLength(key) + 1);
+ Object.defineProperty(result, key, {
+ value: snapshotJson(descriptor.value, budget, seen), enumerable: true, writable: true, configurable: true,
+ });
+ }
+ return result;
+}
+
+/** Signed zero and property insertion order remain observable through native reads. */
+export function equalJson(first, second) {
+ if (Object.is(first, second)) return true;
+ if (first === null || second === null || typeof first !== 'object' || typeof second !== 'object') return false;
+ if (Array.isArray(first) !== Array.isArray(second)) return false;
+ const firstKeys = Object.keys(first);
+ const secondKeys = Object.keys(second);
+ return firstKeys.length === secondKeys.length
+ && firstKeys.every((key, index) => key === secondKeys[index] && equalJson(first[key], second[key]));
+}
diff --git a/src/binance-orderbook-trade/chart-storage/mirror-module.js b/src/binance-orderbook-trade/chart-storage/mirror-module.js
new file mode 100644
index 0000000..48b4c61
--- /dev/null
+++ b/src/binance-orderbook-trade/chart-storage/mirror-module.js
@@ -0,0 +1,16 @@
+/* Generated from the pinned public 70940 factory; see mirror-scoped-manifest.json. */
+const originalFactorySource = "70940(Z,x,e){\"use strict\";e.r(x),e.d(x,{TradingView:()=>Dt,default:()=>Cn});var o=e(49253),v=e(77135),d=e(75510),i=e(42288),p=e(31085),r=e(41594),M=e(43917),h=e.n(M),P=e(81936),b=e.n(P),V=e(7980),U=e.n(V),D=e(76469),c=e(4260),k=e(53837),B=e(27571),C=e(45250),y=e(87646),A=e.n(y),q=e(80817);const E=(0,C.range)(1,1380),m=(0,C.range)(1,365).map(t=>`${t}D`),R=(0,C.range)(1,52).map(t=>`${t}W`),I=(0,C.range)(1,12).map(t=>`${t}M`),F=[\"1S\",...E,...m,...R,...I],L=({symbol:t,params:n})=>{const a=new URLSearchParams(n);return`${t}@${a.toString()}`},X=t=>{const[n,a]=t.split(\"@\"),s=new URLSearchParams(a).entries(),g=A()(Array.from(s));return{symbol:n,params:g}},ue=()=>{const t=new Map;let n={},a=[],u={onReady:()=>null,resolveSymbol:()=>null,searchSymbols:()=>null,getBars:()=>null,subscribeBars:()=>null,unsubscribeBars:()=>null};return({config:s,onSymbolResolving:g,onSymbolResolved:S,onFetchSymbolsInfo:K,onFetchBars:Y,onSubscribeBars:H,onUnsubscribeBars:j,onFetchServerTimeApi:$,onTransformSymbolInfo:f=({template:T})=>T,onSearchSymbols:l=()=>!0})=>(u={onReady:T=>{setTimeout(async()=>{const _=(await K()).map(O=>({...O,ticker:L({symbol:O.symbol,params:O.params||{}})}));a=_.map(({ticker:O,symbol:G,description:te=\"\",exchange:J=\"\",type:oe=\"\"})=>({symbol:G,ticker:O,description:te,exchange:J,type:oe})),n=(0,C.keyBy)(_,\"ticker\"),T({exchanges:[],symbols_types:[],supported_resolutions:F,supports_marks:!1,supports_timescale_marks:!1,supports_time:!0,...s})})},resolveSymbol:async(T,W,_)=>{const O=n[T];if(!O){_(`cannot resolve symbol - ${T}`);return}const G=f({info:O,template:{description:O.description||\"\",fractional:!1,has_seconds:!0,seconds_multipliers:[\"1\"],has_intraday:!0,intraday_multipliers:[\"1\",\"3\",\"5\",\"15\",\"30\",\"60\",\"120\",\"240\",\"360\",\"480\",\"720\"],has_daily:!0,daily_multipliers:[\"1\"],has_weekly_and_monthly:!0,weekly_multipliers:[\"1\"],monthly_multipliers:[\"1\"],minmov:1,minmove2:0,name:O.symbol,ticker:O.ticker,timezone:\"Etc/UTC\",pricescale:O.tickSize,session:\"24x7\",type:O.type||\"\",exchange:O.exchange||\"\",listed_exchange:O.exchange||\"\",format:\"price\",supported_resolutions:F,visible_plots_set:\"ohlcv\",volume_precision:3}});if(g){const te=X(G.ticker||\"\");if(!await g({symbol:T,info:G,tickerInfo:te})){_(`symbol resolving error - ${T}`);return}}setTimeout(()=>{W(G),S?.(G)})},searchSymbols:(T,W,_,O)=>{const G=new RegExp(T,\"i\"),te=new RegExp(W,\"i\"),J=new RegExp(_,\"i\"),oe=a.filter(Te=>{const{symbol:N,ticker:w,description:re=\"\",exchange:ie=\"\",type:le=\"\"}=Te;return[N,w,re].some(de=>G.test(de||\"\"))&&te.test(ie)&&J.test(le)&&l?.({item:Te})});O(oe)},getBars:async(T,W,_,O,G)=>{const{from:te,to:J,firstDataRequest:oe}=_,Te=(0,q.Cz)(W);try{if(J>=0){const N=X(T.ticker||\"\"),w=await Y({symbolInfo:T,resolution:W,rangeStartDate:te,rangeEndDate:J,tickerInfo:N,binanceInterval:Te,firstDataRequest:oe});O(w,{noData:w.length===0})}else O([],{noData:!0})}catch(N){G(N.toString())}},subscribeBars:(T,W,_,O,G)=>{const te=X(T.ticker||\"\"),J=(0,q.Cz)(W),oe=H({symbolInfo:T,resolution:W,callback:_,onResetCacheNeededCallback:G,tickerInfo:te,binanceInterval:J});t.set(O,oe)},unsubscribeBars:T=>{t?.get(T)?.(),t?.delete(T),j?.(T)},getServerTime:async T=>{try{const W=await $();T(W)}catch(W){console.warn(W),T(Date.now()/1e3)}}},{datafeed:u,observers:t,symbolsInfo:n})},Le=({namespace:t,type:n})=>{const a=(0,r.useMemo)(()=>h().createInstance({name:t}),[t]);return(0,r.useCallback)(()=>{n===\"tv\"&&a.removeItem(\"myTradingView\")},[a,n])};var Ue=e(76535),pr=e(26531),Ye=e(17409),Qe=e(94652),Ae=e(76830),he=e(48651),Fe=e(79344),se=e(51029),Je=e(36307),or=e(25379),yr=e(44745),Ne=e(15727),rr=e(45358),sr=e(82071),Ve=e(85411),ge=e(94889),hr=e(94547),tr=e(80686),Ar=e(61875);function Fr(t){return(0,ge._)(t)||(0,hr._)(t)||(0,Ar._)(t)||(0,tr._)()}var Nr=e(92873),zr=e(35630),Vr=e(12055),Qr=e(79515),Br=e(80737),Jr=e(40545),Zr=e(98107),Kr=e(51846),Xr=\"Chart.Crosshair.PlusButton.DrawHorizontalLine\",wr=function(t){var n=t.namespace,a=t.symbol,u=t.priceType,s=t.crosshairPrice,g=(0,Nr.o)(\"\",\"kline-ui\").getI18n,S=(0,Vr.r)().format,K=k.y$(n),Y=(0,Br.S)({namespace:n}),H=(0,Jr.hw)(),j=(0,Zr.c)(),$=(0,Qr.nH)(),f=(0,se.Ye)(),l=(0,Kr.E)({symbol:a}),T=(0,Fe.f0)(a),W=T.isCM,_=(0,r.useRef)(s);(0,r.useEffect)(function(){_.current=s},[s]);var O=(0,r.useRef)(H);(0,r.useEffect)(function(){O.current=H},[H]);var G=(0,r.useRef)(j);(0,r.useEffect)(function(){G.current=j},[j]);var te=(0,r.useRef)(u);(0,r.useEffect)(function(){te.current=u},[u]);var J=(0,r.useRef)($);(0,r.useEffect)(function(){J.current=$},[$]);var oe=(0,r.useRef)(f);(0,r.useEffect)(function(){oe.current=f},[f]);var Te=(0,r.useRef)(l);(0,r.useEffect)(function(){Te.current=l},[l]);var N=(0,r.useRef)(W);return(0,r.useEffect)(function(){N.current=W},[W]),{items_processor:(function(){var w=(0,o._)(function(re,ie,le){var de,Be,ve,Ce,ce,Ke,Ze,Ge,Xe,Ie,He,ur,Tr,_r,Cr;return(0,i._)(this,function(Er){if(Be=K.getState().tradingViewReference,!Be)return console.warn(\"[context-menu] trading view reference is not found\"),[2,re];if(Be.chartsCount()>1)return[2,re];if(ve=Be.activeChart().symbolExt(),!ve)return[2,re];switch(Ce=ve.name,ce=_.current,Ke=le.menuName===\"CrosshairMenuView\"?\"chart-trading\":\"context-menu\",Ze={price:\"\".concat(ce.value),formattedPrice:ce.formatted,source:Ke,chartType:\"tradingview\"},Ge=(de=oe.current[Ce])===null||de===void 0?void 0:de.baseAsset,Xe=g(\"trade\",{defaultValue:\"Trade\"}),Ie=S(ce.formatted),He=G.current&&te.current===c.SJ.Last&&J.current&&Ge?[ie.createAction({actionId:\"Chart.PlaceOrder.BuyLimit\",label:\"\".concat(Xe,\" \").concat(Ge,\" @ \").concat(Ie,\" \").concat(g(\"limit\",{defaultValue:\"Limit\"})),onExecute:function(){return Te.current.onClickBuyLimit(Ze)}}),ie.createAction({actionId:\"Chart.PlaceOrder.BuyStop\",label:\"\".concat(Xe,\" \").concat(Ge,\" @ \").concat(Ie,\" \").concat(g(\"stop\",{defaultValue:\"Stop\"})),onExecute:function(){return Te.current.onClickBuyStop(Ze)}})]:[],ur=O.current?ie.createAction({actionId:\"Chart.PriceAlert.CreateAtCrosshair\",label:g(\"create-alert-at\",{defaultValue:\"Create Alert at {{price}}\",price:ce.formatted}),onExecute:function(){Y({symbol:Ce,price:ce.formatted,mode:zr.n.CREATE})}}):null,re.forEach(function(we){if(!(!(\"execute\"in we)||!(\"getState\"in we))){var Dr=we.getState();if(Dr.actionId===Xr){var z=we.execute.bind(we);we.execute=function(){(0,Kr.A)({elementId:\"draw_horizontal_line\",isDelivery:N.current,chartType:\"tradingview\"}),z()}}}}),le.menuName){case\"CrosshairMenuView\":return ur?[2,[ur].concat((0,d._)(He),(0,d._)(re.slice()))]:[2,(0,d._)(He).concat((0,d._)(re.slice()))];case\"ChartContextMenu\":return ur?(Tr=Fr(re),_r=Tr[0],Cr=Tr.slice(1),[2,[_r,ur].concat((0,d._)(He),(0,d._)(Cr))]):[2,(0,d._)(He).concat((0,d._)(re.slice()))];default:return[2,re]}return[2]})});return function(re,ie,le){return w.apply(this,arguments)}})()}},qr=function(){var t=(0,r.useState)({value:NaN,formatted:\"\"}),n=t[0],a=t[1],u=(0,r.useCallback)(function(s){var g=s.crosshairData,S=s.activeChart;S&&a({value:g.price,formatted:S.priceFormatter().format(g.price)})},[]);return{onCrosshairMoved:u,crosshairPrice:n}};const Hr=t=>Math.floor(Math.log10(t)),et=t=>{const[n=\"1\"]=t.split(\",\");return Hr(+n)},pe=({symbolInfo:t,minTick:n})=>{const{pricescale:a=2}=t||{};return n===\"default\"||n===void 0?Hr(a):et(n)},br=t=>(n,a)=>{try{return n?{format:u=>{const s=pe({symbolInfo:n,minTick:a});return t.format({symbolInfo:n,minTick:a,value:u,precision:s})}}:null}catch{return null}},Mr=10**12,rt=10**9,tt=10**6,jr=10**3,gr=t=>{const n=t.toString(),[,a=\"\"]=n.split(\".\");return Math.min(a.length,3)},nt=({value:t})=>{const n=Math.abs(t);if(n>=Mr){const a=t/Mr;return{value:a,unit:\"T\",precision:gr(a)}}if(n>=rt){const a=t/rt;return{value:a,unit:\"B\",precision:gr(a)}}if(n>=tt){const a=t/tt;return{value:a,unit:\"M\",precision:gr(a)}}if(n>=jr){const a=t/jr;return{value:a,unit:\"K\",precision:gr(a)}}return{value:t,unit:\"\",precision:gr(t)}},ut=t=>(n,a,u)=>{try{return a&&n.type===\"volume\"?{format:s=>s===void 0?\"\":t.formatVolume({format:n,symbolInfo:a,precision:u,value:s,volumeDetail:nt({value:s})})}:null}catch{return null}};var lt=function(){var t=(0,Vr.r)(),n=t.format,a=(0,r.useMemo)(function(){return br({format:function(s){var g=s.value,S=s.precision;return n(g,{precision:S})}})},[n]),u=(0,r.useMemo)(function(){return ut({formatVolume:function(s){var g=s.value,S=s.volumeDetail;if(g===void 0||!S)return\"\";var K=n(S.value,{precision:S.precision});return\"\".concat(K).concat(S.unit)}})},[n]);return{priceFormatterFactory:a,studyFormatterFactory:u}},dt=e(74069),ir=function(t){var n=(0,dt.K)(t),a=(0,r.useMemo)(function(){return n.map(function(u){var s=u.id,g=u.time,S=u.isBuy,K=u.price,Y=u.tooltip;return{id:s,text:\"\",price:K,quantity:\"\",time:Math.round(g/1e3),isBuy:S,tooltip:Y}})},[n]);return{executionOrders:a}},at=function(t){var n=t.namespace,a=t.symbol,u=(0,Br.j)({namespace:n,symbol:a});return(0,r.useMemo)(function(){return u.map(function(s){var g=s.id,S=s.price,K=s.formattedPrice,Y=s.sideText,H=s.showCloseButton,j=s.isPriceChangeable,$=s.cancelTooltipText,f=s.onClick,l=s.onCancel,T=s.onPriceChanged;return{id:g,price:+S,text:\"\".concat(Y,\" \").concat(K),editable:j,cancellable:H,cancelTooltip:$,type:c.ND.PriceAlert,onCancel:H?l:void 0,onMove:j?function(W){var _=W.price;T({currentPrice:_})}:void 0,onClick:f}})},[u])},vt=e(90664),ot=function(t){var n=t.symbol,a=(0,vt.w)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){var s=u.id,g=u.text,S=u.price;return{id:s,text:g,price:+S,type:c.ND.BreakEvenPrice}})},[a])},cr=e(82508),ft=e(91025),mt=e(36077),Rr=function(t){var n=t.symbol,a=(0,mt.c)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){return(0,ft._)((0,cr._)({},u),{type:c.ND.LiquidationPrice})})},[a])},pt=function(t){var n=t.namespace,a=t.symbol,u=at({namespace:n,symbol:a}),s=ot({symbol:a}),g=Rr({symbol:a});return(0,r.useMemo)(function(){return(0,d._)(u).concat((0,d._)(s),(0,d._)(g))},[u,s,g])},st=e(42902),Ir=e(90170),yt=e(63345),ht=e(48210),Pr=e(51289),xr=e(24326),$r=e(90922),kr=e(34235),it=e(63762),Ft=function(t){var n=(0,Fe.f0)(t),a=n.pricePrecision,u=n.isCM,s=(0,st.yD)(t)||{},g=s.lastPrice,S=g===void 0?\"0\":g,K=(0,kr.Bp)(u),Y=(0,it.HY)(t),H=(0,kr.gv)(u),j=(0,kr.WF)(u);return(0,r.useMemo)(function(){return(0,$r.qF)({orders:(0,d._)(K).concat((0,d._)(H),(0,d._)(j)),symbol:t}).map(function($){return Y({order:$,lastPrice:S,pricePrecision:a})})},[S,K,H,a,t,Y])},gt=e(31542),Tt=function(t){var n=t.type;(0,gt.u4)(\"webClick\",{module:\"chart\",elementId:n})},Nt=function(t){var n=t.symbol,a=t.namespace,u=(0,r.useState)(0),s=u[0],g=u[1],S=(0,r.useCallback)(function(){return g(function(N){return N+1})},[]),K=Ft(n),Y=(0,xr.IT)({namespace:a}),H=(0,Fe.f0)(n),j=H.isDelivery,$=H.contractType,f=(0,st.yD)(n)||{},l=f.lastPrice,T=l===void 0?\"0\":l,W=(0,yt.s)(n),_=(0,it.YZ)({symbol:n,afterConfirmationModalIsShown:S}),O=(0,it.u8)(n),G=(0,it.jn)(n),te=(0,r.useCallback)(function(N){return(0,ht.D)({order:N,isDelivery:j,symbol:j?N.symbol:\"\".concat(N.symbol,\"_\").concat($),module:\"tradingview_kline\"})},[$,j]),J=(0,r.useMemo)(function(){return K.map(function(N){var w=N.order,re=N.price,ie=N.quantity,le=N.quantityText,de=N.isBuy,Be=N.text,ve=N.priceTooltipText,Ce=N.pnlText,ce=(0,Pr.$L)(w),Ke=(0,Pr.OX)(w),Ze=(0,Pr.xL)(w),Ge=ce===\"LIMIT\",Xe=(0,Ir.Y)(w);return{id:String(Ke),text:Be,price:+re,quantity:le,isBuy:de,editable:Xe&&G,tooltip:ve,modifyTooltip:w.closePosition||!Ge?\"\":void 0,onMove:G?function(Ie){var He=Ie.price;te(w),Tt({type:\"TV_vertical\"}),_({order:w,price:+He,quantity:+Ze,lastPrice:T})}:void 0,onModify:w.closePosition||!Ge||!Xe?void 0:function(){Tt({type:\"TV_amountclick\"}),O({order:w,price:re.toString(),quantity:ie,lastPrice:T})},onCancel:function(){Tt({type:\"TV_cancel\"}),W(w)}}})},[G,K,T,te,_,O,W,s]),oe=(0,r.useMemo)(function(){return Y.map(function(N){var w=N.id,re=N.value,ie=N.text,le=N.onChange,de=N.isBuy,Be=N.type;return{id:w,price:+re,quantity:\"\",text:ie,isBuy:de,editable:!0,limitOrderType:c.aA.OrderPreview,onMove:function(ve){var Ce=ve.price;(0,xr.eP)({namespace:a,orderType:Be}),le({id:w,newPrice:Ce})},onMoving:function(ve){var Ce=ve.price;return le({id:w,newPrice:Ce})}}})},[Y,a]),Te=(0,r.useMemo)(function(){return(0,d._)(J).concat((0,d._)(oe))},[J,oe]);return{orderLines:Te}},Vt=e(17855),Bt=function(t,n){var a=(0,r.useState)(t()),u=a[0],s=a[1];return(0,Vt.A)(function(){return s(t())},n),u},Kt=e(58656),Ht=function(t){var n=t.symbol,a=(0,Kt.Q)({symbol:n})||[],u=Bt(function(){return a.map(function(s){var g=s.position,S=s.pnl,K=s.pnlInNumber,Y=s.pnlText,H=s.price,j=s.quantity,$=s.reverseText,f=s.isBuy,l=s.displayReverseOrderModal,T=s.displayClosePositionModal;return{id:g.id||\"\".concat(g.symbol,\"-\").concat(g.positionSide),PNL:K,text:\"\".concat(Y,\" \").concat(S),price:H,quantity:j,tooltip:\"\",reverseTooltip:$,isBuy:f,onReverse:l,onClose:function(){T(),(0,gt.u4)(\"$WebClick\",{item:1,module:\"xxx\",$element_id:\"kline_close_position\"})}}})},[a]);return{positionLines:u}},jt=e(47354),bt=e.n(jt),$t=e(87017),Ut=e(99168),Yt=e(3742),Wt=e(43335),Gt=e(15540),zt=e(20163),Qt=e(47255);const Jt=t=>({endTime:n})=>(n??0)===t;var Zt=e(36488),Ct=e(48018),Xt=e(82911),We=e(69578),wt=e(53208),Rt=e(19862),qt=e(8874),en=e(26679),rn=e(66547),It=(function(){var t=(0,o._)(function(n){var a,u,s,g,S,K,Y,H,j;return(0,i._)(this,function($){switch($.label){case 0:a=n.onFetchKlineData,u=n.startTime,s=n.endTime,g=n.interval,S=n.limit,K=S===void 0?1e3:S,Y=[],H=[],j=s,$.label=1;case 1:return[4,a((0,cr._)({interval:g,limit:K},j===0?{}:{endTime:j}))];case 2:if(H=$.sent(),H.length===0)return[3,4];H.forEach(function(f){var l=(0,v._)(f,8),T=l[0],W=l[1],_=l[2],O=l[3],G=l[4],te=l[5],J=l[7];Y.push({time:+T,open:+W,high:+_,low:+O,close:+G,volume:+te,quantity:+J})}),j=H[0][0]-1,$.label=3;case 3:if(j>u)return[3,1];$.label=4;case 4:return Y.sort(function(f,l){return f.time-l.time}),[2,Y]}})});return function(a){return t.apply(this,arguments)}})(),tn=function(t){var n=t.firstDataRequest,a=t.hasRequested,u=t.rangeEndDate;return n&&!a?0:u*1e3},nn=e(78142),an=e(16238),St=e.n(an),on=e(98087),xt=e.n(on),sn=e(55009),cn=e.n(sn),kt=e(56477);const Et=t=>cn()(t.chartsCount(),n=>t.chart(n)),un=async t=>{try{const{schema:n,data:a}=await t.exportData({includedStudies:[]}),u=xt()(a)?.[n.findIndex(s=>s.type===\"time\")];return u!==void 0&&Number.isFinite(u)?u*1e3:void 0}catch{return}},ln=({getWidget:t,...n})=>{const a=new Map,u=new Map,s=()=>{try{const f=t();return f?St()(Et(f).map(l=>l.symbolExt()?.ticker)):[]}catch{return[]}},g=f=>{const l=s();return!l.length||l.includes(f)},S=f=>{const l=u.get(f);if(l)return l;const T={controller:new AbortController,claimedAsMain:!1};return u.set(f,T),T},K=f=>{const l=u.get(f);l&&(l.controller.abort(),u.delete(f))},Y=()=>{const f=s();f.length&&Array.from(u.keys()).forEach(l=>{const T=u.get(l);if(T){if(f.includes(l)){T.claimedAsMain=!0;return}a.has(l)||T.claimedAsMain&&K(l)}})},H=(f,l)=>{f.closed||l.time{f.held=[];let T=l;try{const _=(await f.options.fetchSince(l)).filter(O=>O.time>=l&&!f.closed);_.forEach(O=>{f.highWater=Math.max(f.highWater,O.time),f.options.onTick(O)}),T=xt()(_)?.time??l}finally{const W=f.held??[];f.held=void 0,W.filter(_=>_.time>=T).forEach(_=>{H(f,_)})}},$=(0,kt.k)({...n,onCheck:Y,repair:async()=>{const f=t();if(f)try{const l=Et(f),T=St()(l.map(_=>{const O=a.get(_.symbolExt()?.ticker??\"\");return O&&{chart:_,feed:O}}));if(!T.length){f.resetCache(),l.forEach(_=>{_.resetData()});return}const W=St()(await Promise.all(T.map(async({chart:_,feed:O})=>{const G=await un(_);return G===void 0?void 0:{feed:O,fromMs:G}})));await Promise.allSettled(W.map(({feed:_,fromMs:O})=>j(_,O)))}catch{}}});return{openFeed:f=>{const l={options:f,held:void 0,highWater:0,closed:!1,detector:void 0};return l.detector=(0,kt.k)({...n,subscribeSignals:f.subscribeSignals,repair:async()=>{l.closed||!l.highWater||g(f.ticker)||await j(l,l.highWater)}}),a.set(f.ticker,l),g(f.ticker)&&$.resubscribeSignals(f.subscribeSignals),{push:T=>{l.held?l.held.push(T):H(l,T)},close:()=>{l.closed=!0,l.detector?.dispose(),a.get(f.ticker)===l&&a.delete(f.ticker)}}},subscribeWake:$.subscribeWake,getSignal:f=>{const l=S(f);return s().includes(f)&&(l.claimedAsMain=!0),l.controller.signal},dispose:()=>{u.forEach(({controller:f})=>{f.abort()}),u.clear(),a.forEach(f=>{f.detector?.dispose()}),a.clear(),$.dispose()}}};var dn=e(71730),vn=e(36620),fn=function(t){var n=t.namespace,a=k.y$(n),u=(0,vn.l)().getSocketSignals,s=(0,r.useRef)();(0,r.useEffect)(function(){return function(){var S;(S=s.current)===null||S===void 0||S.instance.dispose(),s.current=void 0}},[]);var g=(0,r.useCallback)(function(S){var K,Y,H=S;if(((K=s.current)===null||K===void 0?void 0:K.key)===H)return s.current.instance;(Y=s.current)===null||Y===void 0||Y.instance.dispose();var j=nn.wr(S),$=ln({getWidget:function(){return a.getState().tradingViewReference},checkPeriodMs:dn.D(j)});return s.current={key:H,instance:$},$},[a]);return(0,r.useMemo)(function(){return{ensureRecovery:g,getSocketSignals:u}},[g,u])},mn=\"tv_web_error\",pn=[Ye.bt.TRADING,Ye.bt.DELIVERING],Re;(function(t){t.Futures=\"futures\",t.FuturesMark=\"futures-mark\",t.FuturesIndex=\"futures-index\",t.Delivery=\"delivery\",t.DeliveryMark=\"delivery-mark\",t.DeliveryIndex=\"delivery-index\"})(Re||(Re={}));var Mt=function(t){var n=t.symbol,a=t.description,u=t.productType,s=t.pricePrecision,g=t.params,S=t.supportedResolutions,K=S===void 0?F:S;return{symbol:n,description:a,tickSize:Math.pow(10,s),exchange:\"Binance\",supportedResolutions:K,type:u,params:g}},yn=function(t){var n=t.namespace,a=t.useMiniTicker,u=a===void 0?!1:a,s=t.isCM,g=c.y$(n),S=k.y$(n),K=(0,$t.useQueryClient)(),Y=fn({namespace:n}),H=Y.ensureRecovery,j=Y.getSocketSignals,$=(0,r.useRef)(0),f=(0,r.useRef)(!1),l=(0,r.useMemo)(function(){return bt()(function(){return(0,We.HP)({eventId:\"trd_chart_tv_load_timeout\",stepId:\"getBarsStart\",catch:function(Q){var ee=Q.error,ae=Q.track;ae({GET_BAR_START_TYPE:ee})},log:function(Q){var ee=Q.track;ee({GET_BAR_START_TYPE:\"SUCCESS\"})}})})},[]),T=(0,r.useMemo)(function(){return bt()(function(z){return(0,We.HP)({eventId:\"trd_chart_tv_load_timeout\",stepId:\"getBarsEnd\",catch:function(ee){var ae=ee.error,Se=ee.track;Se({GET_BAR_END_TYPE:ae,duration_:(0,We.Pu)(z-$.current)})},log:function(ee){var ae=ee.track;ae({GET_BAR_END_TYPE:\"SUCCESS\",duration_:(0,We.Pu)(z-$.current)})}})})},[]),W=(0,Nr.o)(\"trd-chart\",\"trade-ui\"),_=W.getI18n,O=(0,he.ud)(),G=O.getI18n,te=(0,v._)(g(c.Ir),1),J=te[0],oe=(0,v._)(S(k.A7),2),Te=oe[1],N=(0,se.Ye)(),w=(0,Zt.JD)(s).isContUnit,re=(0,se.y_)(),ie=(0,r.useRef)(N),le=(0,r.useRef)(J),de=(0,r.useRef)(re);(0,r.useEffect)(function(){ie.current=N},[N]),(0,r.useEffect)(function(){le.current=J},[J]),(0,r.useEffect)(function(){de.current=re},[re]);var Be=(0,r.useMemo)(function(){return w?Rt.b:Rt.S},[w]),ve=(0,r.useMemo)(function(){return G(\"usdtFutures-13\",{defaultValue:\"Futures\"})||\"\"},[G]),Ce=(0,r.useMemo)(function(){return G(\"inverseFutures-13\",{defaultValue:\"Delivery\"})||\"\"},[G]),ce=(0,r.useMemo)(function(){return _(\"indexPrice\",{defaultValue:\"Index Price\"})||\"\"},[_]),Ke=(0,r.useMemo)(function(){return _(\"markPrice\",{defaultValue:\"Mark Price\"})||\"\"},[_]),Ze=(0,r.useMemo)(function(){return _(\"lastPrice\",{defaultValue:\"Last Price\"})||\"\"},[_]),Ge=(0,r.useMemo)(function(){return{exchanges:[{name:\"Binance\",value:\"Binance\",desc:\"\"}],symbols_types:[{name:ve,value:Re.Futures},{name:\"\".concat(ve,\" \").concat(Ke),value:Re.FuturesMark},{name:\"\".concat(ve,\" \").concat(ce),value:Re.FuturesIndex},{name:Ce,value:Re.Delivery},{name:\"\".concat(Ce,\" \").concat(Ke),value:Re.DeliveryMark},{name:\"\".concat(Ce,\" \").concat(ce),value:Re.DeliveryIndex}]}},[Ce,ve,ce,Ke]),Xe=(0,r.useCallback)((0,o._)(function(){var z,Q,ee;return(0,i._)(this,function(ae){switch(ae.label){case 0:return ae.trys.push([0,2,,3]),[4,(0,en.c)(K)];case 1:return ae.sent(),[3,3];case 2:return z=ae.sent(),(0,gt.u4)(mn,{type:\"onFetchSymbolsInfo\",message:z.message,business:\"futures\"}),[3,3];case 3:return Q=ie.current,ee=Object.keys(Q).reduce(function(Se,xe){var ne=Q[xe]||Ct.o,ke=ne.symbol,Me=ne.pricePrecision,fe=ne.isDelivery,Pe=ne.contractStatus;if(!pn.includes(Pe))return Se;var _e=(0,Yt.S)(ne,G),je=_e.symbol,Ee=_e.contractTypeI18n,me=_e.deliveryDateFormat,ye=\"\".concat(je,\" \").concat(Ee).concat(me?\" \".concat(me):\"\"),$e=\"\".concat(ye,\" \").concat(Ze),Oe=\"\".concat(ye,\" \").concat(ce),lr=\"\".concat(ye,\" \").concat(Ke);return Se.concat([Mt({symbol:ke,description:$e,productType:fe?Re.Delivery:Re.Futures,pricePrecision:Me,params:{priceType:c.SJ.Last}}),Mt({symbol:ke,description:Oe,productType:fe?Re.DeliveryIndex:Re.FuturesIndex,pricePrecision:Me,params:{priceType:c.SJ.Index}}),Mt({symbol:ke,description:lr,productType:fe?Re.DeliveryMark:Re.FuturesMark,pricePrecision:(0,Xt.X)(Me),params:{priceType:c.SJ.Mark}})])},[]),[2,ee]}})}),[G,ce,Ke,Ze,K]),Ie=(0,r.useCallback)(function(z){var Q=z.ticker,ee=z.symbol,ae=z.interval,Se=z.priceType,xe=z.retryUntil,ne=ie.current[ee]||Ct.o,ke=ne.pair,Me=ne.contractType,fe=ne.contractVal,Pe=ne.isDelivery,_e=Pe?Be(fe):function(Oe){return Oe},je=(0,wt.h)({symbol:ee,pair:ke,contractType:Me,isDelivery:Pe,priceType:Se}),Ee,me,ye=function(Oe){return zt.y(\"tv\",\"\".concat(Pe?\"cm\":\"um\",\"|\").concat(ee,\"|\").concat(Se,\"|\").concat(Oe.interval,\"|\").concat((Ee=Oe.limit)!==null&&Ee!==void 0?Ee:\"\",\"|\").concat((me=Oe.endTime)!==null&&me!==void 0?me:\"\"))};if(xe===void 0)return{transform:_e,onFetchKlineData:je};var $e=H(ae);return{transform:_e,onFetchKlineData:Qt.b(je,{enabled:Jt(xe),key:ye,signal:$e.getSignal(Q),subscribeWake:$e.subscribeWake})}},[H,Be]),He=(0,r.useCallback)((function(){var z=(0,o._)(function(Q){var ee,ae,Se,xe,ne,ke,Me,fe,Pe,_e,je,Ee,me,ye,$e,Oe,lr,Or;return(0,i._)(this,function(dr){switch(dr.label){case 0:return ee=Q.binanceInterval,ae=Q.rangeStartDate,Se=Q.rangeEndDate,xe=Q.tickerInfo,ne=Q.symbolInfo,ke=Q.firstDataRequest,$.current=Date.now(),l(),Me=xe.symbol,fe=Me===void 0?\"\":Me,Pe=xe.params,_e=Pe.priceType,je=_e===void 0?le.current:_e,de.current({symbol:fe,priceType:je})?(T(Date.now()),[2,[]]):(Ee=Math.max(0,ae*1e3),me=tn({firstDataRequest:ke,hasRequested:f.current,rangeEndDate:Se}),f.current=!0,$e=Ie({ticker:(ye=ne.ticker)!==null&&ye!==void 0?ye:\"\",symbol:fe,interval:ee,priceType:je,retryUntil:ke?me:void 0}),Oe=$e.transform,lr=$e.onFetchKlineData,[4,It({onFetchKlineData:lr,startTime:Ee,endTime:me,interval:ee})]);case 1:return Or=dr.sent(),T(Date.now()),[2,Or.map(Oe)]}})});return function(Q){return z.apply(this,arguments)}})(),[Ie,T,l]),ur=(0,r.useCallback)(function(z){var Q=z.binanceInterval,ee=z.callback,ae=z.tickerInfo,Se=z.symbolInfo,xe=ae.symbol,ne=xe===void 0?\"\":xe,ke=ae.params,Me=ke.priceType,fe=Me===void 0?le.current:Me;if(de.current({symbol:ne,priceType:fe}))return function(){return null};var Pe=ie.current[ne]||Ct.o,_e=Pe.pair,je=Pe.contractType,Ee=Pe.isDelivery,me,ye=(me=Se.ticker)!==null&&me!==void 0?me:\"\",$e=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe}).transform,Oe=H(Q),lr=Oe.openFeed({ticker:ye,onTick:ee,subscribeSignals:j(Ee),fetchSince:(function(){var dr=(0,o._)(function(Lr){var Ur,Yr,ct,De;return(0,i._)(this,function(ze){switch(ze.label){case 0:return Ur=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe,retryUntil:0}),Yr=Ur.transform,ct=Ur.onFetchKlineData,[4,It({onFetchKlineData:ct,startTime:Lr,endTime:0,interval:Q})];case 1:return De=ze.sent(),[2,De.map(Yr)]}})});return function(Lr){return dr.apply(this,arguments)}})()}),Or=(0,rn.B)({symbol:ne,pair:_e,contractType:je,interval:Q,priceType:fe,isDelivery:Ee,useMiniTicker:u,callback:function(dr){lr.push($e(dr))}});return function(){lr.close(),Or()}},[Ie,H,j,u]),Tr=(0,r.useCallback)((0,o._)(function(){var z;return(0,i._)(this,function(Q){switch(Q.label){case 0:return[4,K.fetchQuery(Wt.Bz.SERVER_TIME(s),function(){return(0,Gt.l)(s)},{})];case 1:return z=Q.sent(),[2,Math.floor(z/1e3)]}})}),[K,s]),_r=(0,r.useCallback)(function(z){return(0,cr._)({},z.template,(0,qt.sp)(s))},[s]),Cr=(0,r.useMemo)(function(){return ue()},[]),Er=Cr({config:Ge,onFetchSymbolsInfo:Xe,onFetchBars:He,onSubscribeBars:ur,onFetchServerTimeApi:Tr,onTransformSymbolInfo:_r}),we=Er.datafeed,Dr=Er.symbolsInfo;return(0,Ut.A)(function(){return Te()},[w]),{datafeed:we,symbolsInfo:Dr}},Pt=\"trd_chart_tv_load_timeout\",hn=function(){var t=(0,We.PM)({id:\"chartReady\"}),n=(0,We.PM)({id:\"getBarsStart\",timeout:3}),a=(0,We.PM)({id:\"getBarsEnd\",timeout:6}),u=(0,We.PM)({id:\"chartResponsive\",timeout:3});We.HP.init({step:t._(n)._(a)._(u),eventId:Pt}),(0,We.HP)({eventId:Pt,stepId:\"chartReady\",catch:function(g){var S=g.error,K=g.track;K({CHART_READY_TYPE:S,connection:JSON.stringify(navigator.connection)})},log:function(g){var S=g.track;S({CHART_READY_TYPE:\"SUCCESS\",connection:JSON.stringify(navigator.connection)})}})},gn=function(t){(0,We.HP)({eventId:Pt,stepId:\"chartResponsive\",catch:function(a){var u=a.error,s=a.track;s({CHART_RESPONSIVE_TYPE:u,connection:JSON.stringify(navigator.connection)})},log:function(a){var u=a.track;u({CHART_RESPONSIVE_TYPE:\"SUCCESS\",duration_:(0,We.Pu)(Date.now()-t)})}})},Tn=[Qe.ZE,Qe.F3],Dt=function(t){var n=t.symbol,a=t.namespace,u=t.useMiniTicker,s=u===void 0?!1:u,g=c.y$(a),S=k.y$(a),K=(0,v._)(g(c.A8),1),Y=K[0],H=(0,v._)(g(c.Ir),2),j=H[0],$=H[1],f=(0,v._)(S(k.yj),1),l=f[0],T=(0,v._)(g(c.e6),1),W=T[0],_=(0,v._)(g(c._b),1),O=_[0],G=(0,he.ot)(),te=(0,pr.K7)(),J=te.pathname.includes(Ae.Q2),oe=te.pathname.includes(Ae.eq),Te=(0,r.useMemo)(function(){return J?(0,d._)(B.oT).concat([\"hide_left_toolbar_by_default\"]):B.oT},[J]),N=(0,se.Ye)(),w=(0,se.v3)({symbol:n}),re=(0,Fe.es)(n),ie=yn({namespace:a,useMiniTicker:s,isCM:re}),le=ie.datafeed,de=ie.symbolsInfo,Be=(0,Je.DP)(),ve=Be.theme,Ce=ve===\"light\"||ve===\"light_glacier\"?\"light\":\"dark\",ce=(0,yr.Z)(Ce),Ke=(0,r.useRef)(-1),Ze=Ht({symbol:n}).positionLines,Ge=Nt({symbol:n,namespace:a}).orderLines,Xe=ir({symbol:n}).executionOrders,Ie=pt({namespace:a,symbol:n}),He=(0,se.y0)(),ur=(0,r.useRef)(He),Tr=(0,r.useRef)(N),_r=(0,r.useRef)(l),Cr=(0,r.useRef)(J),Er=(0,r.useRef)(oe),we=(0,r.useRef)(de);(0,r.useEffect)(function(){we.current=de},[de]),(0,r.useEffect)(function(){ur.current=He},[He]),(0,r.useEffect)(function(){Tr.current=N},[N]),(0,r.useEffect)(function(){_r.current=l},[l]),(0,r.useEffect)(function(){Cr.current=J},[J]),(0,r.useEffect)(function(){Er.current=oe},[oe]),(0,Ve.a)(n,s),(0,sr.A)({symbol:n,interval:Y,priceType:j});var Dr=(0,r.useCallback)(function(){Ke.current=Date.now()},[]),z=(0,r.useCallback)((0,o._)(function(){var De,ze,qe,be,vr,fr,er;return(0,i._)(this,function(nr){switch(nr.label){case 0:if(Cr.current||!_r.current)return[2,Promise.resolve()];nr.label=1;case 1:return nr.trys.push([1,5,,6]),De=h().createInstance({name:a}),[4,De.keys()];case 2:return ze=nr.sent(),qe=ze.filter(function(mr){return/^((#TV_SYMBOL-)|(myTradingView))/g.test(mr)}),be=qe.map((function(){var mr=(0,o._)(function(ar){return(0,i._)(this,function(Sr){return[2,De.getItem(ar)]})});return function(ar){return mr.apply(this,arguments)}})()),[4,Promise.all(be)];case 3:return vr=nr.sent(),fr=U()(qe,vr),[4,Promise.all(Tn.map((function(){var mr=(0,o._)(function(ar){var Sr,Wr;return(0,i._)(this,function(Gr){switch(Gr.label){case 0:return a===ar?[2,Promise.resolve()]:(Sr=h().createInstance({name:ar}),Wr=fr.map((function(){var Sn=(0,o._)(function(Lt){var _t,Ot,At;return(0,i._)(this,function(Mn){return _t=(0,v._)(Lt,2),Ot=_t[0],At=_t[1],Ot?[2,Sr.setItem(Ot,At)]:[2,Promise.resolve()]})});return function(Lt){return Sn.apply(this,arguments)}})()),[4,Promise.all(Wr)]);case 1:return Gr.sent(),[2,Promise.resolve()]}})});return function(ar){return mr.apply(this,arguments)}})()))];case 4:return nr.sent(),[2,Promise.resolve()];case 5:return er=nr.sent(),console.warn(typeof er==\"string\"?er:er.message),[2,Promise.resolve()];case 6:return[2]}})}),[a]),Q=(0,r.useMemo)(function(){return z},[z]),ee=(0,or.R)(),ae=(0,Ne.fQ)(a),Se=(0,Ne.m3)(a),xe=(0,Ne.S_)(a),ne=(0,Ne.Ds)(a),ke=(0,r.useRef)(n),Me=(0,r.useRef)(Y),fe=(0,r.useCallback)(function(){return ee(\"tradingView\"),xe({panel_index:0,symbol:n,interval:Y}),hn()},[Y,ee,n,xe]),Pe=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be,vr,fr,er=(fr=(vr=(be=qe.symbolExt())===null||be===void 0?void 0:be.name)!==null&&vr!==void 0?vr:qe.symbol())!==null&&fr!==void 0?fr:\"\";ae({panel_index:ze,symbol:er,previous_symbol:ke.current}),ke.current=er},[ae]),_e=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be=qe.resolution();Se({panel_index:ze,interval:be,previous_interval:Me.current}),Me.current=be},[Se]),je=(0,r.useCallback)(function(){gn(Ke.current)},[]),Ee=(0,r.useCallback)(function(){},[]),me=(0,r.useMemo)(function(){return J?Ee:void 0},[Ee,J]),ye=(0,r.useCallback)((function(){var De=(0,o._)(function(ze){var qe,be,vr,fr,er,nr,mr,ar,Sr;return(0,i._)(this,function(Wr){switch(Wr.label){case 0:return qe=ze.chart,be=qe.symbolExt(),be?(vr=b()(we.current,function(Gr){return Gr.ticker===be.ticker||Gr.symbol===be.ticker}),fr=vr||{},er=fr.params,nr=er===void 0?{}:er,mr=nr.priceType,ar=mr===void 0?\"\":mr,ar&&$(ar),Sr=Tr.current[be.name],Sr?Er.current||Cr.current?[2]:[4,z()]:[2]):[2];case 1:return Wr.sent(),G({stateSymbol:Sr.symbol,isDeliveryColumn:Sr.isDelivery}),[2]}})});return function(ze){return De.apply(this,arguments)}})(),[G,z,$]),$e=qr(),Oe=$e.onCrosshairMoved,lr=$e.crosshairPrice,Or=wr({namespace:a,symbol:n,priceType:j,crosshairPrice:lr}),dr=lt(),Lr=(0,r.useMemo)(function(){return L({symbol:n,params:{priceType:j}})},[n,j]),Ur=(0,r.useMemo)(function(){return{symbol:Lr,theme:ce,limitOrders:Ge,positionOrders:Ze,executionOrders:Xe,labelLines:Ie,initialConfig:{tvConfig:{symbol:Lr,library_path:\"/\".concat(Ye.u_,\"/trading-platform-30/\"),theme:ce,datafeed:le,enabled_features:Te,context_menu:Or,custom_formatters:dr},onCrosshairMoved:Oe,onScriptLoaded:Dr,onSave:Q,onChartReadyDone:fe,onFirstTimeDataLoadedDone:je,onInitChart:me,onActiveChartChanged:ye,onSymbolChanged:Pe,onIntervalChanged:_e}}},[Lr,le,Te,Xe,Ie,Or,dr,Oe,ye,fe,je,me,_e,Q,Dr,Pe,Ge,Ze,ce]),Yr=Le({namespace:a,type:\"tv\"}),ct=(0,r.useCallback)(function(){ne({panel_index:0,symbol:n}),Yr()},[Yr,n,ne]);return(0,p.jsxs)(D.t,{name:\"trading_view\",onReset:ct,children:[(0,p.jsx)(Ue.A,{namespace:a,tradingViewProps:Ur}),w&&j===\"index\"&&O===c.Ev.Single&&W===c.tU.TradingView&&(0,p.jsx)(rr.B,{})]})};const Cn=Dt}";
+
+/** Static factory generation preserves the host lexical body except its mirror write expression. */
+export function createMirrorFactory(__dispatchChartMirror) {
+ if (typeof __dispatchChartMirror !== 'function') throw new Error('Mirror dispatch must be a function');
+ return {70940(Z,x,e){"use strict";e.r(x),e.d(x,{TradingView:()=>Dt,default:()=>Cn});var o=e(49253),v=e(77135),d=e(75510),i=e(42288),p=e(31085),r=e(41594),M=e(43917),h=e.n(M),P=e(81936),b=e.n(P),V=e(7980),U=e.n(V),D=e(76469),c=e(4260),k=e(53837),B=e(27571),C=e(45250),y=e(87646),A=e.n(y),q=e(80817);const E=(0,C.range)(1,1380),m=(0,C.range)(1,365).map(t=>`${t}D`),R=(0,C.range)(1,52).map(t=>`${t}W`),I=(0,C.range)(1,12).map(t=>`${t}M`),F=["1S",...E,...m,...R,...I],L=({symbol:t,params:n})=>{const a=new URLSearchParams(n);return`${t}@${a.toString()}`},X=t=>{const[n,a]=t.split("@"),s=new URLSearchParams(a).entries(),g=A()(Array.from(s));return{symbol:n,params:g}},ue=()=>{const t=new Map;let n={},a=[],u={onReady:()=>null,resolveSymbol:()=>null,searchSymbols:()=>null,getBars:()=>null,subscribeBars:()=>null,unsubscribeBars:()=>null};return({config:s,onSymbolResolving:g,onSymbolResolved:S,onFetchSymbolsInfo:K,onFetchBars:Y,onSubscribeBars:H,onUnsubscribeBars:j,onFetchServerTimeApi:$,onTransformSymbolInfo:f=({template:T})=>T,onSearchSymbols:l=()=>!0})=>(u={onReady:T=>{setTimeout(async()=>{const _=(await K()).map(O=>({...O,ticker:L({symbol:O.symbol,params:O.params||{}})}));a=_.map(({ticker:O,symbol:G,description:te="",exchange:J="",type:oe=""})=>({symbol:G,ticker:O,description:te,exchange:J,type:oe})),n=(0,C.keyBy)(_,"ticker"),T({exchanges:[],symbols_types:[],supported_resolutions:F,supports_marks:!1,supports_timescale_marks:!1,supports_time:!0,...s})})},resolveSymbol:async(T,W,_)=>{const O=n[T];if(!O){_(`cannot resolve symbol - ${T}`);return}const G=f({info:O,template:{description:O.description||"",fractional:!1,has_seconds:!0,seconds_multipliers:["1"],has_intraday:!0,intraday_multipliers:["1","3","5","15","30","60","120","240","360","480","720"],has_daily:!0,daily_multipliers:["1"],has_weekly_and_monthly:!0,weekly_multipliers:["1"],monthly_multipliers:["1"],minmov:1,minmove2:0,name:O.symbol,ticker:O.ticker,timezone:"Etc/UTC",pricescale:O.tickSize,session:"24x7",type:O.type||"",exchange:O.exchange||"",listed_exchange:O.exchange||"",format:"price",supported_resolutions:F,visible_plots_set:"ohlcv",volume_precision:3}});if(g){const te=X(G.ticker||"");if(!await g({symbol:T,info:G,tickerInfo:te})){_(`symbol resolving error - ${T}`);return}}setTimeout(()=>{W(G),S?.(G)})},searchSymbols:(T,W,_,O)=>{const G=new RegExp(T,"i"),te=new RegExp(W,"i"),J=new RegExp(_,"i"),oe=a.filter(Te=>{const{symbol:N,ticker:w,description:re="",exchange:ie="",type:le=""}=Te;return[N,w,re].some(de=>G.test(de||""))&&te.test(ie)&&J.test(le)&&l?.({item:Te})});O(oe)},getBars:async(T,W,_,O,G)=>{const{from:te,to:J,firstDataRequest:oe}=_,Te=(0,q.Cz)(W);try{if(J>=0){const N=X(T.ticker||""),w=await Y({symbolInfo:T,resolution:W,rangeStartDate:te,rangeEndDate:J,tickerInfo:N,binanceInterval:Te,firstDataRequest:oe});O(w,{noData:w.length===0})}else O([],{noData:!0})}catch(N){G(N.toString())}},subscribeBars:(T,W,_,O,G)=>{const te=X(T.ticker||""),J=(0,q.Cz)(W),oe=H({symbolInfo:T,resolution:W,callback:_,onResetCacheNeededCallback:G,tickerInfo:te,binanceInterval:J});t.set(O,oe)},unsubscribeBars:T=>{t?.get(T)?.(),t?.delete(T),j?.(T)},getServerTime:async T=>{try{const W=await $();T(W)}catch(W){console.warn(W),T(Date.now()/1e3)}}},{datafeed:u,observers:t,symbolsInfo:n})},Le=({namespace:t,type:n})=>{const a=(0,r.useMemo)(()=>h().createInstance({name:t}),[t]);return(0,r.useCallback)(()=>{n==="tv"&&a.removeItem("myTradingView")},[a,n])};var Ue=e(76535),pr=e(26531),Ye=e(17409),Qe=e(94652),Ae=e(76830),he=e(48651),Fe=e(79344),se=e(51029),Je=e(36307),or=e(25379),yr=e(44745),Ne=e(15727),rr=e(45358),sr=e(82071),Ve=e(85411),ge=e(94889),hr=e(94547),tr=e(80686),Ar=e(61875);function Fr(t){return(0,ge._)(t)||(0,hr._)(t)||(0,Ar._)(t)||(0,tr._)()}var Nr=e(92873),zr=e(35630),Vr=e(12055),Qr=e(79515),Br=e(80737),Jr=e(40545),Zr=e(98107),Kr=e(51846),Xr="Chart.Crosshair.PlusButton.DrawHorizontalLine",wr=function(t){var n=t.namespace,a=t.symbol,u=t.priceType,s=t.crosshairPrice,g=(0,Nr.o)("","kline-ui").getI18n,S=(0,Vr.r)().format,K=k.y$(n),Y=(0,Br.S)({namespace:n}),H=(0,Jr.hw)(),j=(0,Zr.c)(),$=(0,Qr.nH)(),f=(0,se.Ye)(),l=(0,Kr.E)({symbol:a}),T=(0,Fe.f0)(a),W=T.isCM,_=(0,r.useRef)(s);(0,r.useEffect)(function(){_.current=s},[s]);var O=(0,r.useRef)(H);(0,r.useEffect)(function(){O.current=H},[H]);var G=(0,r.useRef)(j);(0,r.useEffect)(function(){G.current=j},[j]);var te=(0,r.useRef)(u);(0,r.useEffect)(function(){te.current=u},[u]);var J=(0,r.useRef)($);(0,r.useEffect)(function(){J.current=$},[$]);var oe=(0,r.useRef)(f);(0,r.useEffect)(function(){oe.current=f},[f]);var Te=(0,r.useRef)(l);(0,r.useEffect)(function(){Te.current=l},[l]);var N=(0,r.useRef)(W);return(0,r.useEffect)(function(){N.current=W},[W]),{items_processor:(function(){var w=(0,o._)(function(re,ie,le){var de,Be,ve,Ce,ce,Ke,Ze,Ge,Xe,Ie,He,ur,Tr,_r,Cr;return(0,i._)(this,function(Er){if(Be=K.getState().tradingViewReference,!Be)return console.warn("[context-menu] trading view reference is not found"),[2,re];if(Be.chartsCount()>1)return[2,re];if(ve=Be.activeChart().symbolExt(),!ve)return[2,re];switch(Ce=ve.name,ce=_.current,Ke=le.menuName==="CrosshairMenuView"?"chart-trading":"context-menu",Ze={price:"".concat(ce.value),formattedPrice:ce.formatted,source:Ke,chartType:"tradingview"},Ge=(de=oe.current[Ce])===null||de===void 0?void 0:de.baseAsset,Xe=g("trade",{defaultValue:"Trade"}),Ie=S(ce.formatted),He=G.current&&te.current===c.SJ.Last&&J.current&&Ge?[ie.createAction({actionId:"Chart.PlaceOrder.BuyLimit",label:"".concat(Xe," ").concat(Ge," @ ").concat(Ie," ").concat(g("limit",{defaultValue:"Limit"})),onExecute:function(){return Te.current.onClickBuyLimit(Ze)}}),ie.createAction({actionId:"Chart.PlaceOrder.BuyStop",label:"".concat(Xe," ").concat(Ge," @ ").concat(Ie," ").concat(g("stop",{defaultValue:"Stop"})),onExecute:function(){return Te.current.onClickBuyStop(Ze)}})]:[],ur=O.current?ie.createAction({actionId:"Chart.PriceAlert.CreateAtCrosshair",label:g("create-alert-at",{defaultValue:"Create Alert at {{price}}",price:ce.formatted}),onExecute:function(){Y({symbol:Ce,price:ce.formatted,mode:zr.n.CREATE})}}):null,re.forEach(function(we){if(!(!("execute"in we)||!("getState"in we))){var Dr=we.getState();if(Dr.actionId===Xr){var z=we.execute.bind(we);we.execute=function(){(0,Kr.A)({elementId:"draw_horizontal_line",isDelivery:N.current,chartType:"tradingview"}),z()}}}}),le.menuName){case"CrosshairMenuView":return ur?[2,[ur].concat((0,d._)(He),(0,d._)(re.slice()))]:[2,(0,d._)(He).concat((0,d._)(re.slice()))];case"ChartContextMenu":return ur?(Tr=Fr(re),_r=Tr[0],Cr=Tr.slice(1),[2,[_r,ur].concat((0,d._)(He),(0,d._)(Cr))]):[2,(0,d._)(He).concat((0,d._)(re.slice()))];default:return[2,re]}return[2]})});return function(re,ie,le){return w.apply(this,arguments)}})()}},qr=function(){var t=(0,r.useState)({value:NaN,formatted:""}),n=t[0],a=t[1],u=(0,r.useCallback)(function(s){var g=s.crosshairData,S=s.activeChart;S&&a({value:g.price,formatted:S.priceFormatter().format(g.price)})},[]);return{onCrosshairMoved:u,crosshairPrice:n}};const Hr=t=>Math.floor(Math.log10(t)),et=t=>{const[n="1"]=t.split(",");return Hr(+n)},pe=({symbolInfo:t,minTick:n})=>{const{pricescale:a=2}=t||{};return n==="default"||n===void 0?Hr(a):et(n)},br=t=>(n,a)=>{try{return n?{format:u=>{const s=pe({symbolInfo:n,minTick:a});return t.format({symbolInfo:n,minTick:a,value:u,precision:s})}}:null}catch{return null}},Mr=10**12,rt=10**9,tt=10**6,jr=10**3,gr=t=>{const n=t.toString(),[,a=""]=n.split(".");return Math.min(a.length,3)},nt=({value:t})=>{const n=Math.abs(t);if(n>=Mr){const a=t/Mr;return{value:a,unit:"T",precision:gr(a)}}if(n>=rt){const a=t/rt;return{value:a,unit:"B",precision:gr(a)}}if(n>=tt){const a=t/tt;return{value:a,unit:"M",precision:gr(a)}}if(n>=jr){const a=t/jr;return{value:a,unit:"K",precision:gr(a)}}return{value:t,unit:"",precision:gr(t)}},ut=t=>(n,a,u)=>{try{return a&&n.type==="volume"?{format:s=>s===void 0?"":t.formatVolume({format:n,symbolInfo:a,precision:u,value:s,volumeDetail:nt({value:s})})}:null}catch{return null}};var lt=function(){var t=(0,Vr.r)(),n=t.format,a=(0,r.useMemo)(function(){return br({format:function(s){var g=s.value,S=s.precision;return n(g,{precision:S})}})},[n]),u=(0,r.useMemo)(function(){return ut({formatVolume:function(s){var g=s.value,S=s.volumeDetail;if(g===void 0||!S)return"";var K=n(S.value,{precision:S.precision});return"".concat(K).concat(S.unit)}})},[n]);return{priceFormatterFactory:a,studyFormatterFactory:u}},dt=e(74069),ir=function(t){var n=(0,dt.K)(t),a=(0,r.useMemo)(function(){return n.map(function(u){var s=u.id,g=u.time,S=u.isBuy,K=u.price,Y=u.tooltip;return{id:s,text:"",price:K,quantity:"",time:Math.round(g/1e3),isBuy:S,tooltip:Y}})},[n]);return{executionOrders:a}},at=function(t){var n=t.namespace,a=t.symbol,u=(0,Br.j)({namespace:n,symbol:a});return(0,r.useMemo)(function(){return u.map(function(s){var g=s.id,S=s.price,K=s.formattedPrice,Y=s.sideText,H=s.showCloseButton,j=s.isPriceChangeable,$=s.cancelTooltipText,f=s.onClick,l=s.onCancel,T=s.onPriceChanged;return{id:g,price:+S,text:"".concat(Y," ").concat(K),editable:j,cancellable:H,cancelTooltip:$,type:c.ND.PriceAlert,onCancel:H?l:void 0,onMove:j?function(W){var _=W.price;T({currentPrice:_})}:void 0,onClick:f}})},[u])},vt=e(90664),ot=function(t){var n=t.symbol,a=(0,vt.w)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){var s=u.id,g=u.text,S=u.price;return{id:s,text:g,price:+S,type:c.ND.BreakEvenPrice}})},[a])},cr=e(82508),ft=e(91025),mt=e(36077),Rr=function(t){var n=t.symbol,a=(0,mt.c)({symbol:n});return(0,r.useMemo)(function(){return a.map(function(u){return(0,ft._)((0,cr._)({},u),{type:c.ND.LiquidationPrice})})},[a])},pt=function(t){var n=t.namespace,a=t.symbol,u=at({namespace:n,symbol:a}),s=ot({symbol:a}),g=Rr({symbol:a});return(0,r.useMemo)(function(){return(0,d._)(u).concat((0,d._)(s),(0,d._)(g))},[u,s,g])},st=e(42902),Ir=e(90170),yt=e(63345),ht=e(48210),Pr=e(51289),xr=e(24326),$r=e(90922),kr=e(34235),it=e(63762),Ft=function(t){var n=(0,Fe.f0)(t),a=n.pricePrecision,u=n.isCM,s=(0,st.yD)(t)||{},g=s.lastPrice,S=g===void 0?"0":g,K=(0,kr.Bp)(u),Y=(0,it.HY)(t),H=(0,kr.gv)(u),j=(0,kr.WF)(u);return(0,r.useMemo)(function(){return(0,$r.qF)({orders:(0,d._)(K).concat((0,d._)(H),(0,d._)(j)),symbol:t}).map(function($){return Y({order:$,lastPrice:S,pricePrecision:a})})},[S,K,H,a,t,Y])},gt=e(31542),Tt=function(t){var n=t.type;(0,gt.u4)("webClick",{module:"chart",elementId:n})},Nt=function(t){var n=t.symbol,a=t.namespace,u=(0,r.useState)(0),s=u[0],g=u[1],S=(0,r.useCallback)(function(){return g(function(N){return N+1})},[]),K=Ft(n),Y=(0,xr.IT)({namespace:a}),H=(0,Fe.f0)(n),j=H.isDelivery,$=H.contractType,f=(0,st.yD)(n)||{},l=f.lastPrice,T=l===void 0?"0":l,W=(0,yt.s)(n),_=(0,it.YZ)({symbol:n,afterConfirmationModalIsShown:S}),O=(0,it.u8)(n),G=(0,it.jn)(n),te=(0,r.useCallback)(function(N){return(0,ht.D)({order:N,isDelivery:j,symbol:j?N.symbol:"".concat(N.symbol,"_").concat($),module:"tradingview_kline"})},[$,j]),J=(0,r.useMemo)(function(){return K.map(function(N){var w=N.order,re=N.price,ie=N.quantity,le=N.quantityText,de=N.isBuy,Be=N.text,ve=N.priceTooltipText,Ce=N.pnlText,ce=(0,Pr.$L)(w),Ke=(0,Pr.OX)(w),Ze=(0,Pr.xL)(w),Ge=ce==="LIMIT",Xe=(0,Ir.Y)(w);return{id:String(Ke),text:Be,price:+re,quantity:le,isBuy:de,editable:Xe&&G,tooltip:ve,modifyTooltip:w.closePosition||!Ge?"":void 0,onMove:G?function(Ie){var He=Ie.price;te(w),Tt({type:"TV_vertical"}),_({order:w,price:+He,quantity:+Ze,lastPrice:T})}:void 0,onModify:w.closePosition||!Ge||!Xe?void 0:function(){Tt({type:"TV_amountclick"}),O({order:w,price:re.toString(),quantity:ie,lastPrice:T})},onCancel:function(){Tt({type:"TV_cancel"}),W(w)}}})},[G,K,T,te,_,O,W,s]),oe=(0,r.useMemo)(function(){return Y.map(function(N){var w=N.id,re=N.value,ie=N.text,le=N.onChange,de=N.isBuy,Be=N.type;return{id:w,price:+re,quantity:"",text:ie,isBuy:de,editable:!0,limitOrderType:c.aA.OrderPreview,onMove:function(ve){var Ce=ve.price;(0,xr.eP)({namespace:a,orderType:Be}),le({id:w,newPrice:Ce})},onMoving:function(ve){var Ce=ve.price;return le({id:w,newPrice:Ce})}}})},[Y,a]),Te=(0,r.useMemo)(function(){return(0,d._)(J).concat((0,d._)(oe))},[J,oe]);return{orderLines:Te}},Vt=e(17855),Bt=function(t,n){var a=(0,r.useState)(t()),u=a[0],s=a[1];return(0,Vt.A)(function(){return s(t())},n),u},Kt=e(58656),Ht=function(t){var n=t.symbol,a=(0,Kt.Q)({symbol:n})||[],u=Bt(function(){return a.map(function(s){var g=s.position,S=s.pnl,K=s.pnlInNumber,Y=s.pnlText,H=s.price,j=s.quantity,$=s.reverseText,f=s.isBuy,l=s.displayReverseOrderModal,T=s.displayClosePositionModal;return{id:g.id||"".concat(g.symbol,"-").concat(g.positionSide),PNL:K,text:"".concat(Y," ").concat(S),price:H,quantity:j,tooltip:"",reverseTooltip:$,isBuy:f,onReverse:l,onClose:function(){T(),(0,gt.u4)("$WebClick",{item:1,module:"xxx",$element_id:"kline_close_position"})}}})},[a]);return{positionLines:u}},jt=e(47354),bt=e.n(jt),$t=e(87017),Ut=e(99168),Yt=e(3742),Wt=e(43335),Gt=e(15540),zt=e(20163),Qt=e(47255);const Jt=t=>({endTime:n})=>(n??0)===t;var Zt=e(36488),Ct=e(48018),Xt=e(82911),We=e(69578),wt=e(53208),Rt=e(19862),qt=e(8874),en=e(26679),rn=e(66547),It=(function(){var t=(0,o._)(function(n){var a,u,s,g,S,K,Y,H,j;return(0,i._)(this,function($){switch($.label){case 0:a=n.onFetchKlineData,u=n.startTime,s=n.endTime,g=n.interval,S=n.limit,K=S===void 0?1e3:S,Y=[],H=[],j=s,$.label=1;case 1:return[4,a((0,cr._)({interval:g,limit:K},j===0?{}:{endTime:j}))];case 2:if(H=$.sent(),H.length===0)return[3,4];H.forEach(function(f){var l=(0,v._)(f,8),T=l[0],W=l[1],_=l[2],O=l[3],G=l[4],te=l[5],J=l[7];Y.push({time:+T,open:+W,high:+_,low:+O,close:+G,volume:+te,quantity:+J})}),j=H[0][0]-1,$.label=3;case 3:if(j>u)return[3,1];$.label=4;case 4:return Y.sort(function(f,l){return f.time-l.time}),[2,Y]}})});return function(a){return t.apply(this,arguments)}})(),tn=function(t){var n=t.firstDataRequest,a=t.hasRequested,u=t.rangeEndDate;return n&&!a?0:u*1e3},nn=e(78142),an=e(16238),St=e.n(an),on=e(98087),xt=e.n(on),sn=e(55009),cn=e.n(sn),kt=e(56477);const Et=t=>cn()(t.chartsCount(),n=>t.chart(n)),un=async t=>{try{const{schema:n,data:a}=await t.exportData({includedStudies:[]}),u=xt()(a)?.[n.findIndex(s=>s.type==="time")];return u!==void 0&&Number.isFinite(u)?u*1e3:void 0}catch{return}},ln=({getWidget:t,...n})=>{const a=new Map,u=new Map,s=()=>{try{const f=t();return f?St()(Et(f).map(l=>l.symbolExt()?.ticker)):[]}catch{return[]}},g=f=>{const l=s();return!l.length||l.includes(f)},S=f=>{const l=u.get(f);if(l)return l;const T={controller:new AbortController,claimedAsMain:!1};return u.set(f,T),T},K=f=>{const l=u.get(f);l&&(l.controller.abort(),u.delete(f))},Y=()=>{const f=s();f.length&&Array.from(u.keys()).forEach(l=>{const T=u.get(l);if(T){if(f.includes(l)){T.claimedAsMain=!0;return}a.has(l)||T.claimedAsMain&&K(l)}})},H=(f,l)=>{f.closed||l.time{f.held=[];let T=l;try{const _=(await f.options.fetchSince(l)).filter(O=>O.time>=l&&!f.closed);_.forEach(O=>{f.highWater=Math.max(f.highWater,O.time),f.options.onTick(O)}),T=xt()(_)?.time??l}finally{const W=f.held??[];f.held=void 0,W.filter(_=>_.time>=T).forEach(_=>{H(f,_)})}},$=(0,kt.k)({...n,onCheck:Y,repair:async()=>{const f=t();if(f)try{const l=Et(f),T=St()(l.map(_=>{const O=a.get(_.symbolExt()?.ticker??"");return O&&{chart:_,feed:O}}));if(!T.length){f.resetCache(),l.forEach(_=>{_.resetData()});return}const W=St()(await Promise.all(T.map(async({chart:_,feed:O})=>{const G=await un(_);return G===void 0?void 0:{feed:O,fromMs:G}})));await Promise.allSettled(W.map(({feed:_,fromMs:O})=>j(_,O)))}catch{}}});return{openFeed:f=>{const l={options:f,held:void 0,highWater:0,closed:!1,detector:void 0};return l.detector=(0,kt.k)({...n,subscribeSignals:f.subscribeSignals,repair:async()=>{l.closed||!l.highWater||g(f.ticker)||await j(l,l.highWater)}}),a.set(f.ticker,l),g(f.ticker)&&$.resubscribeSignals(f.subscribeSignals),{push:T=>{l.held?l.held.push(T):H(l,T)},close:()=>{l.closed=!0,l.detector?.dispose(),a.get(f.ticker)===l&&a.delete(f.ticker)}}},subscribeWake:$.subscribeWake,getSignal:f=>{const l=S(f);return s().includes(f)&&(l.claimedAsMain=!0),l.controller.signal},dispose:()=>{u.forEach(({controller:f})=>{f.abort()}),u.clear(),a.forEach(f=>{f.detector?.dispose()}),a.clear(),$.dispose()}}};var dn=e(71730),vn=e(36620),fn=function(t){var n=t.namespace,a=k.y$(n),u=(0,vn.l)().getSocketSignals,s=(0,r.useRef)();(0,r.useEffect)(function(){return function(){var S;(S=s.current)===null||S===void 0||S.instance.dispose(),s.current=void 0}},[]);var g=(0,r.useCallback)(function(S){var K,Y,H=S;if(((K=s.current)===null||K===void 0?void 0:K.key)===H)return s.current.instance;(Y=s.current)===null||Y===void 0||Y.instance.dispose();var j=nn.wr(S),$=ln({getWidget:function(){return a.getState().tradingViewReference},checkPeriodMs:dn.D(j)});return s.current={key:H,instance:$},$},[a]);return(0,r.useMemo)(function(){return{ensureRecovery:g,getSocketSignals:u}},[g,u])},mn="tv_web_error",pn=[Ye.bt.TRADING,Ye.bt.DELIVERING],Re;(function(t){t.Futures="futures",t.FuturesMark="futures-mark",t.FuturesIndex="futures-index",t.Delivery="delivery",t.DeliveryMark="delivery-mark",t.DeliveryIndex="delivery-index"})(Re||(Re={}));var Mt=function(t){var n=t.symbol,a=t.description,u=t.productType,s=t.pricePrecision,g=t.params,S=t.supportedResolutions,K=S===void 0?F:S;return{symbol:n,description:a,tickSize:Math.pow(10,s),exchange:"Binance",supportedResolutions:K,type:u,params:g}},yn=function(t){var n=t.namespace,a=t.useMiniTicker,u=a===void 0?!1:a,s=t.isCM,g=c.y$(n),S=k.y$(n),K=(0,$t.useQueryClient)(),Y=fn({namespace:n}),H=Y.ensureRecovery,j=Y.getSocketSignals,$=(0,r.useRef)(0),f=(0,r.useRef)(!1),l=(0,r.useMemo)(function(){return bt()(function(){return(0,We.HP)({eventId:"trd_chart_tv_load_timeout",stepId:"getBarsStart",catch:function(Q){var ee=Q.error,ae=Q.track;ae({GET_BAR_START_TYPE:ee})},log:function(Q){var ee=Q.track;ee({GET_BAR_START_TYPE:"SUCCESS"})}})})},[]),T=(0,r.useMemo)(function(){return bt()(function(z){return(0,We.HP)({eventId:"trd_chart_tv_load_timeout",stepId:"getBarsEnd",catch:function(ee){var ae=ee.error,Se=ee.track;Se({GET_BAR_END_TYPE:ae,duration_:(0,We.Pu)(z-$.current)})},log:function(ee){var ae=ee.track;ae({GET_BAR_END_TYPE:"SUCCESS",duration_:(0,We.Pu)(z-$.current)})}})})},[]),W=(0,Nr.o)("trd-chart","trade-ui"),_=W.getI18n,O=(0,he.ud)(),G=O.getI18n,te=(0,v._)(g(c.Ir),1),J=te[0],oe=(0,v._)(S(k.A7),2),Te=oe[1],N=(0,se.Ye)(),w=(0,Zt.JD)(s).isContUnit,re=(0,se.y_)(),ie=(0,r.useRef)(N),le=(0,r.useRef)(J),de=(0,r.useRef)(re);(0,r.useEffect)(function(){ie.current=N},[N]),(0,r.useEffect)(function(){le.current=J},[J]),(0,r.useEffect)(function(){de.current=re},[re]);var Be=(0,r.useMemo)(function(){return w?Rt.b:Rt.S},[w]),ve=(0,r.useMemo)(function(){return G("usdtFutures-13",{defaultValue:"Futures"})||""},[G]),Ce=(0,r.useMemo)(function(){return G("inverseFutures-13",{defaultValue:"Delivery"})||""},[G]),ce=(0,r.useMemo)(function(){return _("indexPrice",{defaultValue:"Index Price"})||""},[_]),Ke=(0,r.useMemo)(function(){return _("markPrice",{defaultValue:"Mark Price"})||""},[_]),Ze=(0,r.useMemo)(function(){return _("lastPrice",{defaultValue:"Last Price"})||""},[_]),Ge=(0,r.useMemo)(function(){return{exchanges:[{name:"Binance",value:"Binance",desc:""}],symbols_types:[{name:ve,value:Re.Futures},{name:"".concat(ve," ").concat(Ke),value:Re.FuturesMark},{name:"".concat(ve," ").concat(ce),value:Re.FuturesIndex},{name:Ce,value:Re.Delivery},{name:"".concat(Ce," ").concat(Ke),value:Re.DeliveryMark},{name:"".concat(Ce," ").concat(ce),value:Re.DeliveryIndex}]}},[Ce,ve,ce,Ke]),Xe=(0,r.useCallback)((0,o._)(function(){var z,Q,ee;return(0,i._)(this,function(ae){switch(ae.label){case 0:return ae.trys.push([0,2,,3]),[4,(0,en.c)(K)];case 1:return ae.sent(),[3,3];case 2:return z=ae.sent(),(0,gt.u4)(mn,{type:"onFetchSymbolsInfo",message:z.message,business:"futures"}),[3,3];case 3:return Q=ie.current,ee=Object.keys(Q).reduce(function(Se,xe){var ne=Q[xe]||Ct.o,ke=ne.symbol,Me=ne.pricePrecision,fe=ne.isDelivery,Pe=ne.contractStatus;if(!pn.includes(Pe))return Se;var _e=(0,Yt.S)(ne,G),je=_e.symbol,Ee=_e.contractTypeI18n,me=_e.deliveryDateFormat,ye="".concat(je," ").concat(Ee).concat(me?" ".concat(me):""),$e="".concat(ye," ").concat(Ze),Oe="".concat(ye," ").concat(ce),lr="".concat(ye," ").concat(Ke);return Se.concat([Mt({symbol:ke,description:$e,productType:fe?Re.Delivery:Re.Futures,pricePrecision:Me,params:{priceType:c.SJ.Last}}),Mt({symbol:ke,description:Oe,productType:fe?Re.DeliveryIndex:Re.FuturesIndex,pricePrecision:Me,params:{priceType:c.SJ.Index}}),Mt({symbol:ke,description:lr,productType:fe?Re.DeliveryMark:Re.FuturesMark,pricePrecision:(0,Xt.X)(Me),params:{priceType:c.SJ.Mark}})])},[]),[2,ee]}})}),[G,ce,Ke,Ze,K]),Ie=(0,r.useCallback)(function(z){var Q=z.ticker,ee=z.symbol,ae=z.interval,Se=z.priceType,xe=z.retryUntil,ne=ie.current[ee]||Ct.o,ke=ne.pair,Me=ne.contractType,fe=ne.contractVal,Pe=ne.isDelivery,_e=Pe?Be(fe):function(Oe){return Oe},je=(0,wt.h)({symbol:ee,pair:ke,contractType:Me,isDelivery:Pe,priceType:Se}),Ee,me,ye=function(Oe){return zt.y("tv","".concat(Pe?"cm":"um","|").concat(ee,"|").concat(Se,"|").concat(Oe.interval,"|").concat((Ee=Oe.limit)!==null&&Ee!==void 0?Ee:"","|").concat((me=Oe.endTime)!==null&&me!==void 0?me:""))};if(xe===void 0)return{transform:_e,onFetchKlineData:je};var $e=H(ae);return{transform:_e,onFetchKlineData:Qt.b(je,{enabled:Jt(xe),key:ye,signal:$e.getSignal(Q),subscribeWake:$e.subscribeWake})}},[H,Be]),He=(0,r.useCallback)((function(){var z=(0,o._)(function(Q){var ee,ae,Se,xe,ne,ke,Me,fe,Pe,_e,je,Ee,me,ye,$e,Oe,lr,Or;return(0,i._)(this,function(dr){switch(dr.label){case 0:return ee=Q.binanceInterval,ae=Q.rangeStartDate,Se=Q.rangeEndDate,xe=Q.tickerInfo,ne=Q.symbolInfo,ke=Q.firstDataRequest,$.current=Date.now(),l(),Me=xe.symbol,fe=Me===void 0?"":Me,Pe=xe.params,_e=Pe.priceType,je=_e===void 0?le.current:_e,de.current({symbol:fe,priceType:je})?(T(Date.now()),[2,[]]):(Ee=Math.max(0,ae*1e3),me=tn({firstDataRequest:ke,hasRequested:f.current,rangeEndDate:Se}),f.current=!0,$e=Ie({ticker:(ye=ne.ticker)!==null&&ye!==void 0?ye:"",symbol:fe,interval:ee,priceType:je,retryUntil:ke?me:void 0}),Oe=$e.transform,lr=$e.onFetchKlineData,[4,It({onFetchKlineData:lr,startTime:Ee,endTime:me,interval:ee})]);case 1:return Or=dr.sent(),T(Date.now()),[2,Or.map(Oe)]}})});return function(Q){return z.apply(this,arguments)}})(),[Ie,T,l]),ur=(0,r.useCallback)(function(z){var Q=z.binanceInterval,ee=z.callback,ae=z.tickerInfo,Se=z.symbolInfo,xe=ae.symbol,ne=xe===void 0?"":xe,ke=ae.params,Me=ke.priceType,fe=Me===void 0?le.current:Me;if(de.current({symbol:ne,priceType:fe}))return function(){return null};var Pe=ie.current[ne]||Ct.o,_e=Pe.pair,je=Pe.contractType,Ee=Pe.isDelivery,me,ye=(me=Se.ticker)!==null&&me!==void 0?me:"",$e=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe}).transform,Oe=H(Q),lr=Oe.openFeed({ticker:ye,onTick:ee,subscribeSignals:j(Ee),fetchSince:(function(){var dr=(0,o._)(function(Lr){var Ur,Yr,ct,De;return(0,i._)(this,function(ze){switch(ze.label){case 0:return Ur=Ie({ticker:ye,symbol:ne,interval:Q,priceType:fe,retryUntil:0}),Yr=Ur.transform,ct=Ur.onFetchKlineData,[4,It({onFetchKlineData:ct,startTime:Lr,endTime:0,interval:Q})];case 1:return De=ze.sent(),[2,De.map(Yr)]}})});return function(Lr){return dr.apply(this,arguments)}})()}),Or=(0,rn.B)({symbol:ne,pair:_e,contractType:je,interval:Q,priceType:fe,isDelivery:Ee,useMiniTicker:u,callback:function(dr){lr.push($e(dr))}});return function(){lr.close(),Or()}},[Ie,H,j,u]),Tr=(0,r.useCallback)((0,o._)(function(){var z;return(0,i._)(this,function(Q){switch(Q.label){case 0:return[4,K.fetchQuery(Wt.Bz.SERVER_TIME(s),function(){return(0,Gt.l)(s)},{})];case 1:return z=Q.sent(),[2,Math.floor(z/1e3)]}})}),[K,s]),_r=(0,r.useCallback)(function(z){return(0,cr._)({},z.template,(0,qt.sp)(s))},[s]),Cr=(0,r.useMemo)(function(){return ue()},[]),Er=Cr({config:Ge,onFetchSymbolsInfo:Xe,onFetchBars:He,onSubscribeBars:ur,onFetchServerTimeApi:Tr,onTransformSymbolInfo:_r}),we=Er.datafeed,Dr=Er.symbolsInfo;return(0,Ut.A)(function(){return Te()},[w]),{datafeed:we,symbolsInfo:Dr}},Pt="trd_chart_tv_load_timeout",hn=function(){var t=(0,We.PM)({id:"chartReady"}),n=(0,We.PM)({id:"getBarsStart",timeout:3}),a=(0,We.PM)({id:"getBarsEnd",timeout:6}),u=(0,We.PM)({id:"chartResponsive",timeout:3});We.HP.init({step:t._(n)._(a)._(u),eventId:Pt}),(0,We.HP)({eventId:Pt,stepId:"chartReady",catch:function(g){var S=g.error,K=g.track;K({CHART_READY_TYPE:S,connection:JSON.stringify(navigator.connection)})},log:function(g){var S=g.track;S({CHART_READY_TYPE:"SUCCESS",connection:JSON.stringify(navigator.connection)})}})},gn=function(t){(0,We.HP)({eventId:Pt,stepId:"chartResponsive",catch:function(a){var u=a.error,s=a.track;s({CHART_RESPONSIVE_TYPE:u,connection:JSON.stringify(navigator.connection)})},log:function(a){var u=a.track;u({CHART_RESPONSIVE_TYPE:"SUCCESS",duration_:(0,We.Pu)(Date.now()-t)})}})},Tn=[Qe.ZE,Qe.F3],Dt=function(t){var n=t.symbol,a=t.namespace,u=t.useMiniTicker,s=u===void 0?!1:u,g=c.y$(a),S=k.y$(a),K=(0,v._)(g(c.A8),1),Y=K[0],H=(0,v._)(g(c.Ir),2),j=H[0],$=H[1],f=(0,v._)(S(k.yj),1),l=f[0],T=(0,v._)(g(c.e6),1),W=T[0],_=(0,v._)(g(c._b),1),O=_[0],G=(0,he.ot)(),te=(0,pr.K7)(),J=te.pathname.includes(Ae.Q2),oe=te.pathname.includes(Ae.eq),Te=(0,r.useMemo)(function(){return J?(0,d._)(B.oT).concat(["hide_left_toolbar_by_default"]):B.oT},[J]),N=(0,se.Ye)(),w=(0,se.v3)({symbol:n}),re=(0,Fe.es)(n),ie=yn({namespace:a,useMiniTicker:s,isCM:re}),le=ie.datafeed,de=ie.symbolsInfo,Be=(0,Je.DP)(),ve=Be.theme,Ce=ve==="light"||ve==="light_glacier"?"light":"dark",ce=(0,yr.Z)(Ce),Ke=(0,r.useRef)(-1),Ze=Ht({symbol:n}).positionLines,Ge=Nt({symbol:n,namespace:a}).orderLines,Xe=ir({symbol:n}).executionOrders,Ie=pt({namespace:a,symbol:n}),He=(0,se.y0)(),ur=(0,r.useRef)(He),Tr=(0,r.useRef)(N),_r=(0,r.useRef)(l),Cr=(0,r.useRef)(J),Er=(0,r.useRef)(oe),we=(0,r.useRef)(de);(0,r.useEffect)(function(){we.current=de},[de]),(0,r.useEffect)(function(){ur.current=He},[He]),(0,r.useEffect)(function(){Tr.current=N},[N]),(0,r.useEffect)(function(){_r.current=l},[l]),(0,r.useEffect)(function(){Cr.current=J},[J]),(0,r.useEffect)(function(){Er.current=oe},[oe]),(0,Ve.a)(n,s),(0,sr.A)({symbol:n,interval:Y,priceType:j});var Dr=(0,r.useCallback)(function(){Ke.current=Date.now()},[]),z=(0,r.useCallback)((0,o._)(function(){var De,ze,qe,be,vr,fr,er;return(0,i._)(this,function(nr){switch(nr.label){case 0:if(Cr.current||!_r.current)return[2,Promise.resolve()];nr.label=1;case 1:return nr.trys.push([1,5,,6]),De=h().createInstance({name:a}),[4,De.keys()];case 2:return ze=nr.sent(),qe=ze.filter(function(mr){return/^((#TV_SYMBOL-)|(myTradingView))/g.test(mr)}),be=qe.map((function(){var mr=(0,o._)(function(ar){return(0,i._)(this,function(Sr){return[2,De.getItem(ar)]})});return function(ar){return mr.apply(this,arguments)}})()),[4,Promise.all(be)];case 3:return vr=nr.sent(),fr=U()(qe,vr),[4,Promise.all(Tn.map((function(){var mr=(0,o._)(function(ar){var Sr,Wr;return(0,i._)(this,function(Gr){switch(Gr.label){case 0:return a===ar?[2,Promise.resolve()]:(Sr=h().createInstance({name:ar}),Wr=__dispatchChartMirror(Sr,fr,()=>fr.map((function(){var Sn=(0,o._)(function(Lt){var _t,Ot,At;return(0,i._)(this,function(Mn){return _t=(0,v._)(Lt,2),Ot=_t[0],At=_t[1],Ot?[2,Sr.setItem(Ot,At)]:[2,Promise.resolve()]})});return function(Lt){return Sn.apply(this,arguments)}})())),[4,Promise.all(Wr)]);case 1:return Gr.sent(),[2,Promise.resolve()]}})});return function(ar){return mr.apply(this,arguments)}})()))];case 4:return nr.sent(),[2,Promise.resolve()];case 5:return er=nr.sent(),console.warn(typeof er=="string"?er:er.message),[2,Promise.resolve()];case 6:return[2]}})}),[a]),Q=(0,r.useMemo)(function(){return z},[z]),ee=(0,or.R)(),ae=(0,Ne.fQ)(a),Se=(0,Ne.m3)(a),xe=(0,Ne.S_)(a),ne=(0,Ne.Ds)(a),ke=(0,r.useRef)(n),Me=(0,r.useRef)(Y),fe=(0,r.useCallback)(function(){return ee("tradingView"),xe({panel_index:0,symbol:n,interval:Y}),hn()},[Y,ee,n,xe]),Pe=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be,vr,fr,er=(fr=(vr=(be=qe.symbolExt())===null||be===void 0?void 0:be.name)!==null&&vr!==void 0?vr:qe.symbol())!==null&&fr!==void 0?fr:"";ae({panel_index:ze,symbol:er,previous_symbol:ke.current}),ke.current=er},[ae]),_e=(0,r.useCallback)(function(De){var ze=De.index,qe=De.chart,be=qe.resolution();Se({panel_index:ze,interval:be,previous_interval:Me.current}),Me.current=be},[Se]),je=(0,r.useCallback)(function(){gn(Ke.current)},[]),Ee=(0,r.useCallback)(function(){},[]),me=(0,r.useMemo)(function(){return J?Ee:void 0},[Ee,J]),ye=(0,r.useCallback)((function(){var De=(0,o._)(function(ze){var qe,be,vr,fr,er,nr,mr,ar,Sr;return(0,i._)(this,function(Wr){switch(Wr.label){case 0:return qe=ze.chart,be=qe.symbolExt(),be?(vr=b()(we.current,function(Gr){return Gr.ticker===be.ticker||Gr.symbol===be.ticker}),fr=vr||{},er=fr.params,nr=er===void 0?{}:er,mr=nr.priceType,ar=mr===void 0?"":mr,ar&&$(ar),Sr=Tr.current[be.name],Sr?Er.current||Cr.current?[2]:[4,z()]:[2]):[2];case 1:return Wr.sent(),G({stateSymbol:Sr.symbol,isDeliveryColumn:Sr.isDelivery}),[2]}})});return function(ze){return De.apply(this,arguments)}})(),[G,z,$]),$e=qr(),Oe=$e.onCrosshairMoved,lr=$e.crosshairPrice,Or=wr({namespace:a,symbol:n,priceType:j,crosshairPrice:lr}),dr=lt(),Lr=(0,r.useMemo)(function(){return L({symbol:n,params:{priceType:j}})},[n,j]),Ur=(0,r.useMemo)(function(){return{symbol:Lr,theme:ce,limitOrders:Ge,positionOrders:Ze,executionOrders:Xe,labelLines:Ie,initialConfig:{tvConfig:{symbol:Lr,library_path:"/".concat(Ye.u_,"/trading-platform-30/"),theme:ce,datafeed:le,enabled_features:Te,context_menu:Or,custom_formatters:dr},onCrosshairMoved:Oe,onScriptLoaded:Dr,onSave:Q,onChartReadyDone:fe,onFirstTimeDataLoadedDone:je,onInitChart:me,onActiveChartChanged:ye,onSymbolChanged:Pe,onIntervalChanged:_e}}},[Lr,le,Te,Xe,Ie,Or,dr,Oe,ye,fe,je,me,_e,Q,Dr,Pe,Ge,Ze,ce]),Yr=Le({namespace:a,type:"tv"}),ct=(0,r.useCallback)(function(){ne({panel_index:0,symbol:n}),Yr()},[Yr,n,ne]);return(0,p.jsxs)(D.t,{name:"trading_view",onReset:ct,children:[(0,p.jsx)(Ue.A,{namespace:a,tradingViewProps:Ur}),w&&j==="index"&&O===c.Ev.Single&&W===c.tU.TradingView&&(0,p.jsx)(rr.B,{})]})};const Cn=Dt}}[70940];
+}
+
+/** Registration rejects source drift before any replacement can reach the runtime cache. */
+export function replaceChartMirrorFactory(originalFactory, dispatch) {
+ if (typeof originalFactory !== 'function' || Function.prototype.toString.call(originalFactory) !== originalFactorySource) {
+ throw new Error('Mirror factory source does not match the pinned public module');
+ }
+ return createMirrorFactory(dispatch);
+}
diff --git a/src/binance-orderbook-trade/chart-storage/mirror-writer.js b/src/binance-orderbook-trade/chart-storage/mirror-writer.js
new file mode 100644
index 0000000..9b50091
--- /dev/null
+++ b/src/binance-orderbook-trade/chart-storage/mirror-writer.js
@@ -0,0 +1,207 @@
+import { snapshotJson, byteLength, equalJson } from './json.js';
+
+const DEFAULT_LIMITS = Object.freeze({
+ maxEntries: 512,
+ maxBatchBytes: 4 * 1024 * 1024,
+ maxReadBytes: 4 * 1024 * 1024,
+ maxPendingBatches: 16,
+ maxPendingBytes: 64 * 1024 * 1024,
+});
+
+function assert(condition, message) {
+ if (!condition) throw new Error(message);
+}
+
+/**
+ * Batch only the host's explicit mirror destination writes. The runtime owns URL
+ * admission; localForage retains its instances, API methods and connections.
+ * Native routing is allowed only before an IndexedDB transaction is created.
+ */
+export function createChartMirrorWriter({ limits: suppliedLimits = {} } = {}) {
+ const limits = { ...DEFAULT_LIMITS, ...suppliedLimits };
+ for (const [key, value] of Object.entries(limits)) {
+ assert(Object.hasOwn(DEFAULT_LIMITS, key), `Unknown mirror limit: ${key}`);
+ assert(Number.isSafeInteger(value) && value > 0, `${key} must be a positive safe integer`);
+ }
+ let phase = 'active';
+ let stopping;
+ let resolveDrain;
+ const stats = {
+ acceptedBatches: 0, rejectedBatches: 0, failedBatches: 0,
+ pendingBatches: 0, pendingBytes: 0, peakPendingBatches: 0, peakPendingBytes: 0,
+ transactions: 0, committedTransactions: 0, abortedTransactions: 0,
+ committedWrites: 0, skippedWrites: 0, nativePassthroughBatches: 0,
+ };
+
+ function stop() {
+ if (phase === 'active') {
+ phase = 'draining';
+ const drained = stats.pendingBatches === 0
+ ? Promise.resolve() : new Promise(resolve => { resolveDrain = resolve; });
+ stopping = drained.then(() => { phase = 'stopped'; });
+ }
+ return stopping;
+ }
+
+ function release(work) {
+ stats.pendingBatches -= 1;
+ stats.pendingBytes -= work.bytes;
+ if (phase === 'draining' && stats.pendingBatches === 0) resolveDrain();
+ }
+
+ /** Native Promise.all may reject early; the lifecycle fence waits for every map member. */
+ function nativeBeforeTransaction(work, nativeThunk) {
+ stats.rejectedBatches += 1;
+ stats.nativePassthroughBatches += 1;
+ stats.pendingBytes -= work.bytes;
+ work.bytes = 0;
+ let results;
+ try {
+ results = nativeThunk();
+ } catch (error) {
+ release(work);
+ return Promise.reject(error);
+ }
+ Promise.allSettled(results).then(() => release(work));
+ return Promise.all(results);
+ }
+
+ /** Request callbacks keep the real transaction alive without awaiting another task. */
+ function transact(transaction, entries) {
+ return new Promise((resolve, reject) => {
+ let index = 0;
+ let writes = 0;
+ let skipped = 0;
+ let failure;
+ const budget = { remaining: limits.maxReadBytes };
+ transaction.oncomplete = () => {
+ stats.committedTransactions += 1;
+ stats.committedWrites += writes;
+ stats.skippedWrites += skipped;
+ resolve();
+ };
+ transaction.onabort = () => {
+ stats.abortedTransactions += 1;
+ reject(failure || transaction.error || new DOMException('Chart mirror batch aborted', 'AbortError'));
+ };
+ transaction.onerror = event => { failure = event.target.error; };
+
+ function guard(action) {
+ try {
+ action();
+ } catch (error) {
+ failure = error;
+ try {
+ transaction.abort();
+ } catch (abortError) {
+ // A concurrent native abort listener may already own the final event.
+ if (abortError instanceof DOMException && abortError.name === 'InvalidStateError') return;
+ throw abortError;
+ }
+ }
+ }
+
+ let store;
+ function advance() {
+ if (index === entries.length) return;
+ const [key, value] = entries[index];
+ function next() { index += 1; advance(); }
+ function put() {
+ const request = store.put(value === null ? undefined : value, key);
+ writes += 1;
+ request.onsuccess = () => guard(next);
+ }
+ if (budget.remaining <= 0) { put(); return; }
+ const read = store.get(key);
+ read.onsuccess = () => guard(() => {
+ let current;
+ try {
+ current = snapshotJson(read.result === undefined ? null : read.result, budget);
+ } catch {
+ // Unsupported old values and exhausted comparison budgets only disable
+ // deduplication; they must not discard this otherwise valid mirror.
+ budget.remaining = 0;
+ put();
+ return;
+ }
+ function writeIfChanged(present) {
+ if (present && equalJson(current, value)) { skipped += 1; next(); }
+ else put();
+ }
+ if (read.result === undefined && value === null) {
+ const exists = store.getKey(key);
+ exists.onsuccess = () => guard(() => writeIfChanged(exists.result !== undefined));
+ } else writeIfChanged(read.result !== undefined);
+ });
+ }
+ guard(() => { store = transaction.objectStore('keyvaluepairs'); advance(); });
+ });
+ }
+
+ async function write(target, entries, work, nativeThunk) {
+ let transaction;
+ try {
+ await target.ready();
+ assert(target.driver() === 'asyncStorage', 'Chart mirror requires the native IndexedDB driver');
+ const info = target._dbInfo;
+ assert((info.name === 'chart_futures' || info.name === 'chart_delivery') && info.storeName === 'keyvaluepairs',
+ 'Unexpected chart mirror database configuration');
+ transaction = info.db.transaction('keyvaluepairs', 'readwrite');
+ } catch {
+ return nativeBeforeTransaction(work, nativeThunk);
+ }
+ stats.acceptedBatches += 1;
+ stats.transactions += 1;
+ try {
+ await transact(transaction, entries);
+ } catch (error) {
+ stats.failedBatches += 1;
+ stop();
+ throw error;
+ } finally {
+ release(work);
+ }
+ }
+
+ function dispatch(target, entries, nativeThunk) {
+ assert(typeof nativeThunk === 'function', 'Chart mirror dispatch requires its original native expression');
+ if (phase === 'stopped') return nativeThunk();
+ if (phase === 'draining') return [stopping.then(() => Promise.all(nativeThunk()))];
+
+ // Register before inspecting caller data: a Proxy can synchronously call stop.
+ const work = { bytes: 0 };
+ stats.pendingBatches += 1;
+ let copied;
+ try {
+ assert(Array.isArray(entries), 'Chart mirror entries must be an array');
+ if (entries.length === 0) { release(work); return []; }
+ stats.peakPendingBatches = Math.max(stats.peakPendingBatches, stats.pendingBatches);
+ assert(entries.length <= limits.maxEntries, 'Chart mirror entry limit exceeded');
+ assert(stats.pendingBatches <= limits.maxPendingBatches, 'Chart mirror pending batch limit exceeded');
+ const budget = { remaining: limits.maxBatchBytes };
+ const seen = new Set();
+ const keys = new Set();
+ copied = entries.map(entry => {
+ assert(Array.isArray(entry) && entry.length === 2, 'Chart mirror entry must be a key/value pair');
+ const [key, value] = entry;
+ assert(typeof key === 'string' && (key.startsWith('#TV_SYMBOL-') || key.startsWith('myTradingView')),
+ 'Chart mirror received a non-chart key');
+ assert(!keys.has(key), 'Chart mirror keys must be unique');
+ keys.add(key);
+ budget.remaining -= byteLength(key) + 3;
+ return [key, snapshotJson(value, budget, seen)];
+ });
+ const bytes = byteLength(copied);
+ assert(bytes <= limits.maxBatchBytes, 'Chart mirror batch byte limit exceeded');
+ assert(stats.pendingBytes + bytes <= limits.maxPendingBytes, 'Chart mirror pending byte limit exceeded');
+ work.bytes = bytes;
+ stats.pendingBytes += bytes;
+ stats.peakPendingBytes = Math.max(stats.peakPendingBytes, stats.pendingBytes);
+ } catch {
+ return [nativeBeforeTransaction(work, nativeThunk)];
+ }
+ return [write(target, copied, work, nativeThunk)];
+ }
+
+ return Object.freeze({ dispatch, getStats: () => ({ ...stats }), getPhase: () => phase, stop });
+}
diff --git a/src/binance-orderbook-trade/chart-storage/runtime.js b/src/binance-orderbook-trade/chart-storage/runtime.js
new file mode 100644
index 0000000..7e73ed9
--- /dev/null
+++ b/src/binance-orderbook-trade/chart-storage/runtime.js
@@ -0,0 +1,87 @@
+import { observeChartStorageBootstrap } from './bootstrap.js';
+import { replaceChartMirrorFactory } from './mirror-module.js';
+import { createChartMirrorWriter } from './mirror-writer.js';
+import { isChartStoragePage } from './scope.js';
+
+const CAPTURE_DEADLINE_MS = 30_000;
+
+/**
+ * The sole interception point is the pinned mirror destination expression.
+ * Unsupported startup retires the hook; it never executes modules to find them.
+ * A retained callback drains accepted writes before resuming the native map.
+ */
+export function startChartStorageOptimizer() {
+ if (!isChartStoragePage()) throw new Error('Chart storage requires a top-level Binance trading page');
+ const writer = createChartMirrorWriter();
+ const state = { status: 'waiting', reason: null, attempts: 0, matches: 0, executions: 0 };
+ let observer;
+ let timer;
+ let stopping;
+
+ function snapshot() {
+ const stats = writer.getStats();
+ return { ...state, status: stats.failedBatches > 0 ? 'native_after_failure' : state.status,
+ phase: writer.getPhase(), writer: stats };
+ }
+
+ function stop(reason = 'manual') {
+ if (stopping) return stopping;
+ state.status = 'stopping';
+ state.reason = reason;
+ clearTimeout(timer);
+ self.removeEventListener('pagehide', onPageHide);
+ if (observer) observer.stop();
+ stopping = writer.stop().then(() => {
+ state.status = 'native';
+ return snapshot();
+ });
+ return stopping;
+ }
+
+ function onPageHide() { void stop('pagehide'); }
+
+ function dispatch(target, entries, nativeThunk) {
+ if (!isChartStoragePage()) void stop('scope_changed');
+ return writer.dispatch(target, entries, nativeThunk);
+ }
+
+ try {
+ observer = observeChartStorageBootstrap({
+ replaceMirrorFactory(original) {
+ if (!isChartStoragePage()) {
+ void stop('scope_changed');
+ return original;
+ }
+ state.attempts += 1;
+ let replacement;
+ try {
+ replacement = replaceChartMirrorFactory(original, dispatch);
+ } catch {
+ // Source drift is an expected release boundary; leave registration native.
+ void stop('source_mismatch');
+ return original;
+ }
+ state.matches += 1;
+ return function (...args) {
+ state.executions += 1;
+ return Reflect.apply(replacement, this, args);
+ };
+ },
+ onCapture() {
+ clearTimeout(timer);
+ state.status = 'active';
+ },
+ });
+ } catch {
+ // A late extension cannot safely replace the runtime's cached module.
+ void stop('bootstrap_unavailable');
+ return Object.freeze({ snapshot, stop });
+ }
+
+ observer.captured.catch(() => {
+ if (!stopping) void stop('capture_failed');
+ });
+ self.addEventListener('pagehide', onPageHide, { once: true });
+ timer = setTimeout(() => { void stop('capture_deadline'); }, CAPTURE_DEADLINE_MS);
+ return Object.freeze({ snapshot, stop });
+}
diff --git a/src/binance-orderbook-trade/chart-storage/scope.js b/src/binance-orderbook-trade/chart-storage/scope.js
new file mode 100644
index 0000000..6c507ea
--- /dev/null
+++ b/src/binance-orderbook-trade/chart-storage/scope.js
@@ -0,0 +1,7 @@
+import { isFuturesTradingPathname } from '../../shared/binance-futures-route.js';
+
+/** Storage admission follows the same concrete contract routes as the trading tools. */
+export function isChartStoragePage() {
+ return self === top && location.origin === 'https://www.binance.com'
+ && isFuturesTradingPathname(location.pathname);
+}
diff --git a/src/binance-orderbook-trade/index.user.js b/src/binance-orderbook-trade/index.user.js
index a9751ea..74ca1ea 100644
--- a/src/binance-orderbook-trade/index.user.js
+++ b/src/binance-orderbook-trade/index.user.js
@@ -3,7 +3,7 @@
// @namespace binance.orderbook.trade
// @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E
// @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E
-// @version 2.7.217
+// @version 2.7.218
// @author jackhai9
// @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板
// @match https://www.binance.com/*/futures/*
@@ -13,6 +13,7 @@
// @updateURL https://raw.githubusercontent.com/jackhai9/userscripts/main/scripts/binance-orderbook-trade.user.js
// @downloadURL https://raw.githubusercontent.com/jackhai9/userscripts/main/scripts/binance-orderbook-trade.user.js
// @run-at document-start
+// @sandbox raw
// @grant none
// ==/UserScript==
@@ -251,10 +252,24 @@ import {
getBinanceChartOrdersTarget as getBinanceChartOrdersTargetDom,
} from './dom/chart-orders.js';
import { showUsdtRebalanceDialog } from './dom/usdt-rebalance-dialog.js';
+import { installChartStorageOptimizer } from './chart-storage/install.js';
+
+installChartStorageOptimizer();
(function initializeOrderbookTrade() {
'use strict';
+ // Page-world injection may precede HTML creation; storage capture must still start immediately.
+ if (!document.documentElement) {
+ const rootObserver = new MutationObserver(() => {
+ if (!document.documentElement) return;
+ rootObserver.disconnect();
+ initializeOrderbookTrade();
+ });
+ rootObserver.observe(document, { childList: true });
+ return;
+ }
+
function isFuturesTradingPage() {
return isFuturesTradingPathname(location.pathname);
}
diff --git a/test/fixtures/binance-chart-storage/README.md b/test/fixtures/binance-chart-storage/README.md
new file mode 100644
index 0000000..5ab3556
--- /dev/null
+++ b/test/fixtures/binance-chart-storage/README.md
@@ -0,0 +1,77 @@
+# Binance chart-storage source fixtures
+
+These public source snapshots are test inputs for an isolated chart-storage
+prototype. They are not userscripts, application startup scripts, or production
+assets. Do not import them from `src/`, add them to userscript builds, or execute
+them on a real Binance page. The repository build targets are explicit `src/`
+entrypoints and do not include this fixture directory.
+
+## Files and provenance
+
+- `webpack-runtime.js` is the exact, unformatted response body from
+ . It identifies its
+ bundler as Rspack 1.7.11 and implements the webpack-compatible
+ `self.webpackChunkfutures_trade_ui` chunk interface.
+- `localforage-module.js` contains the complete original module `43917` method
+ factory from . Its inline
+ localForage 1.10.0 / Mozilla / Apache License 2.0 notice is preserved verbatim.
+ The only surrounding code is a registration-only chunk with the string ID
+ `codex-chart-storage-fixture` and the original module ID `43917`.
+- `manifest.json` records retrieval timestamps, source URLs, SHA-256 checksums,
+ byte counts, extraction offsets, dependency inspection, and verification.
+ The full main bundle was a temporary extraction input and is not retained.
+
+Retrieved on 2026-10-06 UTC. These snapshots establish that dated public source
+contract; they do not establish which bundle a later live page has loaded.
+
+## Extraction contract
+
+Python's `urllib.request.urlopen` retrieved both public URLs without reading any
+credential files. Acorn 8.18.0 parsed the main bundle as an ECMAScript script. The
+extractor selected the unique numeric property `43917` in the first chunk push's
+module map and copied its original source range. It did not match braces with a
+regular expression or regenerate/minify the factory.
+
+The manifest ranges are zero-based UTF-16 string offsets with exclusive ends,
+matching Acorn and JavaScript `String.prototype.slice`. `module_property_sha256`
+checks the entire original `43917(_,y,e){...}` property.
+`factory_sha256` checks Acorn's function value range, `(_,y,e){...}`, without the
+numeric property key. Both hashes use the UTF-8 encoding of those exact source
+slices. `fixture_sha256` includes the new registration wrapper. The runtime hash
+checks downloaded bytes without transformation.
+
+The fixture payload has exactly two elements: chunk IDs and the module map. It
+contains no third runtime callback and no module require. The string chunk ID was
+absent from both downloaded source files, including the runtime's existing chunk
+maps.
+
+## Runtime and dependency boundaries
+
+Load the runtime and then the module fixture only in a fresh, isolated test
+context with an empty chunk queue. Registration adds the factory without
+executing it. The real runtime can later supply its require function through a
+separate test-owned chunk callback; explicitly requiring `43917` is a distinct
+step owned by the test harness.
+
+The factory imports no external webpack modules. It reads the third parameter's
+`g` property four times to obtain the global object for its bundled Browserify
+code. Preserve that real runtime contract; do not substitute an empty object for
+the webpack require argument. Browserify's modules are contained in the factory;
+its loader also contains the original missing-module `require` fallback.
+
+On an empty queue, the runtime installs module/chunk registration, deferred
+execution, and JS/CSS loading helpers. It does not start an application or load a
+chunk. It does process any preexisting queue, including runtime callbacks, so
+these files must not be injected into an existing page. The loader helpers retain
+their upstream network capabilities; isolation and network blocking belong to
+the test harness.
+
+Acorn scope inspection found exactly four references to the webpack parameter,
+all reading `.g`, and no external webpack require calls. A bounded Node VM check
+loaded the unmodified runtime, captured its real require in a test-owned empty
+chunk, and registered the fixture. It observed only module ID `43917`, zero
+factory invocations, and zero DOM, storage, network, or timer accesses. The check
+used failing accessors for those boundaries and a counting module-registration
+proxy. No localForage factory, browser, real page, or database was executed during
+fixture acquisition. Browser behavior and storage migration must be verified by
+the separate offline harness.
diff --git a/test/fixtures/binance-chart-storage/host-persistence-extract.js b/test/fixtures/binance-chart-storage/host-persistence-extract.js
new file mode 100644
index 0000000..bd6bfdb
--- /dev/null
+++ b/test/fixtures/binance-chart-storage/host-persistence-extract.js
@@ -0,0 +1,187 @@
+import assert from 'node:assert/strict';
+import { createHash } from 'node:crypto';
+import { readFile, readdir, writeFile } from 'node:fs/promises';
+import path from 'node:path';
+import { parse } from 'acorn';
+import { analyze } from 'eslint-scope';
+
+const roots = process.argv.slice(2);
+assert(roots.length > 0, 'Supply directories containing the public Binance source chunks');
+const modules = new Map();
+const sources = new Map();
+const sha = value => createHash('sha256').update(value).digest('hex');
+
+function visit(node, action) {
+ if (!node || typeof node !== 'object') return;
+ action(node);
+ for (const value of Object.values(node)) {
+ if (Array.isArray(value)) value.forEach(child => visit(child, action));
+ else if (value?.type) visit(value, action);
+ }
+}
+
+for (const root of roots) {
+ for (const file of (await readdir(root)).filter(name => name.endsWith('.js')).sort()) {
+ const source = await readFile(path.join(root, file), 'utf8');
+ const ast = parse(source, { ecmaVersion: 'latest', ranges: true });
+ const record = { file, source, sha256: sha(source) };
+ sources.set(file, record);
+ visit(ast, node => {
+ if (node.type !== 'CallExpression' || node.callee.type !== 'MemberExpression'
+ || node.callee.property.name !== 'push'
+ || !source.slice(node.callee.object.start, node.callee.object.end).includes('webpackChunkfutures_trade_ui')
+ || node.arguments[0]?.type !== 'ArrayExpression'
+ || node.arguments[0].elements[1]?.type !== 'ObjectExpression') return;
+ for (const property of node.arguments[0].elements[1].properties) {
+ if (property.type === 'Property' && typeof property.key.value === 'number') {
+ if (!modules.has(property.key.value)) modules.set(property.key.value, { ...record, property });
+ }
+ }
+ });
+ }
+}
+
+function freeNames(source) {
+ const ast = parse(source, { ecmaVersion: 'latest', ranges: true });
+ return new Set(analyze(ast, { ecmaVersion: 2024, sourceType: 'script' })
+ .globalScope.through.map(reference => reference.identifier.name));
+}
+
+/** Select complete original declarations, recursively retaining their lexical dependencies. */
+function declarations(moduleId, names) {
+ const module = modules.get(moduleId);
+ assert(module, `Missing source module ${moduleId}`);
+ const available = new Map();
+ for (const statement of module.property.value.body.body) {
+ if (statement.type === 'VariableDeclaration') {
+ for (const declaration of statement.declarations) {
+ if (declaration.id.type === 'Identifier') available.set(declaration.id.name, { node: declaration, kind: statement.kind });
+ }
+ } else if (statement.type === 'FunctionDeclaration') {
+ available.set(statement.id.name, { node: statement, kind: null });
+ }
+ }
+ const selected = new Map();
+ function add(name) {
+ if (selected.has(name)) return;
+ const declaration = available.get(name);
+ assert(declaration, `Missing declaration ${moduleId}:${name}`);
+ selected.set(name, declaration);
+ const raw = module.source.slice(declaration.node.start, declaration.node.end);
+ const wrapped = declaration.kind ? `${declaration.kind} ${raw};` : raw;
+ for (const dependency of freeNames(wrapped)) if (available.has(dependency)) add(dependency);
+ }
+ names.forEach(add);
+ return [...selected].sort((first, second) => first[1].node.start - second[1].node.start)
+ .map(([name, declaration]) => ({ ...module, ...declaration, name, moduleId }));
+}
+
+function nestedDeclarator(moduleId, name, predicate) {
+ const module = modules.get(moduleId);
+ const candidates = [];
+ visit(module.property.value.body, node => {
+ if (node.type === 'VariableDeclarator' && node.id.name === name && predicate(node)) candidates.push(node);
+ });
+ assert.equal(candidates.length, 1, `Expected exactly one ${moduleId}:${name}`);
+ return { ...module, node: candidates[0], moduleId };
+}
+
+const tv = declarations(76535, ['kt', 'wt', 'xt', 'zt', 'Wt']);
+const basic = declarations(15426, ['Wo', 'Dc']);
+const saveCallback = nestedDeclarator(76535, 'Tt', node => node.init.arguments?.[0]?.type === 'ArrowFunctionExpression');
+saveCallback.node = saveCallback.node.init.arguments[0];
+
+const mirrorModule = [...modules].find(([, module]) => module.file === 'TradingView.99bc5074.js'
+ && module.source.slice(module.property.start, module.property.end).includes('Tn=[Qe.ZE,Qe.F3]'));
+assert(mirrorModule, 'Missing original TradingView mirror module');
+const mirrorCallback = nestedDeclarator(mirrorModule[0], 'z', node => {
+ const raw = mirrorModule[1].source.slice(node.start, node.end);
+ return raw.includes('De.keys()') && raw.includes('Sr.setItem(Ot,At)');
+});
+mirrorCallback.node = mirrorCallback.node.init.arguments[0];
+const mirrorNames = [...freeNames(`const callback=${mirrorCallback.source.slice(mirrorCallback.node.start, mirrorCallback.node.end)};`)]
+ .filter(name => !['a', 'Cr', '_r', 'Promise', 'console'].includes(name));
+const mirror = declarations(mirrorModule[0], mirrorNames);
+
+const dependencies = new Set();
+function requiredModules(node, parameter) {
+ const found = new Set();
+ visit(node, child => {
+ if (child.type === 'CallExpression' && child.callee.type === 'Identifier'
+ && child.callee.name === parameter && child.arguments[0]?.type === 'Literal'
+ && typeof child.arguments[0].value === 'number') found.add(child.arguments[0].value);
+ });
+ return found;
+}
+function addModule(id) {
+ if (id === 43917 || dependencies.has(id)) return;
+ const module = modules.get(id);
+ assert(module, `Missing original dependency module ${id}`);
+ dependencies.add(id);
+ for (const child of requiredModules(module.property.value.body, module.property.value.params[2]?.name)) addModule(child);
+}
+for (const declaration of [...tv, ...basic, ...mirror]) {
+ for (const id of requiredModules(declaration.node, declaration.property.value.params[2].name)) addModule(id);
+}
+
+let fixture = '/* Exact public host persistence fragments; see host-persistence-manifest.json. */\n'
+ + '(self.webpackChunkfutures_trade_ui=self.webpackChunkfutures_trade_ui||[]).push([["codex-chart-host-persistence-fixture"],{\n';
+const fragments = [];
+function appendOriginal(record, node, label) {
+ const raw = record.source.slice(node.start, node.end);
+ const start = fixture.length;
+ fixture += raw;
+ fragments.push({
+ label, source_file: record.file, source_utf16_range: [node.start, node.end],
+ fixture_utf16_range: [start, fixture.length], sha256: sha(raw),
+ });
+}
+for (const id of [...dependencies].sort((first, second) => first - second)) {
+ const module = modules.get(id);
+ appendOriginal(module, module.property, `module:${id}`);
+ fixture += ',\n';
+}
+function appendDeclarations(items) {
+ for (const item of items) {
+ if (item.kind) fixture += `${item.kind} `;
+ appendOriginal(item, item.node, `declaration:${item.moduleId}:${item.name}`);
+ fixture += item.kind ? ';\n' : '\n';
+ }
+}
+fixture += '"codex-chart-host-tv"(module,exports,r){\n';
+appendDeclarations(tv);
+fixture += 'r.d(exports,{save:()=>xt,load:()=>zt,saveCustomSettings:()=>Wt,createSaveCallback:()=>createSaveCallback});\n'
+ + 'function createSaveCallback(storage,key,widget,onSave){const et={current:storage},st={current:key},p={current:widget},t={initialConfig:{onSave}};return ';
+appendOriginal(saveCallback, saveCallback.node, 'callback:76535:Tt');
+fixture += ';}\n},\n"codex-chart-host-basic"(module,exports,a){\n';
+appendDeclarations(basic);
+fixture += 'a.d(exports,{create:()=>Dc});\n},\n"codex-chart-host-mirror"(module,exports,e){\n';
+appendDeclarations(mirror);
+fixture += 'e.d(exports,{create:()=>createMirror});\n'
+ + 'function createMirror(namespace){const a=namespace,Cr={current:false},_r={current:true};return ';
+appendOriginal(mirrorCallback, mirrorCallback.node, `callback:${mirrorModule[0]}:z`);
+fixture += ';}\n}}]);\n';
+
+const usedFiles = [...new Set(fragments.map(fragment => fragment.source_file))].sort();
+const manifest = {
+ description: 'Exact source fragments from the public Binance chart persistence implementation, with original dependency factories.',
+ generated_at: new Date().toISOString(),
+ offset_contract: 'Zero-based UTF-16 String.slice offsets; exclusive ends; SHA-256 uses UTF-8 bytes.',
+ extraction: { parser: 'acorn', declarations: 'Lexical dependency closure; exact original declarator/function slices.', dependency_module_count: dependencies.size },
+ wrapper_contract: {
+ runtime: 'Existing captured Rspack runtime; localForage module 43917 comes from the existing fixture.',
+ tv: 'Only original persistence helpers and save callback; storage/key/widget/onSave references are supplied by the test.',
+ basic: 'Original Wo/Dc and their helpers; React subscriptions, debounce and Ec JSON serialization are not executed.',
+ mirror: 'Original async callback and original SWC/lodash dependencies; namespace is supplied, Cr.current=false and _r.current=true select the native enabled branch.',
+ exclusions: 'No chart rendering, exchange requests, login, production records, or real-page adapter installation.',
+ },
+ sources: usedFiles.map(file => ({
+ file, source_url: `https://bin.bnbstatic.com/static/${file.startsWith('main.') ? '' : 'chunks/'}${file}`,
+ sha256: sources.get(file).sha256, bytes: Buffer.byteLength(sources.get(file).source),
+ })),
+ fixture: { file: 'host-persistence.js', sha256: sha(fixture), bytes: Buffer.byteLength(fixture) },
+ fragments,
+};
+await writeFile(new URL('host-persistence.js', import.meta.url), fixture);
+await writeFile(new URL('host-persistence-manifest.json', import.meta.url), `${JSON.stringify(manifest, null, 2)}\n`);
+console.log(JSON.stringify({ fixtureBytes: Buffer.byteLength(fixture), dependencyModules: dependencies.size, fragments: fragments.length, sourceFiles: usedFiles }));
diff --git a/test/fixtures/binance-chart-storage/host-persistence-manifest.json b/test/fixtures/binance-chart-storage/host-persistence-manifest.json
new file mode 100644
index 0000000..cba3385
--- /dev/null
+++ b/test/fixtures/binance-chart-storage/host-persistence-manifest.json
@@ -0,0 +1,3617 @@
+{
+ "description": "Exact source fragments from the public Binance chart persistence implementation, with original dependency factories.",
+ "generated_at": "2026-10-06T13:39:08.155Z",
+ "offset_contract": "Zero-based UTF-16 String.slice offsets; exclusive ends; SHA-256 uses UTF-8 bytes.",
+ "extraction": {
+ "parser": "acorn",
+ "declarations": "Lexical dependency closure; exact original declarator/function slices.",
+ "dependency_module_count": 203
+ },
+ "wrapper_contract": {
+ "runtime": "Existing captured Rspack runtime; localForage module 43917 comes from the existing fixture.",
+ "tv": "Only original persistence helpers and save callback; storage/key/widget/onSave references are supplied by the test.",
+ "basic": "Original Wo/Dc and their helpers; React subscriptions, debounce and Ec JSON serialization are not executed.",
+ "mirror": "Original async callback and original SWC/lodash dependencies; namespace is supplied, Cr.current=false and _r.current=true select the native enabled branch.",
+ "exclusions": "No chart rendering, exchange requests, login, production records, or real-page adapter installation."
+ },
+ "sources": [
+ {
+ "file": "16873.442d9b04.js",
+ "source_url": "https://bin.bnbstatic.com/static/chunks/16873.442d9b04.js",
+ "sha256": "df41025e362f574e874621bd4cd03cae4fa0f21a6fd8634d2358c29cc045932d",
+ "bytes": 25068
+ },
+ {
+ "file": "28928.9aeec892.js",
+ "source_url": "https://bin.bnbstatic.com/static/chunks/28928.9aeec892.js",
+ "sha256": "0e8870ba5befe8aba1652a7a773264546fd82a5739f8a9da501680c75f6ada18",
+ "bytes": 614617
+ },
+ {
+ "file": "36648.dd8ae7dc.js",
+ "source_url": "https://bin.bnbstatic.com/static/chunks/36648.dd8ae7dc.js",
+ "sha256": "8191b50fb47212425d03146834207645c0e89b8ce623e23a71477194ee9a60ff",
+ "bytes": 24108
+ },
+ {
+ "file": "40784.08580559.js",
+ "source_url": "https://bin.bnbstatic.com/static/chunks/40784.08580559.js",
+ "sha256": "437eeea6e220f2b25abb32e7a2d3af589be5b95815d0ba5dbb7060e0a57cdfa8",
+ "bytes": 37118
+ },
+ {
+ "file": "56679.46a7195c.js",
+ "source_url": "https://bin.bnbstatic.com/static/chunks/56679.46a7195c.js",
+ "sha256": "9b7f8073ff13501279886af4fc929fd835e587146a51b03441851ed4b208209d",
+ "bytes": 312969
+ },
+ {
+ "file": "TradingView.99bc5074.js",
+ "source_url": "https://bin.bnbstatic.com/static/chunks/TradingView.99bc5074.js",
+ "sha256": "b6a2cddd017fb4517abb4ea458e19905c59f955a3f99efa18beb1e82f3938c1c",
+ "bytes": 52058
+ },
+ {
+ "file": "main.37a180b9.js",
+ "source_url": "https://bin.bnbstatic.com/static/main.37a180b9.js",
+ "sha256": "97bc05dcb6bf9e0803a00027e51e26a2d190c4c6d0a2b5151cfa44dfb846f451",
+ "bytes": 751358
+ }
+ ],
+ "fixture": {
+ "file": "host-persistence.js",
+ "sha256": "abacc9588761a054e63b234bf8d3a591b9f1cd195461848811f83c072c010020",
+ "bytes": 50082
+ },
+ "fragments": [
+ {
+ "label": "module:195",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 588121,
+ 588325
+ ],
+ "fixture_utf16_range": [
+ 206,
+ 410
+ ],
+ "sha256": "076356d5ceb471a777fe40a21efb1d97cb83ca9a90c282480adb583dc8a798ba"
+ },
+ {
+ "label": "module:1386",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 581333,
+ 581610
+ ],
+ "fixture_utf16_range": [
+ 412,
+ 689
+ ],
+ "sha256": "044ac771b6a4cc51ccc79cef867ae9174b1f48477686eb246d704bf93a52d651"
+ },
+ {
+ "label": "module:1877",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 602788,
+ 602891
+ ],
+ "fixture_utf16_range": [
+ 691,
+ 794
+ ],
+ "sha256": "7a1d9f60d4ee88867155bf1e3ffd70946e06ca89cb61d15fd681c1e51ec70043"
+ },
+ {
+ "label": "module:2306",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 601866,
+ 602113
+ ],
+ "fixture_utf16_range": [
+ 796,
+ 1043
+ ],
+ "sha256": "3e8dad8683615c659b227002344b3595c6b1a52aa9df81ca48d2e2960515286a"
+ },
+ {
+ "label": "module:2478",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 152945,
+ 153055
+ ],
+ "fixture_utf16_range": [
+ 1045,
+ 1155
+ ],
+ "sha256": "1b2e839dbc520323294a6f15c90ecfcd2c45fa9a13dd670cf375b77bfdb06e5b"
+ },
+ {
+ "label": "module:3283",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 605925,
+ 606144
+ ],
+ "fixture_utf16_range": [
+ 1157,
+ 1376
+ ],
+ "sha256": "e88a488c04e793705eb1107c28bc4036dde73bbc21134f437ce67538d768683c"
+ },
+ {
+ "label": "module:3316",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 603116,
+ 603311
+ ],
+ "fixture_utf16_range": [
+ 1378,
+ 1573
+ ],
+ "sha256": "39bc82d78923ca68ef8182399e8e511f825b647f10b8232c974e5c26907cf53b"
+ },
+ {
+ "label": "module:3334",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 587936,
+ 588120
+ ],
+ "fixture_utf16_range": [
+ 1575,
+ 1759
+ ],
+ "sha256": "ad54b48293376bdbfea2ec41c971f8e72cbcd34910b63e0462db9e028180de21"
+ },
+ {
+ "label": "module:3422",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 583928,
+ 584103
+ ],
+ "fixture_utf16_range": [
+ 1761,
+ 1936
+ ],
+ "sha256": "54698aaffe8e378dfd57928298979484f83244b6e3c7ec3184ef43a8a5bc63fe"
+ },
+ {
+ "label": "module:3766",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 602203,
+ 602274
+ ],
+ "fixture_utf16_range": [
+ 1938,
+ 2009
+ ],
+ "sha256": "6365268be7722795a85c6d18a02aadebfb99e771f38a0d0492edd19fbbea0bad"
+ },
+ {
+ "label": "module:5681",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 600889,
+ 600994
+ ],
+ "fixture_utf16_range": [
+ 2011,
+ 2116
+ ],
+ "sha256": "0fa6390d2a0bd82281474aafa21be303f447e6a4ff7334b9626d42d90a8ddc24"
+ },
+ {
+ "label": "module:7144",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 600416,
+ 600524
+ ],
+ "fixture_utf16_range": [
+ 2118,
+ 2226
+ ],
+ "sha256": "bc17621fe2ee7f1c85fb0ad66bcc2cb1b6ace99a18be6bf3094225944a2b23f3"
+ },
+ {
+ "label": "module:7452",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 600525,
+ 600607
+ ],
+ "fixture_utf16_range": [
+ 2228,
+ 2310
+ ],
+ "sha256": "6c0069e1e25ed659eabee671b7976def0900fec31c13946f19c82dfb36518c78"
+ },
+ {
+ "label": "module:7462",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 603557,
+ 603619
+ ],
+ "fixture_utf16_range": [
+ 2312,
+ 2374
+ ],
+ "sha256": "973d646d7a2f9c1fdd5410608000a554bd2b35f89232501d102c742548e4a7c9"
+ },
+ {
+ "label": "module:7980",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 51659,
+ 51716
+ ],
+ "fixture_utf16_range": [
+ 2376,
+ 2433
+ ],
+ "sha256": "285e2cd28d53b4c0af6ea15808f4b7dd9647724ac3ed4c42a8b73ab4f9d2f908"
+ },
+ {
+ "label": "module:8006",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 602892,
+ 602954
+ ],
+ "fixture_utf16_range": [
+ 2435,
+ 2497
+ ],
+ "sha256": "d9b0457c8f0d4daf6c6f5ca498bda8559defaec5ed25c78b52764696053f8f92"
+ },
+ {
+ "label": "module:8666",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 608024,
+ 608115
+ ],
+ "fixture_utf16_range": [
+ 2499,
+ 2590
+ ],
+ "sha256": "02898fd5d4ba2b5c890e0ce990d965dfbc127f6316f709154bb809afe0e52f4d"
+ },
+ {
+ "label": "module:8870",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 596653,
+ 596890
+ ],
+ "fixture_utf16_range": [
+ 2592,
+ 2829
+ ],
+ "sha256": "5e2f97c5451b90e8679da15b25dcd897c6d13bbcce0ff2826fac725c2be453a7"
+ },
+ {
+ "label": "module:10010",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 724875,
+ 725025
+ ],
+ "fixture_utf16_range": [
+ 2831,
+ 2981
+ ],
+ "sha256": "a125b4a241318d59e17a0ed05f5c16d600d5cf8395cbaabae8ab28e8c992ad10"
+ },
+ {
+ "label": "module:10358",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 583053,
+ 583470
+ ],
+ "fixture_utf16_range": [
+ 2983,
+ 3400
+ ],
+ "sha256": "43c97e26758d54132c7afd6343e46d51f5783121ff9a5bffb9eacd0df3210607"
+ },
+ {
+ "label": "module:11112",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 598187,
+ 598399
+ ],
+ "fixture_utf16_range": [
+ 3402,
+ 3614
+ ],
+ "sha256": "ecc5189072302807e35f5303102c58426681cc86db50ec5b8e7d3a7f99465598"
+ },
+ {
+ "label": "module:11129",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 583587,
+ 583687
+ ],
+ "fixture_utf16_range": [
+ 3616,
+ 3716
+ ],
+ "sha256": "b0b1a0bdd1c551f7c1e49c13e4186d5de62115a7ad2f7d9d415330ffe587a194"
+ },
+ {
+ "label": "module:12343",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 588870,
+ 589097
+ ],
+ "fixture_utf16_range": [
+ 3718,
+ 3945
+ ],
+ "sha256": "b7a9f772aa3003f472337e5347a26c0906387017d4aab2b6e4a6785ca8bb4000"
+ },
+ {
+ "label": "module:12393",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 600157,
+ 600220
+ ],
+ "fixture_utf16_range": [
+ 3947,
+ 4010
+ ],
+ "sha256": "2856af6a5b28b6722fd4ef2dde16d73d85a46470614218e0f5b690195b994fea"
+ },
+ {
+ "label": "module:12762",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 590317,
+ 590386
+ ],
+ "fixture_utf16_range": [
+ 4012,
+ 4081
+ ],
+ "sha256": "22f9a148b3db7d548577c772c572eecddfbfb2a3594983130e02090f8abf6f6c"
+ },
+ {
+ "label": "module:13964",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 600608,
+ 600747
+ ],
+ "fixture_utf16_range": [
+ 4083,
+ 4222
+ ],
+ "sha256": "ae950db8670c1d8c1f7146271a12dd924357bbede67488090348fbd44ef64b00"
+ },
+ {
+ "label": "module:15630",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 154859,
+ 155318
+ ],
+ "fixture_utf16_range": [
+ 4224,
+ 4683
+ ],
+ "sha256": "cca361c7312b3cdcf5ed0a4fb2181e27d05efb6e63e4af5df9ce5a1d18bc3d99"
+ },
+ {
+ "label": "module:17099",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 591798,
+ 592062
+ ],
+ "fixture_utf16_range": [
+ 4685,
+ 4949
+ ],
+ "sha256": "ccd88beb86ffc5ba7c62ec61095c9736b439586a67cacff65d99708619bde6e9"
+ },
+ {
+ "label": "module:18069",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 12802,
+ 12930
+ ],
+ "fixture_utf16_range": [
+ 4951,
+ 5079
+ ],
+ "sha256": "679a51e58ebe23f63c53d2318e26068b2d2c3b98a551a578e3d014507702fb51"
+ },
+ {
+ "label": "module:19270",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 582651,
+ 582719
+ ],
+ "fixture_utf16_range": [
+ 5081,
+ 5149
+ ],
+ "sha256": "b7b3525dd7e04e3cb91053cad71c1abb6d638d1329175498f953e1163d73aa4e"
+ },
+ {
+ "label": "module:19770",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 581611,
+ 581675
+ ],
+ "fixture_utf16_range": [
+ 5151,
+ 5215
+ ],
+ "sha256": "62b118f55bc2fe8412e84f29ecced21b98b77280ca833835e84624e8825ca6a7"
+ },
+ {
+ "label": "module:19953",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 160325,
+ 160430
+ ],
+ "fixture_utf16_range": [
+ 5217,
+ 5322
+ ],
+ "sha256": "863b9a8662f226e7e712eb70dbc6b55dc7b8c03a0913fb66f22cb6d0a6794fdb"
+ },
+ {
+ "label": "module:20155",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 597653,
+ 597704
+ ],
+ "fixture_utf16_range": [
+ 5324,
+ 5375
+ ],
+ "sha256": "b0a1a6e840564e933218960780b8e1f46cb26618f0b69c6d7a1b7ae753d62aa3"
+ },
+ {
+ "label": "module:20714",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 598852,
+ 598993
+ ],
+ "fixture_utf16_range": [
+ 5377,
+ 5518
+ ],
+ "sha256": "f88aba7ba290fdf970df49f973430b4a62c04d737a8c61d08c0576fe95df88dc"
+ },
+ {
+ "label": "module:21779",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 603397,
+ 603492
+ ],
+ "fixture_utf16_range": [
+ 5520,
+ 5615
+ ],
+ "sha256": "4fd4f0043856c796af4a9a806420d02d116ee09561fadb00bd4d0ed2172104f4"
+ },
+ {
+ "label": "module:22053",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 605880,
+ 605924
+ ],
+ "fixture_utf16_range": [
+ 5617,
+ 5661
+ ],
+ "sha256": "09ace35dce7702409403ecabf4d55d59b2aa4420bfb9f75880b630682e17f7ae"
+ },
+ {
+ "label": "module:23212",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 582090,
+ 582293
+ ],
+ "fixture_utf16_range": [
+ 5663,
+ 5866
+ ],
+ "sha256": "5560039aabf6e603485b9a45561a0064980c85c0c2e4966dc1d5a848768eaf35"
+ },
+ {
+ "label": "module:23305",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 597989,
+ 598081
+ ],
+ "fixture_utf16_range": [
+ 5868,
+ 5960
+ ],
+ "sha256": "76b7d8b3dcea102a57e2442cf95c607c181241a1e29829cd7d81562e0ac432be"
+ },
+ {
+ "label": "module:23904",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 601392,
+ 601506
+ ],
+ "fixture_utf16_range": [
+ 5962,
+ 6076
+ ],
+ "sha256": "0094d51330b28ac60bf2a8bdabbcb0cf0a7e6852c04613447936cf26e171d958"
+ },
+ {
+ "label": "module:23945",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 590476,
+ 590578
+ ],
+ "fixture_utf16_range": [
+ 6078,
+ 6180
+ ],
+ "sha256": "17db0c12313a7eb39d2e29f6c00586620723ec08eaed5ee1d4fdc7311b833fc2"
+ },
+ {
+ "label": "module:24367",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 596336,
+ 596476
+ ],
+ "fixture_utf16_range": [
+ 6182,
+ 6322
+ ],
+ "sha256": "e2499a5dc51b5669c5bd82d31f5b5e29bb8688c681c0414333ef756dea3d34aa"
+ },
+ {
+ "label": "module:24733",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 13018,
+ 13104
+ ],
+ "fixture_utf16_range": [
+ 6324,
+ 6410
+ ],
+ "sha256": "43431e20283e5305fd970706759a00edcc3f8b91f3bbd761198c9bc4a6031080"
+ },
+ {
+ "label": "module:24810",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 583800,
+ 583927
+ ],
+ "fixture_utf16_range": [
+ 6412,
+ 6539
+ ],
+ "sha256": "25eb9dd97577c853a4f82dcd567b942bbc3b1d0f507381db5016651232c7de55"
+ },
+ {
+ "label": "module:25098",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 581052,
+ 581332
+ ],
+ "fixture_utf16_range": [
+ 6541,
+ 6821
+ ],
+ "sha256": "c82e176a400a0cd8293a310171392887096d7eaa6e33f5fd2543cb2114c1ad80"
+ },
+ {
+ "label": "module:25276",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 598400,
+ 598547
+ ],
+ "fixture_utf16_range": [
+ 6823,
+ 6970
+ ],
+ "sha256": "35c96ee93562c76dca5c8c5c7c4fbe2bff5fe7f11f20e8ef8c9c2ffca31162ee"
+ },
+ {
+ "label": "module:25288",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 608213,
+ 608314
+ ],
+ "fixture_utf16_range": [
+ 6972,
+ 7073
+ ],
+ "sha256": "d1852dee788b486e5e7bc03154efe7b28799b90ce1e4897722c8957da577b9ec"
+ },
+ {
+ "label": "module:26137",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 590838,
+ 590925
+ ],
+ "fixture_utf16_range": [
+ 7075,
+ 7162
+ ],
+ "sha256": "a13e11fb6aac6b87eaee0a5124954da4c12b4aac006abd740e66b5e2d36a4969"
+ },
+ {
+ "label": "module:26885",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 589365,
+ 589911
+ ],
+ "fixture_utf16_range": [
+ 7164,
+ 7710
+ ],
+ "sha256": "3800f3c951c32987b6c859f67360fd3d3db923c9d86b591034cf7c6050969437"
+ },
+ {
+ "label": "module:27276",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 605787,
+ 605879
+ ],
+ "fixture_utf16_range": [
+ 7712,
+ 7804
+ ],
+ "sha256": "7eb44499ed19ee5204938b1e2da230197ebc37e37ec48b45210bef64fec6582f"
+ },
+ {
+ "label": "module:28486",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 597109,
+ 597652
+ ],
+ "fixture_utf16_range": [
+ 7806,
+ 8349
+ ],
+ "sha256": "7e12564ab2ddc53b343264b0169bce39dc4d39cfa6b51ac6536cd68f3f43f7fb"
+ },
+ {
+ "label": "module:29005",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 602114,
+ 602202
+ ],
+ "fixture_utf16_range": [
+ 8351,
+ 8439
+ ],
+ "sha256": "49e58fbd250cca015d07b74faeea15bb4cbf108dcff9a5277b4db3231b704493"
+ },
+ {
+ "label": "module:30125",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 10532,
+ 10621
+ ],
+ "fixture_utf16_range": [
+ 8441,
+ 8530
+ ],
+ "sha256": "5267718742dafe4ef8897ad7aefe55afa4fb5835e5f04e372e48387a54afe4c0"
+ },
+ {
+ "label": "module:31182",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 595335,
+ 596028
+ ],
+ "fixture_utf16_range": [
+ 8532,
+ 9225
+ ],
+ "sha256": "57ecfb09e70d275a697dbd72dbd030f2ecceaf7f1428a81f8a83e67f0f41d47a"
+ },
+ {
+ "label": "module:31623",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 582595,
+ 582650
+ ],
+ "fixture_utf16_range": [
+ 9227,
+ 9282
+ ],
+ "sha256": "d23626b921c3851b077fe782f133c2442e37e72490b4924b41f2d38e723a0eea"
+ },
+ {
+ "label": "module:33103",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 580982,
+ 581051
+ ],
+ "fixture_utf16_range": [
+ 9284,
+ 9353
+ ],
+ "sha256": "06e2420c76d1d48767dd3791966ba470ecf4ede3e30f39d65c62f93489d916b2"
+ },
+ {
+ "label": "module:33934",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 593890,
+ 594435
+ ],
+ "fixture_utf16_range": [
+ 9355,
+ 9900
+ ],
+ "sha256": "9e333b6302905e8d364b659a17f151fe28938ad6f0e334179bdcf10957eb33e0"
+ },
+ {
+ "label": "module:34162",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 603493,
+ 603556
+ ],
+ "fixture_utf16_range": [
+ 9902,
+ 9965
+ ],
+ "sha256": "5e3aab576a027f8149de89f5aa55c8f012a04073488b22a14d14ea0663035801"
+ },
+ {
+ "label": "module:34772",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 609193,
+ 609236
+ ],
+ "fixture_utf16_range": [
+ 9967,
+ 10010
+ ],
+ "sha256": "74a4a3a55a577ea63b0298f4cc68af56e523d4132bea354c8d5ae33123dc82ed"
+ },
+ {
+ "label": "module:34789",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 14806,
+ 14888
+ ],
+ "fixture_utf16_range": [
+ 10012,
+ 10094
+ ],
+ "sha256": "32c44883fc73a5acba8b7fe7fe0aec70983715e598048ea8d52d995c3c8ba9aa"
+ },
+ {
+ "label": "module:35636",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 584819,
+ 584865
+ ],
+ "fixture_utf16_range": [
+ 10096,
+ 10142
+ ],
+ "sha256": "8aa2bc7f50436919d1cd13fe4476dd408a8b99a14e99511b3155dd0759bceb66"
+ },
+ {
+ "label": "module:36208",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 156551,
+ 156679
+ ],
+ "fixture_utf16_range": [
+ 10144,
+ 10272
+ ],
+ "sha256": "9179a6b984728afb56607d5e91d1be3fc57e795b37a9e70cbd6a81e84b862ad9"
+ },
+ {
+ "label": "module:36465",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 583688,
+ 583799
+ ],
+ "fixture_utf16_range": [
+ 10274,
+ 10385
+ ],
+ "sha256": "649a6dae9650211c1b5fbb9bc74b984ad69c1591b5ad532898dfd5922c199976"
+ },
+ {
+ "label": "module:36526",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 588699,
+ 588869
+ ],
+ "fixture_utf16_range": [
+ 10387,
+ 10557
+ ],
+ "sha256": "6e802bcb7fdadb8f3572a196bc16d7d888138b9b736aef290a0e2f1de5b8cbe7"
+ },
+ {
+ "label": "module:36529",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 606187,
+ 606287
+ ],
+ "fixture_utf16_range": [
+ 10559,
+ 10659
+ ],
+ "sha256": "96d9de5d6c4e7384c4a2428b796588c1efa7a3218cc8efab21c2d6fb2e9bee56"
+ },
+ {
+ "label": "module:37250",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 152550,
+ 152669
+ ],
+ "fixture_utf16_range": [
+ 10661,
+ 10780
+ ],
+ "sha256": "bec925fb9c03e2835a9061d2335bea548bef7f36cf2565fa1650b47f4043f16c"
+ },
+ {
+ "label": "module:37681",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 14634,
+ 14719
+ ],
+ "fixture_utf16_range": [
+ 10782,
+ 10867
+ ],
+ "sha256": "be85b0228c0df4f4ae9298bbc02a4279b9952885ab369af2bed23a3eed59bd61"
+ },
+ {
+ "label": "module:37930",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 10442,
+ 10531
+ ],
+ "fixture_utf16_range": [
+ 10869,
+ 10958
+ ],
+ "sha256": "2505dc60f9f3c3ba44c92d0eedab1cd5504fef3dfac125a919f3546dc6976d0c"
+ },
+ {
+ "label": "module:38330",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 586374,
+ 586764
+ ],
+ "fixture_utf16_range": [
+ 10960,
+ 11350
+ ],
+ "sha256": "a4c9afe5b6627bd34bb1a67c5bd2683d7460fabbe94f74dc1051e28b4b7a9fa7"
+ },
+ {
+ "label": "module:38546",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 601768,
+ 601865
+ ],
+ "fixture_utf16_range": [
+ 11352,
+ 11449
+ ],
+ "sha256": "b8f462f3e4b7ffa81cb1211712cfe5d89e0c3f0be6da881e6385e5169af0c12f"
+ },
+ {
+ "label": "module:39032",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 592822,
+ 593034
+ ],
+ "fixture_utf16_range": [
+ 11451,
+ 11663
+ ],
+ "sha256": "43d3875f94841a6f891b5dc7269a4f64bd6ebeceed54fea96b8140ea5ca5c85f"
+ },
+ {
+ "label": "module:39361",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 598082,
+ 598186
+ ],
+ "fixture_utf16_range": [
+ 11665,
+ 11769
+ ],
+ "sha256": "fa861a78279bfb59c00bff33d6d7d713d5581b3f5e2ec5d31b17cb5d3550a73d"
+ },
+ {
+ "label": "module:39747",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 584362,
+ 584559
+ ],
+ "fixture_utf16_range": [
+ 11771,
+ 11968
+ ],
+ "sha256": "fd9087e691aa5e7ca29fd2513d82aaabb1bc731ba0ca1333d7fe4b54bb927700"
+ },
+ {
+ "label": "module:40091",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 589098,
+ 589364
+ ],
+ "fixture_utf16_range": [
+ 11970,
+ 12236
+ ],
+ "sha256": "f4ddedfc03f88f1b5b506ce02eef194ae887bf9b1b36222092f2c0b730d9fddc"
+ },
+ {
+ "label": "module:41580",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 607382,
+ 607477
+ ],
+ "fixture_utf16_range": [
+ 12238,
+ 12333
+ ],
+ "sha256": "7a0f418052e24cf5980a502dbe6610625014a82a9a9d6ac0ab1c58faefbb2791"
+ },
+ {
+ "label": "module:41950",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 593035,
+ 593101
+ ],
+ "fixture_utf16_range": [
+ 12335,
+ 12401
+ ],
+ "sha256": "003a365d6f91f05476d6487de7a433cbfb89b47c8e6fb184f576e0437a43a5d1"
+ },
+ {
+ "label": "module:42288",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 730272,
+ 731546
+ ],
+ "fixture_utf16_range": [
+ 12403,
+ 13677
+ ],
+ "sha256": "246f01a4d0f336d062be0988ab5fac44bc1ef5010a2b4b838029ae865ad9bea8"
+ },
+ {
+ "label": "module:43282",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 608825,
+ 608937
+ ],
+ "fixture_utf16_range": [
+ 13679,
+ 13791
+ ],
+ "sha256": "676f59e8231caa9da455f32829d6c0f945baeb8ae99bc33bf46b2e43c8d6d049"
+ },
+ {
+ "label": "module:43316",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 151666,
+ 151777
+ ],
+ "fixture_utf16_range": [
+ 13793,
+ 13904
+ ],
+ "sha256": "789962ff39c302092f11d8f43ffa4d9ea966269dd1e3d2b20d1647efcfe6e18f"
+ },
+ {
+ "label": "module:43636",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 585411,
+ 585479
+ ],
+ "fixture_utf16_range": [
+ 13906,
+ 13974
+ ],
+ "sha256": "ca098e0e2a61bbebab107fd291dc4d152c2d4a1a2857016f0bd182999fc18b68"
+ },
+ {
+ "label": "module:44700",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 596206,
+ 596335
+ ],
+ "fixture_utf16_range": [
+ 13976,
+ 14105
+ ],
+ "sha256": "ad2582b788d5e0c2c9ac9a77344476516337db4e0374593d38c3016ff3b928fd"
+ },
+ {
+ "label": "module:45770",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 593399,
+ 593603
+ ],
+ "fixture_utf16_range": [
+ 14107,
+ 14311
+ ],
+ "sha256": "4f15d5e29cc67f1720b173dd27fb86fa6d0e4abc40baad6ed0e482bbbcfb10cf"
+ },
+ {
+ "label": "module:46108",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 14555,
+ 14633
+ ],
+ "fixture_utf16_range": [
+ 14313,
+ 14391
+ ],
+ "sha256": "169b7da271880c14187b5639a0b6b5d3c49c3987a93ba9cb5603a470e26e4604"
+ },
+ {
+ "label": "module:46123",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 605678,
+ 605786
+ ],
+ "fixture_utf16_range": [
+ 14393,
+ 14501
+ ],
+ "sha256": "b8d7c80369fad193a1ba03470405fe286ac7658c09a3b10ab8057b4a0a0c198d"
+ },
+ {
+ "label": "module:46155",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 159145,
+ 159287
+ ],
+ "fixture_utf16_range": [
+ 14503,
+ 14645
+ ],
+ "sha256": "04173860436f6b66674ec04cbe264cd1525017826e0eee271d85de2a65aa4e1c"
+ },
+ {
+ "label": "module:46285",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 605613,
+ 605677
+ ],
+ "fixture_utf16_range": [
+ 14647,
+ 14711
+ ],
+ "sha256": "33de54c2cb12a8fcdb4f47a9f97dced8414171fc37aa16bfc6507023738b7fa9"
+ },
+ {
+ "label": "module:47170",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 584866,
+ 584952
+ ],
+ "fixture_utf16_range": [
+ 14713,
+ 14799
+ ],
+ "sha256": "e121a8fd18aa8f287a4ad409e631375ac63ffc2723557795016081c3994a9e03"
+ },
+ {
+ "label": "module:47403",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 590579,
+ 590740
+ ],
+ "fixture_utf16_range": [
+ 14801,
+ 14962
+ ],
+ "sha256": "17bb980be3235b618fbb4db7b6fb6e2d98e978ee30226850481fe5403933c24e"
+ },
+ {
+ "label": "module:48244",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 585106,
+ 585210
+ ],
+ "fixture_utf16_range": [
+ 14964,
+ 15068
+ ],
+ "sha256": "74e0732678e94e0c9d501cac1e1596555f412d23eac2d2644dbafc5e9ef1c231"
+ },
+ {
+ "label": "module:48360",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 607549,
+ 607896
+ ],
+ "fixture_utf16_range": [
+ 15070,
+ 15417
+ ],
+ "sha256": "7372b285fa22ff4014492363a40a4384b02ad4573b1a4c812a5dd9d9af908fac"
+ },
+ {
+ "label": "module:49253",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 725282,
+ 725635
+ ],
+ "fixture_utf16_range": [
+ 15419,
+ 15772
+ ],
+ "sha256": "fd76469b1ff5b4b7efce3c6285c37484ac20f5d62fc56f7981ca89b08e1b7459"
+ },
+ {
+ "label": "module:49460",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 591298,
+ 591365
+ ],
+ "fixture_utf16_range": [
+ 15774,
+ 15841
+ ],
+ "sha256": "b8fa1b3c7d9927f70ed9ffa2691983387b3d4f6cd0f0a6e5fd7dcaafb79cec1a"
+ },
+ {
+ "label": "module:49753",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 600748,
+ 600888
+ ],
+ "fixture_utf16_range": [
+ 15843,
+ 15983
+ ],
+ "sha256": "27f58c02fdbc86dfb43e698b56e7938f3360d9d6b305f328a97ba926453f4bd7"
+ },
+ {
+ "label": "module:49828",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 602955,
+ 603057
+ ],
+ "fixture_utf16_range": [
+ 15985,
+ 16087
+ ],
+ "sha256": "73953bbffb627e2fc92aacf145b810220cc770b48e97929544f7fe586815b2cc"
+ },
+ {
+ "label": "module:50424",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 13728,
+ 14554
+ ],
+ "fixture_utf16_range": [
+ 16089,
+ 16915
+ ],
+ "sha256": "f5885015cb3d0ca09cda448e53274aca7c07b9e7be8e5bdd1d05ad9b17e62a36"
+ },
+ {
+ "label": "module:51187",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 607897,
+ 608023
+ ],
+ "fixture_utf16_range": [
+ 16917,
+ 17043
+ ],
+ "sha256": "2947454024a20506c8f80b490fc48ba46fcf0ed2cf63ce14b22a48ac45977d97"
+ },
+ {
+ "label": "module:51340",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 582294,
+ 582542
+ ],
+ "fixture_utf16_range": [
+ 17045,
+ 17293
+ ],
+ "sha256": "54ec3daa5430d76865ae238a4a3dfce32d0b25b773648434452d9e40176cc472"
+ },
+ {
+ "label": "module:52291",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 590926,
+ 591191
+ ],
+ "fixture_utf16_range": [
+ 17295,
+ 17560
+ ],
+ "sha256": "bd89faf92252986369abbe8a469f435d5e5227f345e8e51fa1797a55fa098cc1"
+ },
+ {
+ "label": "module:53142",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 606145,
+ 606186
+ ],
+ "fixture_utf16_range": [
+ 17562,
+ 17603
+ ],
+ "sha256": "95eb6beb510a123da017b6925f8cd0449c368670a1f39d305023cbc0d815da3b"
+ },
+ {
+ "label": "module:54732",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 601074,
+ 601152
+ ],
+ "fixture_utf16_range": [
+ 17605,
+ 17683
+ ],
+ "sha256": "3c6d129039fc047ed6ecf8664953199a664a321d73d491ef8ba253b2fc3a9750"
+ },
+ {
+ "label": "module:56027",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 585480,
+ 585589
+ ],
+ "fixture_utf16_range": [
+ 17685,
+ 17794
+ ],
+ "sha256": "176d5d500963e2961f23b6074bca044f9075a73c31e0a37b19e604d5b2c8a8ec"
+ },
+ {
+ "label": "module:57073",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 584219,
+ 584361
+ ],
+ "fixture_utf16_range": [
+ 17796,
+ 17938
+ ],
+ "sha256": "b265b6f5dfe11ae6fde73ab8fc2d7bf89b0ac5557ebc925eba5421a8eaba210e"
+ },
+ {
+ "label": "module:57200",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 588326,
+ 588548
+ ],
+ "fixture_utf16_range": [
+ 17940,
+ 18162
+ ],
+ "sha256": "17b1d87c35dc3194ff68148fac027fc06f889af08190ec5477915685749b6376"
+ },
+ {
+ "label": "module:57452",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 598548,
+ 598716
+ ],
+ "fixture_utf16_range": [
+ 18164,
+ 18332
+ ],
+ "sha256": "635d4be50d563a73ad7bd2055fe63b23a7efcb7c9473dde4d0cd45ad5c732b33"
+ },
+ {
+ "label": "module:57923",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 584953,
+ 585105
+ ],
+ "fixture_utf16_range": [
+ 18334,
+ 18486
+ ],
+ "sha256": "23137ccd8629224e67761e1a43d9cf85eecf6dba47bc68c2abb80c7168676e1f"
+ },
+ {
+ "label": "module:58898",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 591673,
+ 591797
+ ],
+ "fixture_utf16_range": [
+ 18488,
+ 18612
+ ],
+ "sha256": "d35825328e8e63626bd819ed59c0f4b51c2b09ed5bbc9858ba5532a2bb70610d"
+ },
+ {
+ "label": "module:59068",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 601153,
+ 601282
+ ],
+ "fixture_utf16_range": [
+ 18614,
+ 18743
+ ],
+ "sha256": "42cdd5284ef2804921beac73147401586066a9eff4e401515d1e2746504ccb72"
+ },
+ {
+ "label": "module:59756",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 151562,
+ 151665
+ ],
+ "fixture_utf16_range": [
+ 18745,
+ 18848
+ ],
+ "sha256": "46424910254d5e60a0f445187f549c6e0b098cc24582b058045cf2cc91051c70"
+ },
+ {
+ "label": "module:61875",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 731700,
+ 732062
+ ],
+ "fixture_utf16_range": [
+ 18850,
+ 19212
+ ],
+ "sha256": "6ad6943dfa4be8af902e88cf95a631115957bc1c273a317ef2ef533f3e3a1ef9"
+ },
+ {
+ "label": "module:62049",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 600221,
+ 600415
+ ],
+ "fixture_utf16_range": [
+ 19214,
+ 19408
+ ],
+ "sha256": "e5a8d86214c5340c1ad7539643a2082aef4f7cbef7c1f9af72962fdd7d505ce2"
+ },
+ {
+ "label": "module:64066",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 604318,
+ 604464
+ ],
+ "fixture_utf16_range": [
+ 19410,
+ 19556
+ ],
+ "sha256": "908917e3e7b6668eeb890c321d849dafbc854c08f350594f56c4b4ea7c038898"
+ },
+ {
+ "label": "module:64103",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 603312,
+ 603396
+ ],
+ "fixture_utf16_range": [
+ 19558,
+ 19642
+ ],
+ "sha256": "cd21c2ea1fcf1ebb8b4df1742c91b1fba44670b3454275f2e90d453be05c577a"
+ },
+ {
+ "label": "module:64207",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 609894,
+ 609976
+ ],
+ "fixture_utf16_range": [
+ 19644,
+ 19726
+ ],
+ "sha256": "772e31a097ab554bb19d6ef789c67168357c9bdf75a4cb210a32406c8c463625"
+ },
+ {
+ "label": "module:64512",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 582025,
+ 582089
+ ],
+ "fixture_utf16_range": [
+ 19728,
+ 19792
+ ],
+ "sha256": "2e25669599e234cc00b72ac92952ac196f56720662e2f76f6ce49a6512bc51d1"
+ },
+ {
+ "label": "module:64759",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 599774,
+ 599947
+ ],
+ "fixture_utf16_range": [
+ 19794,
+ 19967
+ ],
+ "sha256": "dd1c0e19dce4179e4cbf33ea4d01d92314879d311879180b2ed997519fcfd8b3"
+ },
+ {
+ "label": "module:65111",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 583471,
+ 583586
+ ],
+ "fixture_utf16_range": [
+ 19969,
+ 20084
+ ],
+ "sha256": "428b1db64df12ccb8a7186a5974937561307238c6f20d6c56e033fdff715669e"
+ },
+ {
+ "label": "module:65187",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 599365,
+ 599638
+ ],
+ "fixture_utf16_range": [
+ 20086,
+ 20359
+ ],
+ "sha256": "e7069f001ffcbdfd163b88639e7a427dfa9a14b5da6d8e986a10b4bd27379b04"
+ },
+ {
+ "label": "module:65387",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 607277,
+ 607381
+ ],
+ "fixture_utf16_range": [
+ 20361,
+ 20465
+ ],
+ "sha256": "99cb92cdf8bc751024275ebde5c15298df013c69f84823ac0cf2dbe6238163bc"
+ },
+ {
+ "label": "module:65406",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 606288,
+ 606371
+ ],
+ "fixture_utf16_range": [
+ 20467,
+ 20550
+ ],
+ "sha256": "1759deaef08ef51d497f2e3b25701fefafee2c0d1bc94798e111be3d4a65475e"
+ },
+ {
+ "label": "module:65568",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 591496,
+ 591549
+ ],
+ "fixture_utf16_range": [
+ 20552,
+ 20605
+ ],
+ "sha256": "1caedce163f61cddca5f80d1584618085a49f4a8e93e2614402a75f15c650ba0"
+ },
+ {
+ "label": "module:65650",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 582543,
+ 582594
+ ],
+ "fixture_utf16_range": [
+ 20607,
+ 20658
+ ],
+ "sha256": "a14be5da5f66bccfc0e2d87991848cb78958e7fd3a7389134e2540abb71a6320"
+ },
+ {
+ "label": "module:66040",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 604179,
+ 604317
+ ],
+ "fixture_utf16_range": [
+ 20660,
+ 20798
+ ],
+ "sha256": "6e91ef6afd3020de5f99249609ba71e42ecb95b82c63096b547054b26649ee86"
+ },
+ {
+ "label": "module:67267",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 600082,
+ 600156
+ ],
+ "fixture_utf16_range": [
+ 20800,
+ 20874
+ ],
+ "sha256": "e4dd6681dd4219a21630e50ac31912643b072ffde0efd07a281080694202ab91"
+ },
+ {
+ "label": "module:68250",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 581676,
+ 581955
+ ],
+ "fixture_utf16_range": [
+ 20876,
+ 21155
+ ],
+ "sha256": "18e4b97e9fadb93c8a1bc97cd3bb8ac990fdc73a9cc1fb4080e11d34e27c4fba"
+ },
+ {
+ "label": "module:69011",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 608315,
+ 608666
+ ],
+ "fixture_utf16_range": [
+ 21157,
+ 21508
+ ],
+ "sha256": "3c953c91d34722c72bfa35afbbaa39d9ef483bf9369b44a982a882634896d2bc"
+ },
+ {
+ "label": "module:69334",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 10622,
+ 12327
+ ],
+ "fixture_utf16_range": [
+ 21510,
+ 23215
+ ],
+ "sha256": "f20e3b0f0d1ab76fb7b8990542d554e8400a38932f5048887d802e0335968472"
+ },
+ {
+ "label": "module:69454",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 153597,
+ 153645
+ ],
+ "fixture_utf16_range": [
+ 23217,
+ 23265
+ ],
+ "sha256": "d360bddb8b451e324d33597e6746c625a82df2e1657563f3bd0d05eee3cf93c4"
+ },
+ {
+ "label": "module:69632",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 598994,
+ 599166
+ ],
+ "fixture_utf16_range": [
+ 23267,
+ 23439
+ ],
+ "sha256": "e71daee0073324f300e041af2a9162fa0dee0ccc6b2688cccd1c4bdd59713a1a"
+ },
+ {
+ "label": "module:70674",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 587160,
+ 587935
+ ],
+ "fixture_utf16_range": [
+ 23441,
+ 24216
+ ],
+ "sha256": "4a741e61c15995b89c7e98b89131d698e977e449f7d943268323a403dd589db3"
+ },
+ {
+ "label": "module:72195",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 13105,
+ 13264
+ ],
+ "fixture_utf16_range": [
+ 24218,
+ 24377
+ ],
+ "sha256": "39dbda9cac00bd3e17ec37b831cf6d70d002442323c04bb408afc85f1b9bb330"
+ },
+ {
+ "label": "module:72532",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 593765,
+ 593889
+ ],
+ "fixture_utf16_range": [
+ 24379,
+ 24503
+ ],
+ "sha256": "9cdcf4ad679ae2c3c8ae33688ebeafccd634adc622d0cdbc83bbff67c3988596"
+ },
+ {
+ "label": "module:72834",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 573724,
+ 577583
+ ],
+ "fixture_utf16_range": [
+ 24505,
+ 28364
+ ],
+ "sha256": "3e1628b3ab6c18c009a39d2230334d59e326c16ec94900cc8986144d8078db9c"
+ },
+ {
+ "label": "module:73788",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 10111,
+ 10228
+ ],
+ "fixture_utf16_range": [
+ 28366,
+ 28483
+ ],
+ "sha256": "6ef54091bd86cd194fec09cdb8b7457c2fa7c382287d01f6ad7203d4853f020b"
+ },
+ {
+ "label": "module:74535",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 599167,
+ 599364
+ ],
+ "fixture_utf16_range": [
+ 28485,
+ 28682
+ ],
+ "sha256": "87f0ef7784d3023fc01302c8cb1160d5450567b70cc691c9286c93e9959a443a"
+ },
+ {
+ "label": "module:74784",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 596579,
+ 596652
+ ],
+ "fixture_utf16_range": [
+ 28684,
+ 28757
+ ],
+ "sha256": "98f21d559b8293f2a14a7a3912447f9e628810db4ef426add2af31d83c1c3497"
+ },
+ {
+ "label": "module:74967",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 596029,
+ 596112
+ ],
+ "fixture_utf16_range": [
+ 28759,
+ 28842
+ ],
+ "sha256": "06c4085571ee1829b8f9d6aa6cae3293200845465517ef4f1c7d3556c7d8d2be"
+ },
+ {
+ "label": "module:75234",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 602275,
+ 602539
+ ],
+ "fixture_utf16_range": [
+ 28844,
+ 29108
+ ],
+ "sha256": "601502a39a577ee921f430c11e53a8c4f42263f54e000af8a60318d00465a86f"
+ },
+ {
+ "label": "module:75257",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 15010,
+ 15172
+ ],
+ "fixture_utf16_range": [
+ 29110,
+ 29272
+ ],
+ "sha256": "547202c622c0678fe1996fe26a59112873e001ae2faf21cb096e2f53362010b0"
+ },
+ {
+ "label": "module:75853",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 606372,
+ 606549
+ ],
+ "fixture_utf16_range": [
+ 29274,
+ 29451
+ ],
+ "sha256": "8b0838231865ee1272a1851fa5119277507ff43ffd010268dce8da2aa8d9b065"
+ },
+ {
+ "label": "module:75857",
+ "source_file": "16873.442d9b04.js",
+ "source_utf16_range": [
+ 15615,
+ 15713
+ ],
+ "fixture_utf16_range": [
+ 29453,
+ 29551
+ ],
+ "sha256": "a261bcfa630fd200eea8d0d40cbdf2e58d64a68a68d44553ab8ce322603face0"
+ },
+ {
+ "label": "module:75964",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 598717,
+ 598851
+ ],
+ "fixture_utf16_range": [
+ 29553,
+ 29687
+ ],
+ "sha256": "701fc82477ca6dd6551953904242c879f9f7aa6e904b8d7642e23250e271245c"
+ },
+ {
+ "label": "module:76853",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 601507,
+ 601641
+ ],
+ "fixture_utf16_range": [
+ 29689,
+ 29823
+ ],
+ "sha256": "32bb4867042c0f3b7c11618f19f156407c3beb49698e989e59a97ad4f2bb5155"
+ },
+ {
+ "label": "module:77135",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 729450,
+ 729907
+ ],
+ "fixture_utf16_range": [
+ 29825,
+ 30282
+ ],
+ "sha256": "43d341be2b691de3e87c9b3cbf50553f3623bf97e18dd6d408feed0d3e769012"
+ },
+ {
+ "label": "module:77848",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 593102,
+ 593398
+ ],
+ "fixture_utf16_range": [
+ 30284,
+ 30580
+ ],
+ "sha256": "f0a411738d6079277fb997895094722b8afaf005697fa5fe8829fa18d4d8580a"
+ },
+ {
+ "label": "module:77979",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 596891,
+ 597108
+ ],
+ "fixture_utf16_range": [
+ 30582,
+ 30799
+ ],
+ "sha256": "1d5034a4d653356fd0b00057aa99c918fef06be13e5a3e2b76c0e3919ef98381"
+ },
+ {
+ "label": "module:78340",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 12676,
+ 12801
+ ],
+ "fixture_utf16_range": [
+ 30801,
+ 30926
+ ],
+ "sha256": "b4b31342f4ac7948890018433d25e0cd6b499cd56fe4676c49fd1713cd8cb6b7"
+ },
+ {
+ "label": "module:78763",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 602673,
+ 602787
+ ],
+ "fixture_utf16_range": [
+ 30928,
+ 31042
+ ],
+ "sha256": "2d935b02a8d78252f0f1016a3a23598d0696f9c26cdb6c9d118b77fc4fb1d7f6"
+ },
+ {
+ "label": "module:78942",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 602540,
+ 602672
+ ],
+ "fixture_utf16_range": [
+ 31044,
+ 31176
+ ],
+ "sha256": "b7c35e89c6a3f7d2441f208e37bfe23eaf047eb5bae09794a7ee7d93a48b6103"
+ },
+ {
+ "label": "module:79067",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 599639,
+ 599773
+ ],
+ "fixture_utf16_range": [
+ 31178,
+ 31312
+ ],
+ "sha256": "6ae9b7a65dff2a2e0da5c1d04311fe66170bc3793c319bf7b81f27319e916c67"
+ },
+ {
+ "label": "module:79071",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 159958,
+ 159993
+ ],
+ "fixture_utf16_range": [
+ 31314,
+ 31349
+ ],
+ "sha256": "3383fcf7b47c1c64ddba300230a7d294c0091fbc1d26cbd5c59e7049c43c8978"
+ },
+ {
+ "label": "module:79847",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 582923,
+ 583052
+ ],
+ "fixture_utf16_range": [
+ 31351,
+ 31480
+ ],
+ "sha256": "415d53d628ff287881c2d0d89f81a5ed1c0636b394308321d7d411ff1e0ae94a"
+ },
+ {
+ "label": "module:79968",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 10229,
+ 10335
+ ],
+ "fixture_utf16_range": [
+ 31482,
+ 31588
+ ],
+ "sha256": "8cdf78eb551dca5ed6baa55039163bdf4b0473f5af611e418d6d854fc4995706"
+ },
+ {
+ "label": "module:80088",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 600995,
+ 601073
+ ],
+ "fixture_utf16_range": [
+ 31590,
+ 31668
+ ],
+ "sha256": "87d74c6c8d1270ad9f16a5f22220f0b5111b71103ee800fce69acdb01bc6cd76"
+ },
+ {
+ "label": "module:80393",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 596113,
+ 596205
+ ],
+ "fixture_utf16_range": [
+ 31670,
+ 31762
+ ],
+ "sha256": "4561e9ccd10aa400d71cccdfae7d9b8578a6c05a07bd5dbd011cd29c9ea9ffac"
+ },
+ {
+ "label": "module:80547",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 607478,
+ 607548
+ ],
+ "fixture_utf16_range": [
+ 31764,
+ 31834
+ ],
+ "sha256": "a36ae886ff1e4a545d4fbecb9b6e26c33d553af60189f8bdd04ede674f580a8e"
+ },
+ {
+ "label": "module:80686",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 727538,
+ 727755
+ ],
+ "fixture_utf16_range": [
+ 31836,
+ 32053
+ ],
+ "sha256": "8e98a2ac4b4d17f0926c355e807a66f0b200cc67b3924d52bb7bda83051ca784"
+ },
+ {
+ "label": "module:80777",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 12328,
+ 12431
+ ],
+ "fixture_utf16_range": [
+ 32055,
+ 32158
+ ],
+ "sha256": "d5a3bd9ed95f328cdcadb2abbadb085a23ae0d892937d195d705fe3454d57628"
+ },
+ {
+ "label": "module:80796",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 603058,
+ 603115
+ ],
+ "fixture_utf16_range": [
+ 32160,
+ 32217
+ ],
+ "sha256": "44c89c326178075a48fddb166cee9ed2a7e031cce9cab3857960f3490212d11c"
+ },
+ {
+ "label": "module:81211",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 608116,
+ 608212
+ ],
+ "fixture_utf16_range": [
+ 32219,
+ 32315
+ ],
+ "sha256": "cbd101b158d70aabecf87e86b3aa8fa0a7feb60fa019caa70dd55435efd8c269"
+ },
+ {
+ "label": "module:81935",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 12432,
+ 12535
+ ],
+ "fixture_utf16_range": [
+ 32317,
+ 32420
+ ],
+ "sha256": "8666ceaaf3946f2a9fde43b9d1a84d3968f6397c7772ff22a4655f6bdd160742"
+ },
+ {
+ "label": "module:82226",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 10336,
+ 10441
+ ],
+ "fixture_utf16_range": [
+ 32422,
+ 32527
+ ],
+ "sha256": "3b465f17792c72b047fe738eeb5d7771937e4f026b172d745139b482968c9729"
+ },
+ {
+ "label": "module:82438",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 162032,
+ 162113
+ ],
+ "fixture_utf16_range": [
+ 32529,
+ 32610
+ ],
+ "sha256": "210c0fa472c3c4ba6ecbd0a33951e1e79960edfa96ee7b162744b33ba5c1ede1"
+ },
+ {
+ "label": "module:82451",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 608667,
+ 608749
+ ],
+ "fixture_utf16_range": [
+ 32612,
+ 32694
+ ],
+ "sha256": "1e1a647582f679e13827e7440d3b8e0b8ce59a4d5c39a9c809ded364ea5bf6e0"
+ },
+ {
+ "label": "module:82619",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 604567,
+ 604630
+ ],
+ "fixture_utf16_range": [
+ 32696,
+ 32759
+ ],
+ "sha256": "d3925a2473dd47710bd679e3fb191545a00b9307ac903fd1ceb6290706f4d358"
+ },
+ {
+ "label": "module:83546",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 12931,
+ 13017
+ ],
+ "fixture_utf16_range": [
+ 32761,
+ 32847
+ ],
+ "sha256": "12770e13eed4a336689e5e43247acb37806734586e18bed27addf145aa238e43"
+ },
+ {
+ "label": "module:83943",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 14720,
+ 14805
+ ],
+ "fixture_utf16_range": [
+ 32849,
+ 32934
+ ],
+ "sha256": "fc7f6cc7819d8084c96f18bb458e41270e2d7dd93a5bce531723e53435b2b5e3"
+ },
+ {
+ "label": "module:84354",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 592718,
+ 592821
+ ],
+ "fixture_utf16_range": [
+ 32936,
+ 33039
+ ],
+ "sha256": "15908c95388269ac42fe80ad5b4b98f5c59084022a43615e1b83d5cf4eb12367"
+ },
+ {
+ "label": "module:84570",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 584560,
+ 584618
+ ],
+ "fixture_utf16_range": [
+ 33041,
+ 33099
+ ],
+ "sha256": "a1e6b1691e64d253b6d095af66b0ae363bbac4f55ea23368157c82f664de67b5"
+ },
+ {
+ "label": "module:84882",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 599948,
+ 600081
+ ],
+ "fixture_utf16_range": [
+ 33101,
+ 33234
+ ],
+ "sha256": "1e2769c3188a2311df87097d0011a17789802b030c1980ba0b050d18f9a72a49"
+ },
+ {
+ "label": "module:85832",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 13358,
+ 13523
+ ],
+ "fixture_utf16_range": [
+ 33236,
+ 33401
+ ],
+ "sha256": "9f520d64444c005acfb112108f5b25492e1b91b39cb5a2afb79e017f599bad59"
+ },
+ {
+ "label": "module:85899",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 597705,
+ 597988
+ ],
+ "fixture_utf16_range": [
+ 33403,
+ 33686
+ ],
+ "sha256": "d0ef9c68a37fde49f64a26b91422e4bdc4b373fae23cc669b6b5dd420bf26944"
+ },
+ {
+ "label": "module:86565",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 51418,
+ 51658
+ ],
+ "fixture_utf16_range": [
+ 33688,
+ 33928
+ ],
+ "sha256": "0f6f721c882062851680b83b1d5773534b60b8a37cc673af0489f1bacee14def"
+ },
+ {
+ "label": "module:86596",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 593604,
+ 593764
+ ],
+ "fixture_utf16_range": [
+ 33930,
+ 34090
+ ],
+ "sha256": "69ad38ed878b07d5516be517db9137d49ce5c668c50fa32cc7b7285dc02609d1"
+ },
+ {
+ "label": "module:86912",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 582720,
+ 582922
+ ],
+ "fixture_utf16_range": [
+ 34092,
+ 34294
+ ],
+ "sha256": "e924a40ffc16cd961d564fc7c5d69f43acab910d68a2a6f7edc25b3ba2251e06"
+ },
+ {
+ "label": "module:87379",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 585211,
+ 585410
+ ],
+ "fixture_utf16_range": [
+ 34296,
+ 34495
+ ],
+ "sha256": "c8f0743a2c3a4f34a4a1bd153ed5855ec56b06c535ced7fa4da06e4bc9986f0c"
+ },
+ {
+ "label": "module:87809",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 158391,
+ 158538
+ ],
+ "fixture_utf16_range": [
+ 34497,
+ 34644
+ ],
+ "sha256": "f4ee5c3f358f7b22762835114176048afa29c626c59690e2aa6bfa073cdca8df"
+ },
+ {
+ "label": "module:87835",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 162114,
+ 162214
+ ],
+ "fixture_utf16_range": [
+ 34646,
+ 34746
+ ],
+ "sha256": "495bbb500b672d87a46ac5a345600389d6a200fe88591445873f66a5911c0066"
+ },
+ {
+ "label": "module:88861",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 594436,
+ 595334
+ ],
+ "fixture_utf16_range": [
+ 34748,
+ 35646
+ ],
+ "sha256": "c78272e4aae60cf26a474e107662fa8e055ef97a279700927c45e0fd78f1ebeb"
+ },
+ {
+ "label": "module:89220",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 247069,
+ 247619
+ ],
+ "fixture_utf16_range": [
+ 35648,
+ 36198
+ ],
+ "sha256": "c2fee77902187f680fa5d4e591720e61726edf085e3bcf78fb3b6a134b1252df"
+ },
+ {
+ "label": "module:89413",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 581956,
+ 582024
+ ],
+ "fixture_utf16_range": [
+ 36200,
+ 36268
+ ],
+ "sha256": "3b52d0b698640c31090b103a3a6226bd8f24fa684a834d0fce5fa3c3932fe429"
+ },
+ {
+ "label": "module:89624",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 586765,
+ 587159
+ ],
+ "fixture_utf16_range": [
+ 36270,
+ 36664
+ ],
+ "sha256": "5cecc166757bee669c09d915cb56038ba503c2ec6cd5c87256b9312d2fffdcbb"
+ },
+ {
+ "label": "module:90313",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 584619,
+ 584818
+ ],
+ "fixture_utf16_range": [
+ 36666,
+ 36865
+ ],
+ "sha256": "1c3e18ac5d5aa1038eeb1db06ed93461766dc24e5b7c5c76b234342a00e67ca3"
+ },
+ {
+ "label": "module:90353",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 585739,
+ 586373
+ ],
+ "fixture_utf16_range": [
+ 36867,
+ 37501
+ ],
+ "sha256": "ab76b89c77f301a1a6835ac45a5f1e8fcc9025c32b6232af1fa6eb6fe0bc1ecc"
+ },
+ {
+ "label": "module:92264",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 592063,
+ 592198
+ ],
+ "fixture_utf16_range": [
+ 37503,
+ 37638
+ ],
+ "sha256": "adc6d910a4945b8b216fcd91a1715484f1d847703e005ec8665e0f438352e143"
+ },
+ {
+ "label": "module:92480",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 12536,
+ 12675
+ ],
+ "fixture_utf16_range": [
+ 37640,
+ 37779
+ ],
+ "sha256": "54313abc78ce9ce3a70460c3c9d88d787dd08e5edbd538856f6f54805bc7e774"
+ },
+ {
+ "label": "module:93526",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 591550,
+ 591672
+ ],
+ "fixture_utf16_range": [
+ 37781,
+ 37903
+ ],
+ "sha256": "1db2e572b412f3fa12052deb00a6e699b0e0186b4fd163ab6dd423dff74da06d"
+ },
+ {
+ "label": "module:93650",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 13265,
+ 13357
+ ],
+ "fixture_utf16_range": [
+ 37905,
+ 37997
+ ],
+ "sha256": "1d44dcb8b93cb2a36dc2bf2f3400e6d3cd2b65685993301185fa41b3126c4f20"
+ },
+ {
+ "label": "module:93655",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 607053,
+ 607276
+ ],
+ "fixture_utf16_range": [
+ 37999,
+ 38222
+ ],
+ "sha256": "ed8d08853c943b620d2fd9e69c28413a4621bc7095b437da0b50033816a5aa19"
+ },
+ {
+ "label": "module:94497",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 601642,
+ 601703
+ ],
+ "fixture_utf16_range": [
+ 38224,
+ 38285
+ ],
+ "sha256": "4615c772475a5838c5bd4612439c16377942727c5abf54f8cffaa958573dc390"
+ },
+ {
+ "label": "module:94652",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 255124,
+ 255362
+ ],
+ "fixture_utf16_range": [
+ 38287,
+ 38525
+ ],
+ "sha256": "832bfdab389d857e595dcf1f4acc3f96cc33761df6c44b94c7d3a263f7306ce3"
+ },
+ {
+ "label": "module:94687",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 585590,
+ 585738
+ ],
+ "fixture_utf16_range": [
+ 38527,
+ 38675
+ ],
+ "sha256": "6cd51955bb8ba4f7370d16a33f2ba04a7d0fb022d85551a8d7aebd5602c70e38"
+ },
+ {
+ "label": "module:94715",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 596477,
+ 596578
+ ],
+ "fixture_utf16_range": [
+ 38677,
+ 38778
+ ],
+ "sha256": "bf7bce2cd271b629dbaa1351ad1d830a96715eb22341065f820ea2c799e7baf4"
+ },
+ {
+ "label": "module:94889",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 725026,
+ 725113
+ ],
+ "fixture_utf16_range": [
+ 38780,
+ 38867
+ ],
+ "sha256": "1b7728d1ffb25d7ab66bedaa2d69f0f869d40a9d7768068db841f074412e0577"
+ },
+ {
+ "label": "module:95243",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 609977,
+ 610054
+ ],
+ "fixture_utf16_range": [
+ 38869,
+ 38946
+ ],
+ "sha256": "a281508355dc2b31a0bed6628444c3835e69f9ed3ee868930827b133efa269ab"
+ },
+ {
+ "label": "module:95894",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 601283,
+ 601391
+ ],
+ "fixture_utf16_range": [
+ 38948,
+ 39056
+ ],
+ "sha256": "73dd83732f567e1d98bfa3e81016447c1dbd82b5375690b6c4659c027359eb70"
+ },
+ {
+ "label": "module:96493",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 603890,
+ 604178
+ ],
+ "fixture_utf16_range": [
+ 39058,
+ 39346
+ ],
+ "sha256": "6022a16a994e7f2e22fb49ccf64fff4541f75aba3024a79aeb0782f1f11627bf"
+ },
+ {
+ "label": "module:96638",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 603620,
+ 603889
+ ],
+ "fixture_utf16_range": [
+ 39348,
+ 39617
+ ],
+ "sha256": "97386fc0ac3b4a1950d9546d477cefb9db7aefd5a3cd7fa4be9b511f526c0f22"
+ },
+ {
+ "label": "module:96830",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 271404,
+ 271588
+ ],
+ "fixture_utf16_range": [
+ 39619,
+ 39803
+ ],
+ "sha256": "142815a78de8e480b7f49b8142a736ab53d94b466a917f352c174fb576b53046"
+ },
+ {
+ "label": "module:97034",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 584104,
+ 584218
+ ],
+ "fixture_utf16_range": [
+ 39805,
+ 39919
+ ],
+ "sha256": "636a711cd1cd753c9e9632151e3b9d78c83f2efdee8c992c157e8f446632a0cf"
+ },
+ {
+ "label": "module:97350",
+ "source_file": "40784.08580559.js",
+ "source_utf16_range": [
+ 13524,
+ 13727
+ ],
+ "fixture_utf16_range": [
+ 39921,
+ 40124
+ ],
+ "sha256": "26bdce0d2dd0bf2e4165e5f06a665735673a150b931f04cae48d06cda193c60e"
+ },
+ {
+ "label": "module:97706",
+ "source_file": "56679.46a7195c.js",
+ "source_utf16_range": [
+ 156976,
+ 157077
+ ],
+ "fixture_utf16_range": [
+ 40126,
+ 40227
+ ],
+ "sha256": "8c5aa29d25e6bc73c1f9ba0e08faf5c817a6064da9b4b538a555f7c165d3525c"
+ },
+ {
+ "label": "module:98121",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 601704,
+ 601767
+ ],
+ "fixture_utf16_range": [
+ 40229,
+ 40292
+ ],
+ "sha256": "135ba2b8442dde80118be4ab46c28ac0472930f78fcbbdf996b5a0bede5c86df"
+ },
+ {
+ "label": "module:98880",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 590387,
+ 590475
+ ],
+ "fixture_utf16_range": [
+ 40294,
+ 40382
+ ],
+ "sha256": "23e079370e85de6a1f06b0a3147992d71778c62366bb348e45438f2b829adac7"
+ },
+ {
+ "label": "module:99306",
+ "source_file": "main.37a180b9.js",
+ "source_utf16_range": [
+ 609149,
+ 609192
+ ],
+ "fixture_utf16_range": [
+ 40384,
+ 40427
+ ],
+ "sha256": "7d16ff5ad030e85fb71cdd8c2c331f559e3111f9fde1844780b26953bdc7d618"
+ },
+ {
+ "label": "declaration:76535:x",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 842,
+ 852
+ ],
+ "fixture_utf16_range": [
+ 40474,
+ 40484
+ ],
+ "sha256": "9ad185a7078d40d73a11dab2f178919067a27cb7b7dea2d47f257e1738025c18"
+ },
+ {
+ "label": "declaration:76535:ot",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 853,
+ 862
+ ],
+ "fixture_utf16_range": [
+ 40490,
+ 40499
+ ],
+ "sha256": "f8b3edc1931d3dc8d7dee6583d346ec88090e793b2da6ede4d9e20f1882a8bae"
+ },
+ {
+ "label": "declaration:76535:Z",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 863,
+ 873
+ ],
+ "fixture_utf16_range": [
+ 40505,
+ 40515
+ ],
+ "sha256": "3674766b2cf72925c19de372f9dad1f3280ddbc4172f292407b078feb4cebe8b"
+ },
+ {
+ "label": "declaration:76535:rt",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 874,
+ 883
+ ],
+ "fixture_utf16_range": [
+ 40521,
+ 40530
+ ],
+ "sha256": "d204358ced7299ffe156e8cb511a1065303ff22970b3541141e92e065434948c"
+ },
+ {
+ "label": "declaration:76535:at",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 884,
+ 895
+ ],
+ "fixture_utf16_range": [
+ 40536,
+ 40547
+ ],
+ "sha256": "7ef5bac3d56cce9f4a4ccb488624d9ca7f67ae4ccd410776535a97f78edc5a7f"
+ },
+ {
+ "label": "declaration:76535:j",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 896,
+ 905
+ ],
+ "fixture_utf16_range": [
+ 40553,
+ 40562
+ ],
+ "sha256": "4f26093b37c5ac6ab0b0e0e90cdc1f799e3181aff7588816f86abb5d3346884a"
+ },
+ {
+ "label": "declaration:76535:X",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 906,
+ 916
+ ],
+ "fixture_utf16_range": [
+ 40568,
+ 40578
+ ],
+ "sha256": "9b58b0dc7769ff55974e8d82666f3999e115af7a03d0758e0e9ea95e89e3555e"
+ },
+ {
+ "label": "declaration:76535:Q",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 917,
+ 925
+ ],
+ "fixture_utf16_range": [
+ 40584,
+ 40592
+ ],
+ "sha256": "65356d2234bf73e526637b595f087be0178713bb05924e53046a9e918db301ce"
+ },
+ {
+ "label": "declaration:76535:ct",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 926,
+ 937
+ ],
+ "fixture_utf16_range": [
+ 40598,
+ 40609
+ ],
+ "sha256": "a220f1fde6fd9f87a1ce53511f6d0a4ff687ef3be7e3706cf141823be6b9c8e9"
+ },
+ {
+ "label": "declaration:76535:it",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 938,
+ 948
+ ],
+ "fixture_utf16_range": [
+ 40615,
+ 40625
+ ],
+ "sha256": "85c46200880294356763a6943229f5377b0ed607802a14acc6de51284720f709"
+ },
+ {
+ "label": "declaration:76535:lt",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 949,
+ 960
+ ],
+ "fixture_utf16_range": [
+ 40631,
+ 40642
+ ],
+ "sha256": "0bd854ed6256e923c3d4a28966e2f97f1a6076c01d2f648efdf45c12abe32982"
+ },
+ {
+ "label": "declaration:76535:ut",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 961,
+ 971
+ ],
+ "fixture_utf16_range": [
+ 40648,
+ 40658
+ ],
+ "sha256": "44e5ccda01242c8dd61697a99798630beb585ed6fdd76e2962edb237f8fddea7"
+ },
+ {
+ "label": "declaration:76535:a",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 972,
+ 982
+ ],
+ "fixture_utf16_range": [
+ 40664,
+ 40674
+ ],
+ "sha256": "b2b87dae2238d22539bac309bd4dc93363985f0d72f0d46f474c3327192a1f45"
+ },
+ {
+ "label": "declaration:76535:E",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 983,
+ 991
+ ],
+ "fixture_utf16_range": [
+ 40680,
+ 40688
+ ],
+ "sha256": "f5845df83303b3fd14d567796ddda2fbe7f0db84dde36a83d05e94fa027e5c50"
+ },
+ {
+ "label": "declaration:76535:O",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 992,
+ 1002
+ ],
+ "fixture_utf16_range": [
+ 40694,
+ 40704
+ ],
+ "sha256": "165f104b7a9466aa11642778013c3bc4a4ac6fbb5ce4d82b0b2f7b598325a465"
+ },
+ {
+ "label": "declaration:76535:W",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 1009,
+ 1036
+ ],
+ "fixture_utf16_range": [
+ 40712,
+ 40739
+ ],
+ "sha256": "1b830418cd017fa6780aeabdc522fda254d402fcf7a2dea8af7dac025cc72273"
+ },
+ {
+ "label": "declaration:76535:_t",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 1037,
+ 1064
+ ],
+ "fixture_utf16_range": [
+ 40747,
+ 40774
+ ],
+ "sha256": "98888ddda77291ec1b0a2fbcf0e655dc12270948e41b8d8668c8d0527256c505"
+ },
+ {
+ "label": "declaration:76535:ft",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 1065,
+ 1084
+ ],
+ "fixture_utf16_range": [
+ 40782,
+ 40801
+ ],
+ "sha256": "3948796a7e26ef18c2fb975473a8880df62b2a1db012804ce4a15430fd3ebbf5"
+ },
+ {
+ "label": "declaration:76535:J",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 1085,
+ 1099
+ ],
+ "fixture_utf16_range": [
+ 40809,
+ 40823
+ ],
+ "sha256": "09fdbf3549b0ad2d869914e4ba6d0a020a47796dfd7ecdb022a47e36b3ec9baf"
+ },
+ {
+ "label": "declaration:76535:kt",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 1100,
+ 1508
+ ],
+ "fixture_utf16_range": [
+ 40831,
+ 41239
+ ],
+ "sha256": "bdfa48ab3ac321be02acf35dfb1b2533c6b555d31d44168186be498b3be0057e"
+ },
+ {
+ "label": "declaration:76535:wt",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 1509,
+ 1735
+ ],
+ "fixture_utf16_range": [
+ 41247,
+ 41473
+ ],
+ "sha256": "87a5d085750406888fee56c39f3692b2dee28fa9b72c268f4787f009db536962"
+ },
+ {
+ "label": "declaration:76535:xt",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 1736,
+ 2052
+ ],
+ "fixture_utf16_range": [
+ 41481,
+ 41797
+ ],
+ "sha256": "3b9d7cfb8fa7a0b37cdd3dfb0a72c41b74ea0a96cad984bb4f21bc1afa82507e"
+ },
+ {
+ "label": "declaration:76535:Wt",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 2053,
+ 2233
+ ],
+ "fixture_utf16_range": [
+ 41805,
+ 41985
+ ],
+ "sha256": "7066ec195732c44d0035f237f6b975979223f7f439f54c519f1cafa04d61ec97"
+ },
+ {
+ "label": "declaration:76535:zt",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 2234,
+ 2801
+ ],
+ "fixture_utf16_range": [
+ 41993,
+ 42560
+ ],
+ "sha256": "1cd5679e7d173811a05fb914dded3fac2c8ebebebfdf2bfc2603afcd6165d417"
+ },
+ {
+ "label": "callback:76535:Tt",
+ "source_file": "36648.dd8ae7dc.js",
+ "source_utf16_range": [
+ 10961,
+ 11094
+ ],
+ "fixture_utf16_range": [
+ 42822,
+ 42955
+ ],
+ "sha256": "3bdedf3c7a6695eed0cea796a03102637b689ec34edfbd86a5d4d59eed3022a2"
+ },
+ {
+ "label": "declaration:15426:ot",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 353192,
+ 353203
+ ],
+ "fixture_utf16_range": [
+ 43009,
+ 43020
+ ],
+ "sha256": "56f8d0ee86d72f3cc375925d68b9f7d4a8897a8e8d4eabef6db73a7c1b63de21"
+ },
+ {
+ "label": "declaration:15426:d",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 353204,
+ 353213
+ ],
+ "fixture_utf16_range": [
+ 43026,
+ 43035
+ ],
+ "sha256": "a8011da3bba4cb745ab319ef046e465913f328b8f7c8764f69f4588d6d5576c6"
+ },
+ {
+ "label": "declaration:15426:A",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 353318,
+ 353328
+ ],
+ "fixture_utf16_range": [
+ 43041,
+ 43051
+ ],
+ "sha256": "5f94721ea577197259ede09d3d9ecd5f3494f2771632c5fb6b191da3389a325a"
+ },
+ {
+ "label": "declaration:15426:R",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 353329,
+ 353337
+ ],
+ "fixture_utf16_range": [
+ 43057,
+ 43065
+ ],
+ "sha256": "c734a535a960f80e1be16a1c84c7fcbfdd94a3462d7a763063fed24128b0b590"
+ },
+ {
+ "label": "declaration:15426:Tt",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 359034,
+ 359045
+ ],
+ "fixture_utf16_range": [
+ 43071,
+ 43082
+ ],
+ "sha256": "ba947090750831ef3335c7b90de6adfb0dcc4bb571747cb8dbac0286dff65947"
+ },
+ {
+ "label": "declaration:15426:vt",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 359046,
+ 359057
+ ],
+ "fixture_utf16_range": [
+ 43088,
+ 43099
+ ],
+ "sha256": "5d279be4f9f699d9d62919bc11ed5b2e703b560ffe91172e0bed0b2a582746dd"
+ },
+ {
+ "label": "declaration:15426:lo",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 452441,
+ 452549
+ ],
+ "fixture_utf16_range": [
+ 43107,
+ 43215
+ ],
+ "sha256": "b15226a664d176b895c57ec87d0a854a77a17b57296a8340e2f40349ca781b62"
+ },
+ {
+ "label": "declaration:15426:Sa",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 499737,
+ 499748
+ ],
+ "fixture_utf16_range": [
+ 43221,
+ 43232
+ ],
+ "sha256": "36bcf27970ff0d306794def572e5e5c87d331dc06b2f9d979db29b2d1862def3"
+ },
+ {
+ "label": "declaration:15426:Dn",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 499749,
+ 499759
+ ],
+ "fixture_utf16_range": [
+ 43238,
+ 43248
+ ],
+ "sha256": "c54a7313d231a4652f386d8a70b0a77d84065701dfa0f677d96f0f8c7c7c343e"
+ },
+ {
+ "label": "declaration:15426:xc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537530,
+ 537541
+ ],
+ "fixture_utf16_range": [
+ 43254,
+ 43265
+ ],
+ "sha256": "a8efd2fc67b16aee2a87a349e11c82297b4f5cf254b78e418a0458b986611dde"
+ },
+ {
+ "label": "declaration:15426:bc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537542,
+ 537552
+ ],
+ "fixture_utf16_range": [
+ 43271,
+ 43281
+ ],
+ "sha256": "67be6013f2a9d488e6f37af0be5e9526b68b5552df143bec72f9b9ace2735c29"
+ },
+ {
+ "label": "declaration:15426:Sc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537553,
+ 537564
+ ],
+ "fixture_utf16_range": [
+ 43287,
+ 43298
+ ],
+ "sha256": "0d73989504bd1f5f943bc7dbfaa17a33547d8026b97be19f30321ebe78941076"
+ },
+ {
+ "label": "declaration:15426:Do",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537565,
+ 537575
+ ],
+ "fixture_utf16_range": [
+ 43304,
+ 43314
+ ],
+ "sha256": "6a6f9d98c4d4d341ed135a4ed092f5c28334add0bb65c190313ad7431b9ecd19"
+ },
+ {
+ "label": "declaration:15426:Ac",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537576,
+ 537587
+ ],
+ "fixture_utf16_range": [
+ 43320,
+ 43331
+ ],
+ "sha256": "435fd7bd280268f4c3eaed6d497568f7d14764ad022138026846e2790aa479ca"
+ },
+ {
+ "label": "declaration:15426:vc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537588,
+ 537598
+ ],
+ "fixture_utf16_range": [
+ 43337,
+ 43347
+ ],
+ "sha256": "5b120e90ec8b92e8c60a804421dffdef8fd1c2aa58f34a6f4fd07aa61297fa5f"
+ },
+ {
+ "label": "declaration:15426:Cc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537599,
+ 537610
+ ],
+ "fixture_utf16_range": [
+ 43353,
+ 43364
+ ],
+ "sha256": "5dc0da8abeee3aaea696a97e069841c858b04950cc21a91c81e12ad241d56ed9"
+ },
+ {
+ "label": "declaration:15426:Qi",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537611,
+ 537621
+ ],
+ "fixture_utf16_range": [
+ 43370,
+ 43380
+ ],
+ "sha256": "a435e6b65a5d242c9da21386a1ff9ce32e35f65a2b90671b2bc15d16b70bff04"
+ },
+ {
+ "label": "declaration:15426:Vo",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537628,
+ 537752
+ ],
+ "fixture_utf16_range": [
+ 43388,
+ 43512
+ ],
+ "sha256": "147376d47e9bac1b304d64f513d3ebfc5f3a3ec0b4587b9f7a31683c9b414a6f"
+ },
+ {
+ "label": "declaration:15426:Mo",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537753,
+ 537791
+ ],
+ "fixture_utf16_range": [
+ 43520,
+ 43558
+ ],
+ "sha256": "cad91c49bceb8905c1235d610a2e72d0f98cee5b2001f6a88ebb2e3af56e2990"
+ },
+ {
+ "label": "declaration:15426:wc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537792,
+ 537820
+ ],
+ "fixture_utf16_range": [
+ 43566,
+ 43594
+ ],
+ "sha256": "388d4c42ffb387d00039fd43d15a8e98c57862fd87311617226772c6ae90b3c5"
+ },
+ {
+ "label": "declaration:15426:No",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537821,
+ 537851
+ ],
+ "fixture_utf16_range": [
+ 43602,
+ 43632
+ ],
+ "sha256": "391ba5f54d116410f0580cff315196eea825b78e1642597a68b560830abd62e8"
+ },
+ {
+ "label": "declaration:15426:Eo",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537852,
+ 537896
+ ],
+ "fixture_utf16_range": [
+ 43640,
+ 43684
+ ],
+ "sha256": "0d642a1b392a1e1af1da5fcf1e76cc86a7288f34886ebac93b2b80393da39879"
+ },
+ {
+ "label": "declaration:15426:qi",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537897,
+ 537914
+ ],
+ "fixture_utf16_range": [
+ 43692,
+ 43709
+ ],
+ "sha256": "16909925e9f5a091cf0d718f10b69559d8d7e6615be2a36579e1687f9e29e1aa"
+ },
+ {
+ "label": "declaration:15426:Rn",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537915,
+ 537932
+ ],
+ "fixture_utf16_range": [
+ 43717,
+ 43734
+ ],
+ "sha256": "4b6ffa9d48051416ea6a9de96502284c8c2eac7aa357e43438859d8d361df37e"
+ },
+ {
+ "label": "declaration:15426:Tc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537933,
+ 537962
+ ],
+ "fixture_utf16_range": [
+ 43742,
+ 43771
+ ],
+ "sha256": "1df691ee775abfb89520e8fe5549435c7d09699c687a80680f29300ec6a1fd85"
+ },
+ {
+ "label": "declaration:15426:Bo",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 537963,
+ 538028
+ ],
+ "fixture_utf16_range": [
+ 43779,
+ 43844
+ ],
+ "sha256": "a943fcd7c936fe7932a59007ec54496d289eb5bb8ed4411d087178f9195d2070"
+ },
+ {
+ "label": "declaration:15426:Ro",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 538029,
+ 538370
+ ],
+ "fixture_utf16_range": [
+ 43846,
+ 44187
+ ],
+ "sha256": "237c259a915dca6aeb80153236d72130e7449aa81fefb1e4ac10f400cefd405f"
+ },
+ {
+ "label": "declaration:15426:Lc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 538370,
+ 538568
+ ],
+ "fixture_utf16_range": [
+ 44188,
+ 44386
+ ],
+ "sha256": "403d0bf86e9d5c72c2dae7bc13ea0847a855f51cedf5c93a92ba0a2dffae7718"
+ },
+ {
+ "label": "declaration:15426:Ic",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 538574,
+ 538745
+ ],
+ "fixture_utf16_range": [
+ 44393,
+ 44564
+ ],
+ "sha256": "1d2aee0d64ecd4167bdd3cc54f005953f29572690a4b06b5abc7a91ef3105f24"
+ },
+ {
+ "label": "declaration:15426:_o",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 538746,
+ 539001
+ ],
+ "fixture_utf16_range": [
+ 44572,
+ 44827
+ ],
+ "sha256": "6fda5f78546408db3eedadbea31abebe0ca2413f112bb1fad8717074a5371253"
+ },
+ {
+ "label": "declaration:15426:ko",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 539002,
+ 539034
+ ],
+ "fixture_utf16_range": [
+ 44835,
+ 44867
+ ],
+ "sha256": "3fdb1f0e4a7c6e4da456f68139cfc397b1a3effa81c9075c179aa6ac6cda3fa0"
+ },
+ {
+ "label": "declaration:15426:Pc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 539035,
+ 539172
+ ],
+ "fixture_utf16_range": [
+ 44875,
+ 45012
+ ],
+ "sha256": "b20c55d629d6c5e0426b30aaaa9378e9114f6284ff2c10c7c75f2b06bed6492c"
+ },
+ {
+ "label": "declaration:15426:Ho",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 539173,
+ 539266
+ ],
+ "fixture_utf16_range": [
+ 45020,
+ 45113
+ ],
+ "sha256": "d3cc9d7026185bb2efd068eb1c5252c2004b1c73d5f48cf0c7312b711e54a7bc"
+ },
+ {
+ "label": "declaration:15426:Wo",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 539267,
+ 541246
+ ],
+ "fixture_utf16_range": [
+ 45121,
+ 47100
+ ],
+ "sha256": "54f9b5d4f41fc656aca9db9b591909d284ba24b53ef35d944e3537446dc7a81b"
+ },
+ {
+ "label": "declaration:15426:Dc",
+ "source_file": "28928.9aeec892.js",
+ "source_utf16_range": [
+ 541247,
+ 542526
+ ],
+ "fixture_utf16_range": [
+ 47108,
+ 48387
+ ],
+ "sha256": "eaba7dfb579d8b085c5f890d78deb8d8b528f1939a0078df5384d7cfc1a18ea1"
+ },
+ {
+ "label": "declaration:70940:o",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 6745,
+ 6755
+ ],
+ "fixture_utf16_range": [
+ 48471,
+ 48481
+ ],
+ "sha256": "57f898825807bc474d4ed21a63a45176cf280cd7cd267bc45cf1f825d549dc40"
+ },
+ {
+ "label": "declaration:70940:v",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 6756,
+ 6766
+ ],
+ "fixture_utf16_range": [
+ 48487,
+ 48497
+ ],
+ "sha256": "99611dfae5183efc7bbecf3b7711008a90757c5f3d22ad36d281ec4e4aa0925e"
+ },
+ {
+ "label": "declaration:70940:i",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 6778,
+ 6788
+ ],
+ "fixture_utf16_range": [
+ 48503,
+ 48513
+ ],
+ "sha256": "58a6ec8c539e8b2818a3a9e166dc301541aa8019e3cb99f13f4c7d13c961d138"
+ },
+ {
+ "label": "declaration:70940:M",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 6811,
+ 6821
+ ],
+ "fixture_utf16_range": [
+ 48519,
+ 48529
+ ],
+ "sha256": "4ba26af3077c24df79061524d201d923b2c29dbff3a4f5c8eba0788d920ed02c"
+ },
+ {
+ "label": "declaration:70940:h",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 6822,
+ 6830
+ ],
+ "fixture_utf16_range": [
+ 48535,
+ 48543
+ ],
+ "sha256": "146f2b139dd62452d5af6fd728ce7469debff80b74102cf2dd04e56a98bbaed5"
+ },
+ {
+ "label": "declaration:70940:V",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 6851,
+ 6860
+ ],
+ "fixture_utf16_range": [
+ 48549,
+ 48558
+ ],
+ "sha256": "0b2f315d50c17637ac2e831984417a0476826c8e6d58c99a793c4ad2d93942da"
+ },
+ {
+ "label": "declaration:70940:U",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 6861,
+ 6869
+ ],
+ "fixture_utf16_range": [
+ 48564,
+ 48572
+ ],
+ "sha256": "426e5ec0784fc49b6f26903423cad35d27d1bfe4a76631217477bf682528f94d"
+ },
+ {
+ "label": "declaration:70940:Qe",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 10129,
+ 10140
+ ],
+ "fixture_utf16_range": [
+ 48578,
+ 48589
+ ],
+ "sha256": "d47e0d632cd4028771ceca7c6412f0295bc8c7da270d3ba2793f58e3c6eb1092"
+ },
+ {
+ "label": "declaration:70940:Tn",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 31600,
+ 31616
+ ],
+ "fixture_utf16_range": [
+ 48595,
+ 48611
+ ],
+ "sha256": "e977d40a86f3906c839fc1579d1c14b4b5d60c087d3a3a0a90036bce51d07841"
+ },
+ {
+ "label": "callback:70940:z",
+ "source_file": "TradingView.99bc5074.js",
+ "source_utf16_range": [
+ 32997,
+ 34322
+ ],
+ "fixture_utf16_range": [
+ 48748,
+ 50073
+ ],
+ "sha256": "79aa5f884c6cc6c32815b756a2a406b73231c0c97d058c781a45d0413fe6b6ee"
+ }
+ ]
+}
diff --git a/test/fixtures/binance-chart-storage/host-persistence.js b/test/fixtures/binance-chart-storage/host-persistence.js
new file mode 100644
index 0000000..4c04b61
--- /dev/null
+++ b/test/fixtures/binance-chart-storage/host-persistence.js
@@ -0,0 +1,285 @@
+/* Exact public host persistence fragments; see host-persistence-manifest.json. */
+(self.webpackChunkfutures_trade_ui=self.webpackChunkfutures_trade_ui||[]).push([["codex-chart-host-persistence-fixture"],{
+195(_,y,e){var t=e(84882),i=e(98121),r=Object.prototype,a=r.hasOwnProperty;function s(n){if(!t(n))return i(n);var o=[];for(var c in Object(n))a.call(n,c)&&c!="constructor"&&o.push(c);return o}_.exports=s},
+1386(_,y,e){var t=e(12393),i=e(62049),r=e(7144),a=e(7452),s=e(13964);function n(o){var c=-1,v=o==null?0:o.length;for(this.clear();++c0){if(++a>=y)return arguments[0]}else a=0;return r.apply(void 0,arguments)}}_.exports=i},
+3334(_,y,e){var t=e(36526),i=e(12343),r=e(22053),a=e(53142),s=e(43282);function n(o){return typeof o=="function"?o:o==null?r:typeof o=="object"?a(o)?i(o[0],o[1]):t(o):s(o)}_.exports=n},
+3422(_,y,e){var t=e(57073),i=e(46285),r=Object.prototype,a=r.hasOwnProperty;function s(n,o,c){var v=n[o];(!(a.call(n,o)&&i(v,c))||c===void 0&&!(o in n))&&t(n,o,c)}_.exports=s},
+3766(_){function y(e,t){return function(i){return e(t(i))}}_.exports=y},
+5681(_,y,e){var t=e(44700);function i(r){var a=t(this,r).delete(r);return this.size-=a?1:0,a}_.exports=i},
+7144(_,y,e){var t=e(97034);function i(r){var a=this.__data__,s=t(a,r);return s<0?void 0:a[s][1]}_.exports=i},
+7452(_,y,e){var t=e(97034);function i(r){return t(this.__data__,r)>-1}_.exports=i},
+7462(_){function y(e){return this.__data__.has(e)}_.exports=y},
+7980(Z,x,e){var o=e(23945),v=e(86565),d=o(v);Z.exports=d},
+8006(_){function y(e){return this.__data__.has(e)}_.exports=y},
+8666(_,y,e){var t=e(70674),i=e(49460),r=e(2306),a=r&&r.isTypedArray,s=a?i(a):t;_.exports=s},
+8870(_,y,e){var t=e(65650),i=Object.prototype,r=i.hasOwnProperty,a=i.toString,s=t?t.toStringTag:void 0;function n(o){var c=r.call(o,s),v=o[s];try{o[s]=void 0;var g=!0}catch{}var S=a.call(o);return g&&(c?o[s]=v:delete o[s]),S}_.exports=n},
+10010(_,y,e){"use strict";e.d(y,{_:()=>t});function t(i,r){(r==null||r>i.length)&&(r=i.length);for(var a=0,s=new Array(r);a=u){var he=w?null:a(_);if(he)return c(he);W=!1,$=n,ne=new t}else ne=w?[]:R;e:for(;++OC))return!1;var b=d.get(o),f=d.get(c);if(b&&f)return b==c&&f==o;var p=-1,u=!0,h=v&s?new t:void 0;for(d.set(o,c),d.set(c,o);++pt});function t(i,r){var a,s,n,o={label:0,sent:function(){if(n[0]&1)throw n[1];return n[1]},trys:[],ops:[]},c=Object.create((typeof Iterator=="function"?Iterator:Object).prototype),v=Object.defineProperty;return v(c,"next",{value:g(0)}),v(c,"throw",{value:g(1)}),v(c,"return",{value:g(2)}),typeof Symbol=="function"&&v(c,Symbol.iterator,{value:function(){return this}}),c;function g(d){return function(A){return S([d,A])}}function S(d){if(a)throw new TypeError("Generator is already executing.");for(;c&&(c=0,d[0]&&(o=0)),o;)try{if(a=1,s&&(n=d[0]&2?s.return:d[0]?s.throw||((n=s.return)&&n.call(s),0):s.next)&&!(n=n.call(s,d[1])).done)return n;switch(s=0,n&&(d=[d[0]&2,n.value]),d[0]){case 0:case 1:n=d;break;case 4:return o.label++,{value:d[1],done:!1};case 5:o.label++,s=d[1],d=[0];continue;case 7:d=o.ops.pop(),o.trys.pop();continue;default:if(n=o.trys,!(n=n.length>0&&n[n.length-1])&&(d[0]===6||d[0]===2)){o=0;continue}if(d[0]===3&&(!n||d[1]>n[0]&&d[1]i});function t(r,a,s,n,o,c,v){try{var g=r[c](v),S=g.value}catch(d){s(d);return}g.done?a(S):Promise.resolve(S).then(n,o)}function i(r){return function(){var a=this,s=arguments;return new Promise(function(n,o){var c=r.apply(a,s);function v(S){t(c,n,o,v,g,"next",S)}function g(S){t(c,n,o,v,g,"throw",S)}v(void 0)})}}},
+49460(_){function y(e){return function(t){return e(t)}}_.exports=y},
+49753(_,y,e){var t=e(25098),i=e(1386),r=e(19770);function a(){this.size=0,this.__data__={hash:new t,map:new(r||i),string:new t}}_.exports=a},
+49828(_){function y(e){var t=-1,i=Array(e.size);return e.forEach(function(r){i[++t]=r}),i}_.exports=y},
+50424(O,p,i){var b=i(58898),u=i(92480),g=i(78340),a=i(18069),f=i(92264),_="[object Boolean]",h="[object Date]",T="[object Map]",d="[object Number]",L="[object RegExp]",I="[object Set]",R="[object String]",S="[object Symbol]",A="[object ArrayBuffer]",Z="[object DataView]",D="[object Float32Array]",K="[object Float64Array]",ae="[object Int8Array]",q="[object Int16Array]",ne="[object Int32Array]",W="[object Uint8Array]",s="[object Uint8ClampedArray]",m="[object Uint16Array]",z="[object Uint32Array]";function U(H,X,M){var ie=H.constructor;switch(X){case A:return b(H);case _:case h:return new ie(+H);case Z:return u(H,M);case D:case K:case ae:case q:case ne:case W:case s:case m:case z:return f(H,M);case T:return new ie;case d:case R:return new ie(H);case L:return g(H);case I:return new ie;case S:return a(H)}}O.exports=U},
+51187(_,y,e){var t=e(87379),i=e(80547),r="[object Symbol]";function a(s){return typeof s=="symbol"||i(s)&&t(s)==r}_.exports=a},
+51340(_,y,e){var t=e(1386),i=e(64103),r=e(21779),a=e(34162),s=e(7462),n=e(96638);function o(c){var v=this.__data__=new t(c);this.size=v.size}o.prototype.clear=i,o.prototype.delete=r,o.prototype.get=a,o.prototype.has=s,o.prototype.set=n,_.exports=o},
+52291(_,y,e){var t=e(65650),i=e(65111),r=e(53142),a=e(51187),s=1/0,n=t?t.prototype:void 0,o=n?n.toString:void 0;function c(v){if(typeof v=="string")return v;if(r(v))return i(v,c)+"";if(a(v))return o?o.call(v):"";var g=v+"";return g=="0"&&1/v==-s?"-0":g}_.exports=c},
+53142(_){var y=Array.isArray;_.exports=y},
+54732(_,y,e){var t=e(44700);function i(r){return t(this,r).has(r)}_.exports=i},
+56027(_,y,e){var t=e(87379),i=e(80547),r="[object Arguments]";function a(s){return i(s)&&t(s)==r}_.exports=a},
+57073(_,y,e){var t=e(72532);function i(r,a,s){a=="__proto__"&&t?t(r,a,{configurable:!0,enumerable:!0,value:s,writable:!0}):r[a]=s}_.exports=i},
+57200(_,y,e){var t=e(41580),i=e(84882),r=e(38546),a=Object.prototype,s=a.hasOwnProperty;function n(o){if(!t(o))return r(o);var c=i(o),v=[];for(var g in o)g=="constructor"&&(c||!s.call(o,g))||v.push(g);return v}_.exports=n},
+57452(_,y,e){var t=e(94497),i="__lodash_hash_undefined__";function r(a,s){var n=this.__data__;return this.size+=this.has(a)?0:1,n[a]=t&&s===void 0?i:s,this}_.exports=r},
+57923(_,y,e){var t=e(93526),i=e(66040);function r(a,s){s=t(s,a);for(var n=0,o=s.length;a!=null&&n-1}v.exports=o},
+61875(_,y,e){"use strict";e.d(y,{_:()=>i});var t=e(10010);function i(r,a){if(r){if(typeof r=="string")return(0,t._)(r,a);var s=Object.prototype.toString.call(r).slice(8,-1);if(s==="Object"&&r.constructor&&(s=r.constructor.name),s==="Map"||s==="Set")return Array.from(s);if(s==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(s))return(0,t._)(r,a)}}},
+62049(_,y,e){var t=e(97034),i=Array.prototype,r=i.splice;function a(s){var n=this.__data__,o=t(n,s);if(o<0)return!1;var c=n.length-1;return o==c?n.pop():r.call(n,o,1),--this.size,!0}_.exports=a},
+64066(_){var y=Function.prototype,e=y.toString;function t(i){if(i!=null){try{return e.call(i)}catch{}try{return i+""}catch{}}return""}_.exports=t},
+64103(_,y,e){var t=e(1386);function i(){this.__data__=new t,this.size=0}_.exports=i},
+64207(_,y,e){var t=e(39032),i=e(25288);function r(a){return t(a,i(a))}_.exports=r},
+64512(_,y,e){var t=e(94715),i=e(78942),r=t(i,"Set");_.exports=r},
+64759(_,y,e){var t=e(41950),i=(function(){var a=/[^.]+$/.exec(t&&t.keys&&t.keys.IE_PROTO||"");return a?"Symbol(src)_1."+a:""})();function r(a){return!!i&&i in a}_.exports=r},
+65111(_){function y(e,t){for(var i=-1,r=e==null?0:e.length,a=Array(r);++i-1&&t%1==0&&t<=y}_.exports=e},
+65406(_,y,e){var t=e(36529),i=e(80547);function r(a){return i(a)&&t(a)}_.exports=r},
+65568(_){function y(e,t){return e.has(t)}_.exports=y},
+65650(_,y,e){var t=e(78942),i=t.Symbol;_.exports=i},
+66040(_,y,e){var t=e(51187),i=1/0;function r(a){if(typeof a=="string"||t(a))return a;var s=a+"";return s=="0"&&1/a==-i?"-0":s}_.exports=r},
+67267(_,y,e){var t=e(41580);function i(r){return r===r&&!t(r)}_.exports=i},
+68250(_,y,e){var t=e(49753),i=e(5681),r=e(80088),a=e(54732),s=e(59068);function n(o){var c=-1,v=o==null?0:o.length;for(this.clear();++c-1&&i%1==0&&iQ,NB:()=>I,OP:()=>w,OQ:()=>M,Sc:()=>V,Tf:()=>y,Wx:()=>m,X2:()=>N,_j:()=>C,cs:()=>ot,dp:()=>W,lw:()=>r,nO:()=>D});var m=(function(d){return d.Year="Year",d.Month="Month",d.Week="Week",d.Day="Day",d.Hour="Hour",d.Minute="Minute",d.Second="Second",d})({}),y=(function(d){return d.Alphabetic="alphabetic",d.Top="top",d.Hanging="hanging",d.Middle="middle",d.Ideographic="ideographic",d.Bottom="bottom",d})({}),D=(function(d){return d.Start="start",d.End="end",d.Center="center",d.Left="left",d.Right="right",d})({}),M=(function(d){return d.All="All",d.Marks="Marks",d.Resize="Resize",d})({}),C=(function(d){return d.White="#ffffff",d.Black="#000000",d.Gray="#2d2d2d",d.CandleRed="#E0294A",d.CandleGreen="#2EBD85",d.CandleBlue="#0303F7",d.LightGray="#9a9b9a",d.GrayD="#D8D8D8",d.GrayF="#F2F2F2",d.Gray8="#848E9C",d.GrayE="#EAECEF",d.GrayC="#C1C6CD",d.YellowF="#F0B90B",d.Dark4="#48515D",d.Dark2="#212833",d.Dark2A="#2A2F37",d.Dark1F="#1F2124",d.Blue3="#328DFD",d.Gray0="#FFFFFF",d.Gray5="#FAFAFA",d.Gray10="#F5F5F5",d.Gray50="#E6E8EA",d.Gray100="#CACED3",d.Gray200="#AEB4BC",d.Gray300="#929AA5",d.Gray400="#76808F",d.Gray500="#5E6673",d.Gray600="#474D57",d.Gray700="#2B2F36",d.Gray800="#1E2026",d.Gray900="#14151A",d.Gray1000="#0B0E11",d.Red5="#FEF6F7",d.Red10="#FEF1F2",d.Red50="#FDDDDE",d.Red100="#FDBAB9",d.Red200="#FC9897",d.Red300="#FC6E75",d.Red400="#F84960",d.Red500="#D9304E",d.Red600="#982A42",d.Red700="#5C1929",d.Red800="#35141D",d.Red900="#221113",d.Green5="#F0FFFA",d.Green10="#E2FDF4",d.Green50="#CBF6E8",d.Green100="#9BE8CD",d.Green200="#54E8B4",d.Green300="#2ED191",d.Green400="#02C076",d.Green500="#0F8F62",d.Green600="#1E644E",d.Green700="#143D31",d.Green800="#102821",d.Green900="#0B1915",d.LineColor1="#ff00ff",d.LineColor2="#cc0066",d.LineColor3="#00cccc",d.LineColor4="#2ED191",d.LineColor5="#bd930b",d.LineColor6="#c305c6",d.AreaFillColor1="#e5e5f2",d.AreaFillColor2="#161833",d})({}),r=(function(d){return d.Normal="NORMAL",d.Percentage="PERCENTAGE",d})({}),w=(function(d){return d.Up="UP",d.Down="DOWN",d.Right="RIGHT",d.Left="LEFT",d})({}),I=(function(d){return d.Line="LINE",d.SegmentedDotLine="SEGMENTED_DOT_LINE",d.Bar="BAR",d.BarCandle="BAR_CANDLE",d.CandleBar="CANDLEBAR",d.Area="AREA",d.GradientArea="GRADIENTAREA",d.TimeGradientArea="TIMEGRADIENTAREA",d.Point="POINT",d.CrossLine="CROSSLINE",d.Transcation="TRANSCATION",d.HollowCandleBar="HOLLOW_CANDLEBAR",d.HollowBar="HOLLOW_BAR",d})({}),W=(function(d){return d.Width="WIDTH",d.Height="HIGHT",d.All="ALL",d})({}),N=(function(d){return d.DefaultCursor="DEFAULTCURSOR",d.CrossHairCursor="CROSSHAIRCURSOR",d.TrendLine="TRENDLINE",d.HorizontalExtendedLine="HROZONTAL_EXTENDED_LINE",d.ExtendTrendLine="EXTRENDLINE",d.FibonacciLine="FIBONACCILINE",d.Measurement="MEASUREMENT",d.InfoLine="INFOLINE",d.HorizontalLine="HORIZONTALLINE",d.VerticalLine="VERTICALLINE",d.CrossLine="CROSSLINE",d.ParallelLine="PARALLELLINE",d.UpArrow="UPARROW",d.DownArrow="DOWNARROW",d.LeftArrow="LEFTARROW",d.RightArrow="RIGHTARROW",d.Text="TEXT",d.Rectangle="RECTANGLE",d.RayLine="RAYLINE",d.PriceLine="PRICELINE",d.ArrowLine="ARROWLINE",d.OpenOrders="OPEN_ORDERS",d.LabelLines="LABELLINES",d.SingleLabelLines="SINGLE_LABEL_LINES",d.Alerts="ALERTS",d.Positions="POSITIONS",d.InformationMines="INFORMATION_MINES",d.ExecutionOrders="EXECUTION_ORDERS",d.SmartMoneySignals="SMART_MONEY_SIGNALS",d.SmartMoneyPositions="SMART_MONEY_POSITIONS",d.Delete="DELETE",d.KeepDrawing="KeepDrawing",d.LockDraw="LOCKDRAW",d.ToggleVisible="TOGGLEVISIBLE",d.Empty="Empty",d.Magnet="Magnet",d.LegacyArrow="ARROW",d.LegacyStraightLine="STRAIGHTLINE",d})({}),V=(function(d){return d.SOLID="solid",d.DASHED="dashed",d.DOTTED="dotted",d.DASHED_2="dashed_2",d})({}),Q=(function(d){return d.BOLD="bold",d.NORMAL="NORMAL",d})({}),ot=(function(d){return d.EDIT="EDIT",d.ADD="ADD",d.REMOVE="REMOVE",d})({})},
+73788(O){function p(i,b,u,g){for(var a=-1,f=i==null?0:i.length;++ae,b:()=>A});const A=f=>({...f}),e=({customSettings:f,widget:g})=>{f&&f.activeChartIndex!==void 0&&g.setActiveChart(f.activeChartIndex)}},
+75853(_,y,e){_=e.nmd(_);var t=e(78942),i=e(34772),r=y&&!y.nodeType&&y,a=r&&!0&&_&&!_.nodeType&&_,s=a&&a.exports===r,n=s?t.Buffer:void 0,o=n?n.isBuffer:void 0,c=o||i;_.exports=c},
+75857(d,g,a){var i=a(90313);function l(s){var u=s==null?0:s.length;return u?i(s,1):[]}d.exports=l},
+75964(_,y,e){var t=e(39747),i=e(74784),r=e(84882);function a(s){return typeof s.constructor=="function"&&!r(s)?t(i(s)):{}}_.exports=a},
+76853(_,y,e){var t=e(69011),i=500;function r(a){var s=t(a,function(o){return n.size===i&&n.clear(),o}),n=s.cache;return s}_.exports=r},
+77135(_,y,e){"use strict";e.d(y,{_:()=>s});var t=e(94889);function i(n,o){var c=n==null?null:typeof Symbol<"u"&&n[Symbol.iterator]||n["@@iterator"];if(c!=null){var v=[],g=!0,S=!1,d,A;try{for(c=c.call(n);!(g=(d=c.next()).done)&&(v.push(d.value),!(o&&v.length===o));g=!0);}catch(C){S=!0,A=C}finally{try{!g&&c.return!=null&&c.return()}finally{if(S)throw A}}return v}}var r=e(80686),a=e(61875);function s(n,o){return(0,t._)(n)||i(n,o)||(0,a._)(n,o)||(0,r._)()}},
+77848(_,y,e){var t=e(23945),i=e(74535);function r(a){return t(function(s,n){var o=-1,c=n.length,v=c>1?n[c-1]:void 0,g=c>2?n[2]:void 0;for(v=a.length>3&&typeof v=="function"?(c--,v):void 0,g&&i(n[0],n[1],g)&&(v=c<3?void 0:v,c=1),s=Object(s);++ot});function t(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
+In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}},
+80777(O,p,i){var b=i(28486),u=i(80547),g="[object Map]";function a(f){return u(f)&&b(f)==g}O.exports=a},
+80796(_,y,e){var t=e(47403),i=e(3316),r=i(t);_.exports=r},
+81211(_,y,e){var t=e(10358),i=e(195),r=e(36529);function a(s){return r(s)?t(s):i(s)}_.exports=a},
+81935(O,p,i){var b=i(28486),u=i(80547),g="[object Set]";function a(f){return u(f)&&b(f)==g}O.exports=a},
+82226(O,p,i){var b=i(84570);function u(g,a,f,_){return b(g,function(h,T,d){a(_,h,f(h),d)}),_}O.exports=u},
+82438(v,h,e){var t=e(15630);function o(r){return r&&r.length?t(r):[]}v.exports=o},
+82451(_,y,e){var t=e(40091),i=e(77848),r=i(function(a,s,n){t(a,s,n)});_.exports=r},
+82619(_){function y(e){return function(){return e}}_.exports=y},
+83546(O,p,i){var b=i(39032),u=i(77979);function g(a,f){return b(a,u(a),f)}O.exports=g},
+83943(O,p,i){var b=i(81935),u=i(49460),g=i(2306),a=g&&g.isSet,f=a?u(a):b;O.exports=f},
+84354(_){function y(e,t){var i=-1,r=e.length;for(t||(t=Array(r));++im});var m=(function(y){return y.MA="@MA",y.EMA="@EMA",y.WMA="@WMA",y.BOLL="@BOLL",y.VWAP="@VWAP",y.AVL="@AVL",y.TRIX="@TRIX",y.SAR="@SAR",y.SUPERTREND="@SUPERTREND",y.VOL="@VOL",y.MACD="@MACD",y.RSI="@RSI",y.KDJ="@KDJ",y.OBV="@OBV",y.CCI="@CCI",y.StochRSI="@Stoch RSI",y.WR="@Wm %R",y.DMI="@DMI",y.MTM="@MTM",y.EMV="@EMV",y.ATR="@ATR",y.OI_UM="@OI-UM",y.OI_CM="@OI-CM",y.LS_ACCO="@LS-Acco",y.LS_POSIT="@LS-Posit",y.LS_RATIO="@LS-Ratio",y.BS_VOL="@BSVol",y.BASIS="@Basis",y.MFI="@MFI",y.FundingRate="@FundingRate",y})({})},
+89413(_,y,e){var t=e(94715),i=e(78942),r=t(i,"Promise");_.exports=r},
+89624(_,y,e){var t=e(93655),i=e(64759),r=e(41580),a=e(64066),s=/[\\^$.*+?()[\]{}|]/g,n=/^\[object .+?Constructor\]$/,o=Function.prototype,c=Object.prototype,v=o.toString,g=c.hasOwnProperty,S=RegExp("^"+v.call(g).replace(s,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");function d(A){if(!r(A)||i(A))return!1;var C=t(A)?S:n;return C.test(a(A))}_.exports=d},
+90313(_,y,e){var t=e(11129),i=e(20714);function r(a,s,n,o,c){var v=-1,g=a.length;for(n||(n=i),c||(c=[]);++v0&&n(S)?s>1?r(S,s-1,n,o,c):t(c,S):o||(c[c.length]=S)}return c}_.exports=r},
+90353(_,y,e){var t=e(51340),i=e(33934),r=e(88861),a=e(31182),s=e(28486),n=e(53142),o=e(75853),c=e(8666),v=1,g="[object Arguments]",S="[object Array]",d="[object Object]",A=Object.prototype,C=A.hasOwnProperty;function N(b,f,p,u,h,l){var E=n(b),m=n(f),D=E?S:s(b),O=m?S:s(f);D=D==g?d:D,O=O==g?d:O;var w=D==d,J=O==d,j=D==O;if(j&&o(b)){if(!o(f))return!1;E=!0,w=!1}if(j&&!w)return l||(l=new t),E||c(b)?i(b,f,p,u,h,l):r(b,f,D,p,u,h,l);if(!(p&v)){var W=w&&C.call(b,"__wrapped__"),V=J&&C.call(f,"__wrapped__");if(W||V){var B=W?b.value():b,H=V?f.value():f;return l||(l=new t),h(B,H,p,u,l)}}return j?(l||(l=new t),a(b,f,p,u,h,l)):!1}_.exports=N},
+92264(_,y,e){var t=e(58898);function i(r,a){var s=a?t(r.buffer):r.buffer;return new r.constructor(s,r.byteOffset,r.length)}_.exports=i},
+92480(O,p,i){var b=i(58898);function u(g,a){var f=a?b(g.buffer):g.buffer;return new g.constructor(f,g.byteOffset,g.byteLength)}O.exports=u},
+93526(_,y,e){var t=e(53142),i=e(65187),r=e(96493),a=e(95243);function s(n,o){return t(n)?n:i(n,o)?[n]:r(a(n))}_.exports=s},
+93650(O,p,i){var b=i(48244),u=i(85832),g=i(25288);function a(f){return b(f,g,u)}O.exports=a},
+93655(_,y,e){var t=e(87379),i=e(41580),r="[object AsyncFunction]",a="[object Function]",s="[object GeneratorFunction]",n="[object Proxy]";function o(c){if(!i(c))return!1;var v=t(c);return v==a||v==s||v==r||v==n}_.exports=o},
+94497(_,y,e){var t=e(94715),i=t(Object,"create");_.exports=i},
+94652(_,y,e){"use strict";e.d(y,{F3:()=>o,GX:()=>c,Lm:()=>s,ZE:()=>n});var t=e(96830),i="chart.spot",r="chart.futures",a="chart.delivery",s="chart.common",n=(0,t.W)()?"chart_eu_futures":"chart_futures",o="chart_delivery",c="chart_shared"},
+94687(_,y,e){var t=e(90353),i=e(80547);function r(a,s,n,o,c){return a===s?!0:a==null||s==null||!i(a)&&!i(s)?a!==a&&s!==s:t(a,s,n,o,r,c)}_.exports=r},
+94715(_,y,e){var t=e(89624),i=e(20155);function r(a,s){var n=i(a,s);return t(n)?n:void 0}_.exports=r},
+94889(_,y,e){"use strict";e.d(y,{_:()=>t});function t(i){if(Array.isArray(i))return i}},
+95243(_,y,e){var t=e(52291);function i(r){return r==null?"":t(r)}_.exports=i},
+95894(_){function y(e){var t=-1,i=Array(e.size);return e.forEach(function(r,a){i[++t]=[a,r]}),i}_.exports=y},
+96493(_,y,e){var t=e(76853),i=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,r=/\\(\\)?/g,a=t(function(s){var n=[];return s.charCodeAt(0)===46&&n.push(""),s.replace(i,function(o,c,v,g){n.push(v?g.replace(r,"$1"):c||o)}),n});_.exports=a},
+96638(_,y,e){var t=e(1386),i=e(19770),r=e(68250),a=200;function s(n,o){var c=this.__data__;if(c instanceof t){var v=c.__data__;if(!i||v.lengtht});var t=function(){var i=arguments.length>0&&arguments[0]!==void 0?arguments[0]:window.location.pathname,r=i.includes("/eu-futures");return r}},
+97034(_,y,e){var t=e(46285);function i(r,a){for(var s=r.length;s--;)if(t(r[s][0],a))return s;return-1}_.exports=i},
+97350(O){var p=Object.prototype,i=p.hasOwnProperty;function b(u){var g=u.length,a=new u.constructor(g);return g&&typeof u[0]=="string"&&i.call(u,"index")&&(a.index=u.index,a.input=u.input),a}O.exports=b},
+97706(v){function h(e,t,o){for(var r=o-1,n=e.length;++r/^(LineTool)/g.test(t);
+const _t=t=>`${t}.customSettings`;
+const ft=t=>`${t}.layout`;
+const J=t=>t.chartId;
+const kt=({save:t})=>{const n={},s=[],i=t.charts.reduce((I,N)=>{const _=N.panes.reduce((m,y)=>{const R=y.sources.find(h=>h.type==="MainSeries");R?.state.symbol&&(n[R?.state.symbol.toUpperCase()]=[]);const[C,Y]=it()(y.sources,h=>W(h.type));return s.push(...C),m.push({...y,sources:Y}),m},[]);return I.push({...N,panes:_}),I},[]),l=E()(s,I=>I.id),u=j()(l,I=>I.state.symbol),d={...n,...u};return{charts:i,drawings:d}};
+const wt=async({storage:t,key:n,charts:s})=>{const i=ft(n),l=await t.getItem(i)||[],u=Q()(l,J),d=Q()(s,J),I=l.map(J),N=s.map(J),y=ut()([...I,...N]).map(R=>d[R]||u[R]).sort((R,C)=>+R.chartId-+C.chartId);return await t.setItem(i,y),y};
+const xt=async({storage:t,save:n,key:s,widget:i})=>{if(!i)return{chartSave:n,drawingsSave:{}};const{drawings:l,charts:u}=kt({save:n}),d={...n,charts:u};return await t.setItem(s,d),await wt({storage:t,key:s,charts:u}),await Promise.all(Object.keys(l).map(I=>t.setItem(`#TV_SYMBOL-${I}`,l[I]))),{chartSave:d,drawingsSave:l}};
+const Wt=async({storage:t,key:n,widget:s})=>{if(!s)return(0,O.b)({activeChartIndex:0});const i=_t(n),l=s.activeChartIndex(),u=(0,O.b)({activeChartIndex:l});return await t.setItem(i,u),u};
+const zt=async({storage:t,key:n})=>{const l=(await t.keys()).filter(h=>/^(#TV_SYMBOL-)/g.test(h)).map(async h=>t.getItem(h)),u=await Promise.all(l),d=rt()(u).filter(h=>!!h),I=j()(d,h=>h.ownerSource),N=ft(n),_=_t(n),m=await t.getItem(n),y=await t.getItem(_);if(!m)return{chartSave:m,customSettings:y};const C=(await t.getItem(N)||m.charts).reduce((h,H)=>{const V=H.panes.reduce((D,M)=>{const z=ot()(M.sources),q=I[M.mainSourceId]||[];return z.push(...q),D.push({...M,sources:z}),D},[]);return h.push({...H,panes:V}),h},[]);return{chartSave:{...m,charts:C},customSettings:y}};
+r.d(exports,{save:()=>xt,load:()=>zt,saveCustomSettings:()=>Wt,createSaveCallback:()=>createSaveCallback});
+function createSaveCallback(storage,key,widget,onSave){const et={current:storage},st={current:key},p={current:widget},t={initialConfig:{onSave}};return async c=>{const{chartSave:L}=await xt({storage:et.current,save:c,key:st.current,widget:p.current});await t.initialConfig.onSave?.(L)};}
+},
+"codex-chart-host-basic"(module,exports,a){
+var ot=a(46108);
+var d=a.n(ot);
+var A=a(22053);
+var R=a.n(A);
+var Tt=a(89220);
+var vt=a(72834);
+const lo=e=>e===null||e.length===0?null:e.length===1?e[0]:e.length===2?{p1:e[0],p2:e[1]}:{p1:e[0],p2:e[1],p3:e[2]};
+var Sa=a(79071);
+var Dn=a.n(Sa);
+var xc=a(43917);
+var bc=a.n(xc);
+var Sc=a(34789);
+var Do=a.n(Sc);
+var Ac=a(46155);
+var vc=a.n(Ac);
+var Cc=a(82451);
+var Qi=a.n(Cc);
+const Vo=[{id:"CANDLE",indicators:[{id:Tt.w.MA,categories:["main"]}]},{id:Tt.w.VOL,indicators:[{id:Tt.w.VOL,categories:["sub"]}]}];
+const Mo=()=>"candlestick-indicator-setting";
+const wc=()=>"candlestick-setting";
+const No=()=>"BASIC_KLINE_INDICATOR";
+const Eo=()=>"BASIC_KLINE_COMPARE_SYMBOL_SETTINGS";
+const qi="BASIC_KLINE_";
+const Rn=e=>`${qi}${e}`;
+const Tc=()=>"original-candlestick";
+const Bo=e=>e.startsWith(qi)&&e.slice(qi.length).trim().startsWith("{");
+function Ro(e){if(!e.trim().startsWith("{"))return null;try{const t=JSON.parse(e);if(!t||typeof t!="object"||Array.isArray(t))return null;const s=t.pair,i=t.contractType;if(s!=null&&i!=null)return String(i).toUpperCase()==="PERPETUAL"?String(s):`${s}_${i}`;const o=t.base,l=t.quote;return o!=null&&l!=null?`${o}${l}`:null}catch{return null}}
+async function Lc(e){const t=await e.keys();for(const s of t){if(!Bo(s))continue;const i=Ro(s.slice(qi.length));if(i==null)continue;const o=Rn(i),l=await e.getItem(s);l!=null&&await e.setItem(o,l)}}
+const Ic=({type:e,lineType:t,dir:s})=>{switch(e){case vt.X2.LegacyArrow:return s||vt.X2.LeftArrow;case vt.X2.LegacyStraightLine:return t||vt.X2.HorizontalLine;default:return e}};
+const _o=e=>{const t=d()(e);for(const i of t??[])i.id=i.id??i.type;const s=t?.find(i=>(i.id??i.type)==="CANDLE");if(s){const i=s.annotations.map(({type:o=vt.X2.Empty,lineType:l,dir:u,...f})=>({type:Ic({type:o,lineType:l,dir:u}),...f}));s.annotations=i}return t};
+const ko={toStored:R(),fromStored:R()};
+const Pc=(e,t,s)=>{const i=o=>({...o,time:s(o.time,t)});return e==null?e:Array.isArray(e)?e.map(o=>o&&i(o)):"time"in e?i(e):vc()(e,o=>o&&i(o))};
+const Ho=(e,t,s)=>e.map(i=>({...i,annotations:i.annotations.map(o=>({...o,data:Pc(o.data,t,s)}))}));
+const Wo=({type:e,namespace:t,symbol:s,saveIndicators:i,saveIndicatorSettings:o,saveAnnotations:l,saveSettings:u,saveAnnotationConfig:f,saveCompareSymbolSettings:S,load:x,clear:_})=>{const $={type:e,symbol:s};if(!t)return{info:$,saveAnnotations:()=>Promise.resolve(),saveIndicators:()=>Promise.resolve(),saveIndicatorSettings:()=>Promise.resolve(),saveSettings:()=>Promise.resolve(),saveAnnotationConfig:()=>Promise.resolve(),saveCompareSymbolSettings:()=>Promise.resolve(),load:()=>Promise.resolve({storeData:[],indicatorSetting:null,setting:null,annotationConfig:null,compareSymbolSettings:null}),clear:Dn()};const tt={indicatorStoreKey:No(),annotationsStoreKey:Rn(s),indicatorSettingStoreKey:Mo(),settingStoreKey:wc(),annotationConfigStoreKey:Tc(),compareSymbolSettingsStoreKey:Eo()},ht=bc().createInstance({name:t});return{info:$,saveAnnotations:async(gt,{interval:Mt,annotationTime:Ut=ko})=>{const _t=Ho(gt,Mt,Ut.toStored);await l({...tt,payload:_t,storage:ht,interval:Mt})},saveIndicators:async gt=>{await i({...tt,payload:gt,storage:ht})},saveIndicatorSettings:async gt=>{await o({...tt,payload:gt,storage:ht})},saveSettings:async gt=>{await u({...tt,payload:gt,storage:ht})},saveAnnotationConfig:async gt=>{await f({...tt,payload:gt,storage:ht})},saveCompareSymbolSettings:async gt=>{await S({...tt,payload:gt,storage:ht})},load:async({interval:gt,annotationTime:Mt=ko})=>{try{const Ut=await x({...tt,storage:ht,interval:gt}),{indicators:_t,annotations:zt,indicatorSetting:Gt,setting:ae,annotationConfig:ge,compareSymbolSettings:de}=Ut,Ee=zt&&Ho(zt,gt,Mt.fromStored),Ue=_t||Vo,Ne=Ee||[],ve=Do()(Ue,ts=>ts.id||ts.type),Ie=Do()(Ne,ts=>ts.id||ts.type),je=Qi()({},ve,Ie);return{storeData:Object.values(je),indicatorSetting:Gt,setting:ae,annotationConfig:ge,compareSymbolSettings:de}}catch(Ut){return console.warn("[candlestick] load saving error: ",Ut),{storeData:[],indicatorSetting:null,setting:null,annotationConfig:null,compareSymbolSettings:null}}},clear:()=>_({storage:ht})}};
+const Dc=({namespace:e,symbol:t})=>Wo({type:"local",namespace:e,symbol:t,saveAnnotations:async({payload:s,annotationsStoreKey:i,storage:o})=>{const l=s.map(u=>({...u,annotations:u.annotations.map(f=>({...f,data:lo(f.data)}))}));await o.setItem(i,l)},saveIndicators:async({payload:s,indicatorStoreKey:i,storage:o})=>{await o.setItem(i,s)},saveIndicatorSettings:async({payload:s,indicatorSettingStoreKey:i,storage:o})=>{await o.setItem(i,s)},saveSettings:async({payload:s,settingStoreKey:i,storage:o})=>{const{"@colorTable":l,"@watermark":u,...f}=s;await o.setItem(i,f)},saveAnnotationConfig:async({payload:s,annotationConfigStoreKey:i,storage:o})=>{await o.setItem(i,s)},saveCompareSymbolSettings:async({payload:s,compareSymbolSettingsStoreKey:i,storage:o})=>{await o.setItem(i,s)},load:async({storage:s,indicatorStoreKey:i,annotationsStoreKey:o,indicatorSettingStoreKey:l,settingStoreKey:u,annotationConfigStoreKey:f,compareSymbolSettingsStoreKey:S})=>{let x=await s.getItem(o);x==null&&(await Lc(s),x=await s.getItem(o));const[_,$,tt,ht,gt]=await Promise.all([s.getItem(i),s.getItem(l),s.getItem(u),s.getItem(f),s.getItem(S)]),Mt=_o(x||[]);return{indicators:_,annotations:Mt,indicatorSetting:$,setting:tt,annotationConfig:ht,compareSymbolSettings:gt}},clear:({storage:s})=>s.clear()});
+a.d(exports,{create:()=>Dc});
+},
+"codex-chart-host-mirror"(module,exports,e){
+var o=e(49253);
+var v=e(77135);
+var i=e(42288);
+var M=e(43917);
+var h=e.n(M);
+var V=e(7980);
+var U=e.n(V);
+var Qe=e(94652);
+var Tn=[Qe.ZE,Qe.F3];
+e.d(exports,{create:()=>createMirror});
+function createMirror(namespace){const a=namespace,Cr={current:false},_r={current:true};return (0,o._)(function(){var De,ze,qe,be,vr,fr,er;return(0,i._)(this,function(nr){switch(nr.label){case 0:if(Cr.current||!_r.current)return[2,Promise.resolve()];nr.label=1;case 1:return nr.trys.push([1,5,,6]),De=h().createInstance({name:a}),[4,De.keys()];case 2:return ze=nr.sent(),qe=ze.filter(function(mr){return/^((#TV_SYMBOL-)|(myTradingView))/g.test(mr)}),be=qe.map((function(){var mr=(0,o._)(function(ar){return(0,i._)(this,function(Sr){return[2,De.getItem(ar)]})});return function(ar){return mr.apply(this,arguments)}})()),[4,Promise.all(be)];case 3:return vr=nr.sent(),fr=U()(qe,vr),[4,Promise.all(Tn.map((function(){var mr=(0,o._)(function(ar){var Sr,Wr;return(0,i._)(this,function(Gr){switch(Gr.label){case 0:return a===ar?[2,Promise.resolve()]:(Sr=h().createInstance({name:ar}),Wr=fr.map((function(){var Sn=(0,o._)(function(Lt){var _t,Ot,At;return(0,i._)(this,function(Mn){return _t=(0,v._)(Lt,2),Ot=_t[0],At=_t[1],Ot?[2,Sr.setItem(Ot,At)]:[2,Promise.resolve()]})});return function(Lt){return Sn.apply(this,arguments)}})()),[4,Promise.all(Wr)]);case 1:return Gr.sent(),[2,Promise.resolve()]}})});return function(ar){return mr.apply(this,arguments)}})()))];case 4:return nr.sent(),[2,Promise.resolve()];case 5:return er=nr.sent(),console.warn(typeof er=="string"?er:er.message),[2,Promise.resolve()];case 6:return[2]}})});}
+}}]);
diff --git a/test/fixtures/binance-chart-storage/host-persistence.md b/test/fixtures/binance-chart-storage/host-persistence.md
new file mode 100644
index 0000000..93a4ce5
--- /dev/null
+++ b/test/fixtures/binance-chart-storage/host-persistence.md
@@ -0,0 +1,70 @@
+# Public host persistence fixture
+
+`host-persistence.js` executes the original Binance storage business functions
+through the existing captured Rspack runtime and localForage module. It does not
+reimplement lodash, the chart save algorithm, Basic storage, or mirroring.
+
+`host-persistence-manifest.json` records each complete source file's public URL,
+SHA-256 and byte count. Every copied declaration, callback and dependency factory
+has its original UTF-16 range, the corresponding generated-fixture range and the
+SHA-256 of the exact UTF-8-encoded slice. The generated registration wrapper and
+each added `var`/`const` declaration delimiter sit outside those original ranges.
+The browser helper checks the whole fixture and all fragment hashes before use.
+
+The source inputs were obtained on 2026-10-06. The original investigation supplied
+`main.37a180b9.js`, `28928.9aeec892.js`, `36648.dd8ae7dc.js` and
+`TradingView.99bc5074.js`. A separate anonymous Chrome visit to the public USUSDT
+page identified the missing lodash factories in chunks `40784`, `45485`, `16873`
+and `56679`. Only those public JS responses were downloaded; extraction retains
+only the required factories. No login, existing browser profile, storage values
+or trading actions were used. Both anonymous browsers were closed in `finally`.
+The current page selected Basic by default; selecting Trading View was unnecessary
+to obtain these dependencies. A successful module observation is not UI evidence.
+
+Regenerate from directories containing these public response bodies:
+
+```sh
+node test/fixtures/binance-chart-storage/host-persistence-extract.js \
+ /path/to/original-public-chunks /path/to/additional-public-chunks
+```
+
+Acorn selects the host functions and recursively collects their original lexical
+declarations. The extractor then collects original webpack dependency factories.
+Missing declarations or dependencies are errors; no replacement implementation is
+inserted. It does not download sources or read a browser profile.
+
+## Executed contracts
+
+- TradingView module `76535`: `kt`, `wt`, `xt`, `zt`, `Wt`, key helpers and the
+ original `Tt` save callback. The test supplies storage, key, widget and onSave
+ references to that unchanged callback. `onSave` is the original mirror callback.
+- Basic module `15426`: `Wo`, `Dc`, default indicator data, key constructors,
+ annotations conversion and the actual `Lc` old-key migration. Tests supply JSON
+ data at this storage boundary. The upstream `Ec` React subscriptions, debounce,
+ loading guard and JSON serialization are not executed by this fixture.
+- TradingView mirror: the original compiled async callback, original SWC helpers,
+ namespace constants and original lodash dependencies. The wrapper supplies the
+ requested namespace and native enabled-state refs (`Cr.current=false`,
+ `_r.current=true`). It does not simulate React lifecycle changes to those refs.
+- The widget boundary implements only the `activeChartIndex()` call used by these
+ storage functions. Chart rendering and the full TradingView widget are absent.
+
+All scenarios run independently against native localForage and the adapter, using
+fresh Chromium contexts, the same original functions and real IndexedDB. They
+cover staged saves, the cross-database mirror, layout retention, restored drawings,
+removing a symbol's last drawing, all six Basic keys, old-key migration and shared
+store clearing. The save/mirror measurement excludes setup and final verification.
+It measures transaction/request counts, not wall-clock speed or blank-page recovery.
+
+The concurrent clear scenario records a real behavioral difference. In the
+captured localForage IndexedDB driver, `setItem` creates its transaction in a
+second `ready().then(...).then(...)` stage, while `clear` creates its transaction
+in the first `ready().then(...)` stage. Simultaneously invoked sets can therefore
+commit after a later-invoked clear. The adapter instead enforces issue order.
+The shared-clear conformance scenario awaits existing saves before clearing;
+the separate concurrency scenario explicitly asserts each implementation's
+different result. Neither result establishes atomic behavior across databases.
+
+These offline tests do not establish production injection, existing-profile data
+compatibility, UI rendering, safety of stopping mounted callers, fallback-driver
+behavior, cross-tab lost-update protection or cross-database atomicity.
diff --git a/test/fixtures/binance-chart-storage/localforage-module.js b/test/fixtures/binance-chart-storage/localforage-module.js
new file mode 100644
index 0000000..15e179c
--- /dev/null
+++ b/test/fixtures/binance-chart-storage/localforage-module.js
@@ -0,0 +1,10 @@
+/* Test-only extraction from Binance main.37a180b9.js; see manifest.json. */
+(self.webpackChunkfutures_trade_ui = self.webpackChunkfutures_trade_ui || []).push([
+ ["codex-chart-storage-fixture"],
+ {43917(_,y,e){/*!
+ localForage -- Offline Storage, Improved
+ Version 1.10.0
+ https://localforage.github.io/localForage
+ (c) 2013-2017 Mozilla, Apache License 2.0
+*/(function(t){if(1)_.exports=t();else var i})(function(){var t,i,r;return(function a(s,n,o){function c(S,d){if(!n[S]){if(!s[S]){var A=void 0;if(!d&&A)return require(S,!0);if(v)return v(S,!0);var C=new Error("Cannot find module '"+S+"'");throw C.code="MODULE_NOT_FOUND",C}var N=n[S]={exports:{}};s[S][0].call(N.exports,function(b){var f=s[S][1][b];return c(f||b)},N,N.exports,a,s,n,o)}return n[S].exports}for(var v=void 0,g=0;g"u"&&a(3);var A=Promise;function C(pe,He){He&&pe.then(function(Ae){He(null,Ae)},function(Ae){He(Ae)})}function N(pe,He,Ae){typeof He=="function"&&pe.then(He),typeof Ae=="function"&&pe.catch(Ae)}function b(pe){return typeof pe!="string"&&(console.warn(pe+" used as a key, but it is not a string."),pe=String(pe)),pe}function f(){if(arguments.length&&typeof arguments[arguments.length-1]=="function")return arguments[arguments.length-1]}var p="local-forage-detect-blob-support",u=void 0,h={},l=Object.prototype.toString,E="readonly",m="readwrite";function D(pe){for(var He=pe.length,Ae=new ArrayBuffer(He),Je=new Uint8Array(Ae),Ze=0;Ze=43)}}).catch(function(){return!1})}function w(pe){return typeof u=="boolean"?A.resolve(u):O(pe).then(function(He){return u=He,u})}function J(pe){var He=h[pe.name],Ae={};Ae.promise=new A(function(Je,Ze){Ae.resolve=Je,Ae.reject=Ze}),He.deferredOperations.push(Ae),He.dbReady?He.dbReady=He.dbReady.then(function(){return Ae.promise}):He.dbReady=Ae.promise}function j(pe){var He=h[pe.name],Ae=He.deferredOperations.pop();if(Ae)return Ae.resolve(),Ae.promise}function W(pe,He){var Ae=h[pe.name],Je=Ae.deferredOperations.pop();if(Je)return Je.reject(He),Je.promise}function V(pe,He){return new A(function(Ae,Je){if(h[pe.name]=h[pe.name]||z(),pe.db)if(He)J(pe),pe.db.close();else return Ae(pe.db);var Ze=[pe.name];He&&Ze.push(pe.version);var Ke=g.open.apply(g,Ze);He&&(Ke.onupgradeneeded=function(dt){var Rt=Ke.result;try{Rt.createObjectStore(pe.storeName),dt.oldVersion<=1&&Rt.createObjectStore(p)}catch(Lt){if(Lt.name==="ConstraintError")console.warn('The database "'+pe.name+'" has been upgraded from version '+dt.oldVersion+" to version "+dt.newVersion+', but the storage "'+pe.storeName+'" already exists.');else throw Lt}}),Ke.onerror=function(dt){dt.preventDefault(),Je(Ke.error)},Ke.onsuccess=function(){var dt=Ke.result;dt.onversionchange=function(Rt){Rt.target.close()},Ae(dt),j(pe)}})}function B(pe){return V(pe,!1)}function H(pe){return V(pe,!0)}function Z(pe,He){if(!pe.db)return!0;var Ae=!pe.db.objectStoreNames.contains(pe.storeName),Je=pe.versionpe.db.version;if(Je&&(pe.version!==He&&console.warn('The database "'+pe.name+`" can't be downgraded from version `+pe.db.version+" to version "+pe.version+"."),pe.version=pe.db.version),Ze||Ae){if(Ae){var Ke=pe.db.version+1;Ke>pe.version&&(pe.version=Ke)}return!0}return!1}function Te(pe){return new A(function(He,Ae){var Je=new FileReader;Je.onerror=Ae,Je.onloadend=function(Ze){var Ke=btoa(Ze.target.result||"");He({__local_forage_encoded_blob:!0,data:Ke,type:pe.type})},Je.readAsBinaryString(pe)})}function ve(pe){var He=D(atob(pe.data));return d([He],{type:pe.type})}function le(pe){return pe&&pe.__local_forage_encoded_blob}function Ce(pe){var He=this,Ae=He._initReady().then(function(){var Je=h[He._dbInfo.name];if(Je&&Je.dbReady)return Je.dbReady});return N(Ae,pe,pe),Ae}function se(pe){J(pe);for(var He=h[pe.name],Ae=He.forages,Je=0;Je0&&(!pe.db||Ke.name==="InvalidStateError"||Ke.name==="NotFoundError"))return A.resolve().then(function(){if(!pe.db||Ke.name==="NotFoundError"&&!pe.db.objectStoreNames.contains(pe.storeName)&&pe.version<=pe.db.version)return pe.db&&(pe.version=pe.db.version+1),H(pe)}).then(function(){return se(pe).then(function(){ge(pe,He,Ae,Je-1)})}).catch(Ae);Ae(Ke)}}function z(){return{forages:[],db:null,dbReady:null,deferredOperations:[]}}function Le(pe){var He=this,Ae={db:null};if(pe)for(var Je in pe)Ae[Je]=pe[Je];var Ze=h[Ae.name];Ze||(Ze=z(),h[Ae.name]=Ze),Ze.forages.push(He),He._initReady||(He._initReady=He.ready,He.ready=Ce);var Ke=[];function dt(){return A.resolve()}for(var Rt=0;Rt>4,wt[Ze++]=(dt&15)<<4|Rt>>2,wt[Ze++]=(Rt&3)<<6|Lt&63;return _t}function fe(pe){var He=new Uint8Array(pe),Ae="",Je;for(Je=0;Je>2],Ae+=G[(He[Je]&3)<<4|He[Je+1]>>4],Ae+=G[(He[Je+1]&15)<<2|He[Je+2]>>6],Ae+=G[He[Je+2]&63];return He.length%3===2?Ae=Ae.substring(0,Ae.length-1)+"=":He.length%3===1&&(Ae=Ae.substring(0,Ae.length-2)+"=="),Ae}function je(pe,He){var Ae="";if(pe&&(Ae=_e.call(pe)),pe&&(Ae==="[object ArrayBuffer]"||pe.buffer&&_e.call(pe.buffer)==="[object ArrayBuffer]")){var Je,Ze=x;pe instanceof ArrayBuffer?(Je=pe,Ze+=ce):(Je=pe.buffer,Ae==="[object Int8Array]"?Ze+=we:Ae==="[object Uint8Array]"?Ze+=Be:Ae==="[object Uint8ClampedArray]"?Ze+=qe:Ae==="[object Int16Array]"?Ze+=rt:Ae==="[object Uint16Array]"?Ze+=q:Ae==="[object Int32Array]"?Ze+=ze:Ae==="[object Uint32Array]"?Ze+=de:Ae==="[object Float32Array]"?Ze+=ae:Ae==="[object Float64Array]"?Ze+=ee:He(new Error("Failed to get type for BinaryArray"))),He(Ze+fe(Je))}else if(Ae==="[object Blob]"){var Ke=new FileReader;Ke.onload=function(){var dt=R+pe.type+"~"+fe(this.result);He(x+Ee+dt)},Ke.readAsArrayBuffer(pe)}else try{He(JSON.stringify(pe))}catch(dt){console.error("Couldn't convert value into a JSON string: ",pe),He(null,dt)}}function Ve(pe){if(pe.substring(0,X)!==x)return JSON.parse(pe);var He=pe.substring(ye),Ae=pe.substring(X,ye),Je;if(Ae===Ee&&U.test(He)){var Ze=He.match(U);Je=Ze[1],He=He.substring(Ze[0].length)}var Ke=Fe(He);switch(Ae){case ce:return Ke;case Ee:return d([Ke],{type:Je});case we:return new Int8Array(Ke);case Be:return new Uint8Array(Ke);case qe:return new Uint8ClampedArray(Ke);case rt:return new Int16Array(Ke);case q:return new Uint16Array(Ke);case ze:return new Int32Array(Ke);case de:return new Uint32Array(Ke);case ae:return new Float32Array(Ke);case ee:return new Float64Array(Ke);default:throw new Error("Unkown type: "+Ae)}}var $e={serialize:je,deserialize:Ve,stringToBuffer:Fe,bufferToString:fe};function nt(pe,He,Ae,Je){pe.executeSql("CREATE TABLE IF NOT EXISTS "+He.storeName+" (id INTEGER PRIMARY KEY, key unique, value)",[],Ae,Je)}function Et(pe){var He=this,Ae={db:null};if(pe)for(var Je in pe)Ae[Je]=typeof pe[Je]!="string"?pe[Je].toString():pe[Je];var Ze=new A(function(Ke,dt){try{Ae.db=openDatabase(Ae.name,String(Ae.version),Ae.description,Ae.size)}catch(Rt){return dt(Rt)}Ae.db.transaction(function(Rt){nt(Rt,Ae,function(){He._dbInfo=Ae,Ke()},function(Lt,_t){dt(_t)})},dt)});return Ae.serializer=$e,Ze}function ht(pe,He,Ae,Je,Ze,Ke){pe.executeSql(Ae,Je,Ze,function(dt,Rt){Rt.code===Rt.SYNTAX_ERR?dt.executeSql("SELECT name FROM sqlite_master WHERE type='table' AND name = ?",[He.storeName],function(Lt,_t){_t.rows.length?Ke(Lt,Rt):nt(Lt,He,function(){Lt.executeSql(Ae,Je,Ze,Ke)},Ke)},Ke):Ke(dt,Rt)},Ke)}function ke(pe,He){var Ae=this;pe=b(pe);var Je=new A(function(Ze,Ke){Ae.ready().then(function(){var dt=Ae._dbInfo;dt.db.transaction(function(Rt){ht(Rt,dt,"SELECT * FROM "+dt.storeName+" WHERE key = ? LIMIT 1",[pe],function(Lt,_t){var wt=_t.rows.length?_t.rows.item(0).value:null;wt&&(wt=dt.serializer.deserialize(wt)),Ze(wt)},function(Lt,_t){Ke(_t)})})}).catch(Ke)});return C(Je,He),Je}function et(pe,He){var Ae=this,Je=new A(function(Ze,Ke){Ae.ready().then(function(){var dt=Ae._dbInfo;dt.db.transaction(function(Rt){ht(Rt,dt,"SELECT * FROM "+dt.storeName,[],function(Lt,_t){for(var wt=_t.rows,zt=wt.length,fr=0;fr0){dt(at.apply(Ze,[pe,Lt,Ae,Je-1]));return}Rt(fr)}})})}).catch(Rt)});return C(Ke,Ae),Ke}function it(pe,He,Ae){return at.apply(this,[pe,He,Ae,1])}function Ot(pe,He){var Ae=this;pe=b(pe);var Je=new A(function(Ze,Ke){Ae.ready().then(function(){var dt=Ae._dbInfo;dt.db.transaction(function(Rt){ht(Rt,dt,"DELETE FROM "+dt.storeName+" WHERE key = ?",[pe],function(){Ze()},function(Lt,_t){Ke(_t)})})}).catch(Ke)});return C(Je,He),Je}function yt(pe){var He=this,Ae=new A(function(Je,Ze){He.ready().then(function(){var Ke=He._dbInfo;Ke.db.transaction(function(dt){ht(dt,Ke,"DELETE FROM "+Ke.storeName,[],function(){Je()},function(Rt,Lt){Ze(Lt)})})}).catch(Ze)});return C(Ae,pe),Ae}function gt(pe){var He=this,Ae=new A(function(Je,Ze){He.ready().then(function(){var Ke=He._dbInfo;Ke.db.transaction(function(dt){ht(dt,Ke,"SELECT COUNT(key) as c FROM "+Ke.storeName,[],function(Rt,Lt){var _t=Lt.rows.item(0).c;Je(_t)},function(Rt,Lt){Ze(Lt)})})}).catch(Ze)});return C(Ae,pe),Ae}function Tt(pe,He){var Ae=this,Je=new A(function(Ze,Ke){Ae.ready().then(function(){var dt=Ae._dbInfo;dt.db.transaction(function(Rt){ht(Rt,dt,"SELECT key FROM "+dt.storeName+" WHERE id = ? LIMIT 1",[pe+1],function(Lt,_t){var wt=_t.rows.length?_t.rows.item(0).key:null;Ze(wt)},function(Lt,_t){Ke(_t)})})}).catch(Ke)});return C(Je,He),Je}function ct(pe){var He=this,Ae=new A(function(Je,Ze){He.ready().then(function(){var Ke=He._dbInfo;Ke.db.transaction(function(dt){ht(dt,Ke,"SELECT key FROM "+Ke.storeName,[],function(Rt,Lt){for(var _t=[],wt=0;wt '__WebKitDatabaseInfoTable__'",[],function(Ze,Ke){for(var dt=[],Rt=0;Rt0}function gr(pe){var He=this,Ae={};if(pe)for(var Je in pe)Ae[Je]=pe[Je];return Ae.keyPrefix=Gt(pe,He._defaultConfig),cr()?(He._dbInfo=Ae,Ae.serializer=$e,A.resolve()):A.reject()}function sr(pe){var He=this,Ae=He.ready().then(function(){for(var Je=He._dbInfo.keyPrefix,Ze=localStorage.length-1;Ze>=0;Ze--){var Ke=localStorage.key(Ze);Ke.indexOf(Je)===0&&localStorage.removeItem(Ke)}});return C(Ae,pe),Ae}function lr(pe,He){var Ae=this;pe=b(pe);var Je=Ae.ready().then(function(){var Ze=Ae._dbInfo,Ke=localStorage.getItem(Ze.keyPrefix+pe);return Ke&&(Ke=Ze.serializer.deserialize(Ke)),Ke});return C(Je,He),Je}function hr(pe,He){var Ae=this,Je=Ae.ready().then(function(){for(var Ze=Ae._dbInfo,Ke=Ze.keyPrefix,dt=Ke.length,Rt=localStorage.length,Lt=1,_t=0;_t