Skip to content

Commit fbc30fc

Browse files
authored
fix: retain data panel sessions across tab switches (#321)
1 parent dc1e167 commit fbc30fc

15 files changed

Lines changed: 1018 additions & 188 deletions

‎docs/userscript-validation.md‎

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ read the Node version from `.nvmrc`.
7070
scope, and available-balance parsing. Canonical gateway symbols retain the
7171
separate exact server contract in `src/shared/canonical-symbol.js`.
7272
- Async work must carry the symbol, route, and lifecycle identity that started
73-
it. A route change, symbol change, hidden/closed panel, or page teardown
74-
invalidates work before its result can render or update shared state.
73+
it. A route change, symbol change, panel close, or page teardown invalidates
74+
work before its result can render or update shared state. Follow each script's
75+
visibility contract: the two data panels below retain an activated session
76+
while hidden; visibility alone does not invalidate its requests.
7577
- Timer, observer, drag, and unload listeners are part of the lifecycle. Stop
7678
business work when its route or panel is inactive, remove listeners when the
7779
panel is removed, and keep only the route watcher needed to discover a future
@@ -124,14 +126,35 @@ prices retain enough significant digits to distinguish nonzero values from zero.
124126
## Binance Trading Data Panel
125127

126128
The panel runs only on an actual Binance futures trading route. It derives the
127-
symbol from the shared route parser, aligns five-minute data to Binance server
128-
time, and keeps the business loop stopped while the document is hidden, the
129-
panel is closed, or the route is not a trading page. A route watcher may remain
130-
alive while the panel is paused so a later SPA transition can restart the
131-
business loop.
129+
symbol from the shared route parser and aligns five-minute data to Binance server
130+
time. An initially hidden document waits for its first visible activation. Once
131+
activated, hiding the tab retains the same route session, in-flight requests,
132+
data, and historical schedule. Closing the panel or leaving a trading route stops
133+
the business loop. The route watcher remains active off-route and while hidden
134+
so a later SPA transition can start a new session; old-symbol and old-path
135+
responses cannot publish into that session.
136+
137+
Current funding refreshes 15 seconds after request completion in the foreground
138+
and 60 seconds after completion in the background. Successful and failed requests
139+
both establish this deadline; the displayed receipt timestamp changes only on
140+
success. Visibility changes recalculate the deadline from that same completion
141+
time, never restart initialization or overlap an existing request. Returning to
142+
an overdue quote schedules one refresh while retaining its existing value and
143+
provenance. An active session includes calibration and initial requests that are
144+
still pending.
145+
146+
Historical data retains its five-minute boundaries, five-second first delay,
147+
10/15/20/30-second retry schedule, and hourly server calibration while hidden.
148+
A delayed timer starts only the latest publication window. Initial and periodic
149+
responses that arrive after their own five-minute window expires are discarded
150+
before updating values, votes, or the receipt timestamp; the current window is
151+
then scheduled once. Browser timer throttling can delay these requests, and a
152+
frozen or discarded page cannot execute JavaScript until resumed or reloaded.
132153

133154
The one-second age display retains its footer elements and updates only changed
134-
text. It does not recreate the timestamp row or alter the data-fetch schedule.
155+
text. It pauses while hidden and updates immediately on return, including removal
156+
of a current funding quote whose settlement has expired. It does not recreate
157+
the timestamp row or alter the historical data-fetch schedule.
135158

136159
Each period fetch records which endpoint produced fresh data and which endpoint
137160
used a cached value or has no value. Fresh and cached indicators remain distinct
@@ -181,14 +204,14 @@ not apply another multiplier to 1000-token contracts.
181204

182205
The single funding row combines the last settled rate and its historical chart
183206
with the separate current rate, interval, and countdown. Only the settled value
184-
participates in the existing simplified funding vote. Current funding refreshes
185-
15 seconds after each request completes, with a 10-second request deadline.
207+
participates in the existing simplified funding vote. Current funding follows
208+
the visibility cadence above, with a 10-second request deadline.
186209
Interval metadata refreshes independently at activation and each full historical
187210
cycle, also with a 10-second deadline. It must never block a historical refresh.
188211
An absent metadata row means an unknown interval, not an assumed eight hours.
189212

190213
Requests and responses retain session, path, and symbol ownership. Changing
191-
routes, hiding, or closing aborts the current-rate, interval, and clock requests;
214+
routes or closing aborts the current-rate, interval, and clock requests;
192215
superseded responses cannot publish. A failed current refresh can retain an actual
193216
previous quote with a visible cache/error label. A first failure without data is
194217
labelled unavailable rather than cached. Metadata cache status is independent.
@@ -230,10 +253,22 @@ and timestamp, and a failed refresh must not overwrite a newer symbol's panel.
230253
The existing `1000` and `1000000` multiplier mapping recognizes Unicode letters
231254
as the start of an asset name; numeric-only names such as `4` retain their digits.
232255

233-
Route changes, hidden documents, panel close, and panel removal invalidate the
234-
refresh epoch and clean up timers and DOM listeners. A route watcher may remain
235-
to detect a later matching page, but it must not continue the business refresh
236-
loop while paused.
256+
Route changes, panel close, and panel removal invalidate the refresh epoch and
257+
clean up business timers. Responses belong to the complete pathname as well as
258+
the symbol, so even a same-symbol language change starts a new session. A route
259+
watcher remains to detect later matching pages, including transitions while
260+
hidden; it does not run business requests off-route or after close.
261+
262+
An initially hidden document waits until first visited. An activated panel keeps
263+
its data, expanded interpretation, pending requests, and session when hidden.
264+
Automatic refresh uses one timeout: 30 seconds after the last completed attempt
265+
in the foreground and five minutes in the background. Failed attempts also set
266+
the deadline without claiming a successful fetch. Returning
267+
before that deadline makes no request; returning after the foreground deadline
268+
silently refreshes once without replacing the table with Loading. Visibility
269+
cannot duplicate an in-flight refresh or reset its timing anchor. A manual
270+
Refresh keeps its explicit superseding-request behavior. Browser throttling or
271+
freezing can delay the schedule; missed intervals are not replayed.
237272

238273
The CMC table has metric, value, and interpretation columns, with expandable
239274
definitions. A short name and separate qualifier identify each ratio's numerator,
@@ -260,8 +295,8 @@ Interpretations describe what the value supports. The volume ratio's 50% thresho
260295
is explicitly a descriptive convention, not a statistical anomaly or trading
261296
signal. Valuation comparisons need peers, volume changes need history, DEX
262297
liquidity is not Binance order-book depth, and profile disclosure scores do not
263-
establish project safety. The existing 30-second refresh, mapping rules, source
264-
timestamps, and API/page-snapshot provenance remain separate from presentation.
298+
establish project safety. The refresh schedule, mapping rules, source timestamps,
299+
and API/page-snapshot provenance remain separate from presentation.
265300

266301
For a browser-openable offline implementation preview, run
267302
`node e2e/binance-orderbook/helpers/data-panels-preview.mjs` after the two builds
@@ -270,6 +305,24 @@ embeds unchanged generated artifacts and labelled example data. It supports both
270305
languages, light/dark themes, and each panel separately, without external network
271306
requests. It is not live Binance or Tampermonkey evidence.
272307

308+
### Background lifecycle validation (2026-10-09)
309+
310+
Trading-data `1.2.2` and CMC-data `0.2.2` passed the full 2548-test Node suite,
311+
including 23 focused background-lifecycle scenarios, and all 41 generated-panel
312+
browser scenarios. Both builds, generated syntax and release metadata checks,
313+
test lint, and `git diff --check` passed. The browser checks cover simulated
314+
visibility and suspended animation frames; the rendered English panels were
315+
inspected with both historical and CMC interpretation details kept open.
316+
317+
The controlled host verifies that ten quick returns preserve the same rows and
318+
initial request batch, without postponing the original foreground deadline.
319+
Other checks cover background cadence, request completion while hidden, failure
320+
cooldowns, late initial/cycle responses, full-path ownership when the host
321+
replaces its history methods, clock continuity,
322+
and settlement expiry on return. These are deterministic request and UI checks,
323+
not CPU or memory measurements. Current Chrome timer throttling, freezing or
324+
discarding, Tampermonkey installation, and live Binance responses remain untested.
325+
273326
### Compact layout validation (2026-10-09)
274327

275328
Trading-data `1.2.1` and CMC-data `0.2.1` passed the full 2528-test Node suite,
@@ -348,8 +401,16 @@ claim live behavior from source inspection alone.
348401
new symbol.
349402
- Open near a five-minute boundary and verify the current period is fetched
350403
after the server-time boundary rather than skipped.
351-
- Hide the tab and return; the business timer stops while hidden and resumes with
352-
a fresh, current-symbol fetch.
404+
- Switch tabs repeatedly within 15 seconds; retain the same rows, current quote,
405+
and history timestamp without new calibration or history requests. Once current
406+
funding is overdue, returning requests it once without clearing the old value.
407+
- Keep the initialized tab hidden: funding refreshes at 60-second completion
408+
intervals, history retains its five-minute schedule, and hourly calibration
409+
continues. The second-by-second display stops and catches up immediately on
410+
return, including a settlement boundary crossed while hidden.
411+
- Delay browser callbacks or pending history across several publication windows;
412+
process only the latest window after its five-second delay. A late initial or
413+
cycle response cannot appear as freshly received current-period data.
353414
- Close the panel and verify background polling does not restart it.
354415
- Simulate a partial endpoint failure; cached rows are marked as cached and are
355416
excluded from directional vote totals while missing rows remain explicit.
@@ -373,8 +434,13 @@ claim live behavior from source inspection alone.
373434
the known mapping renders data.
374435
- Verify the displayed source and update time distinguish a page snapshot from a
375436
data API response.
376-
- Hide or close the panel while a refresh is pending; no stale render or timer
377-
survives removal.
437+
- Hide the tab while a refresh is pending; its current-symbol response still
438+
renders and a quick return causes no duplicate request or Loading state.
439+
- Keep the tab hidden through a five-minute refresh, then return; only an overdue
440+
foreground snapshot triggers one silent refresh. Repeated returns after errors
441+
must respect the completion-based cooldown.
442+
- Close or leave the route while a refresh is pending; no stale render or timer
443+
survives removal, and visibility events cannot reopen a closed panel.
378444
- Navigate between matching and non-matching Binance routes and verify the
379445
route watcher does not leave a business loop running off-route.
380446
- Expand interpretations for FDV, volume/cap, liquidity/cap, supply, holders, and

‎e2e/binance-orderbook/specs/data-panels-presentation.pw.js‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { cmcDetail, tradingDataset } from '../../../test/helpers/data-media-migr
33
import {
44
createDataPanelsFixture, installDataPanels, DATA_PANEL_NOW, DATA_PANELS, POSITION_AUDIT_KEY,
55
} from '../helpers/data-panels-host.js';
6+
import { installSimulatedVisibility, setSimulatedVisibility } from '../helpers/simulated-visibility.js';
67

78
const SIDE_BY_SIDE = { trading: { left: 1040, top: 60 }, cmc: { left: 520, top: 60 } };
89

@@ -27,6 +28,68 @@ function presentationDataset() {
2728
return dataset;
2829
}
2930

31+
test('user retains both data panels and expanded details through ten simulated background returns', async ({ page, context }, testInfo) => {
32+
// Given both generated panels are loaded with expanded details under a controlled browser clock
33+
const fixture = await createDataPanelsFixture(context, { dataset: presentationDataset() });
34+
await fixture.open(page, { locale: 'en', controlledClock: true, viewport: { width: 1366, height: 768 } });
35+
await installSimulatedVisibility(page);
36+
const trading = page.locator('#jh-binance-trading-data-panel');
37+
const cmc = page.locator('#jh-binance-cmc-data-panel');
38+
await trading.locator('[data-metric="funding"] .td-spark-button').click();
39+
await cmc.locator('[data-expand-metric="fdv"]').click();
40+
const updated = await trading.locator('[data-role="updated-at"]').textContent();
41+
await page.evaluate(() => {
42+
window.retainedDataPanelRows = [...document.querySelectorAll('#jh-binance-trading-data-panel-rows > tr, #jh-binance-cmc-data-panel-rows > tr')];
43+
});
44+
45+
// When visibility and suspended animation frames simulate ten one-second tab absences
46+
for (let index = 0; index < 10; index++) {
47+
await setSimulatedVisibility(page, true);
48+
await page.clock.runFor(1_000);
49+
await setSimulatedVisibility(page, false);
50+
}
51+
52+
// Then existing rows, details and funding stay readable without a duplicate HTTP request or fresh history timestamp
53+
expect(await page.evaluate(() => {
54+
const rows = [...document.querySelectorAll('#jh-binance-trading-data-panel-rows > tr, #jh-binance-cmc-data-panel-rows > tr')];
55+
return rows.length === window.retainedDataPanelRows.length && rows.every((row, index) => row === window.retainedDataPanelRows[index]);
56+
})).toBe(true);
57+
await expect(trading.locator('[data-role="current-funding"]')).toHaveText('0.00378%');
58+
await expect(trading.locator('[data-role="funding-countdown"]')).toHaveText('Countdown 03:59:50');
59+
await expect(trading.locator('[data-role="updated-at"]')).toHaveText(updated);
60+
await expect(trading.locator('[data-metric="funding"] .td-spark-button')).toHaveAttribute('aria-expanded', 'true');
61+
await expect(cmc.locator('[data-expand-metric="fdv"]')).toHaveAttribute('aria-expanded', 'true');
62+
await expect(cmc.locator('#jh-binance-cmc-data-panel-explanation-fdv')).toBeVisible();
63+
expect(fixture.requests).toHaveLength(10);
64+
expect(fixture.gmRequests).toHaveLength(3);
65+
expect(fixture.errors).toEqual([]);
66+
await page.screenshot({ path: testInfo.outputPath('background-panels-en.png'), fullPage: true });
67+
});
68+
69+
test('user sees an expired funding quote withdrawn immediately after a simulated background settlement', async ({ page, context }) => {
70+
// Given a generated trading panel has a current quote whose settlement is two seconds away
71+
const dataset = presentationDataset();
72+
dataset.premiumIndex.nextFundingTime = DATA_PANEL_NOW + 2_000;
73+
const fixture = await createDataPanelsFixture(context, { dataset });
74+
await fixture.open(page, { controlledClock: true, panels: ['trading'] });
75+
await installSimulatedVisibility(page);
76+
const trading = page.locator('#jh-binance-trading-data-panel');
77+
const updated = await trading.locator('[data-role="updated-at"]').textContent();
78+
79+
// When the tab returns after settlement with no newer funding response available
80+
await setSimulatedVisibility(page, true);
81+
await page.clock.runFor(3_000);
82+
await setSimulatedVisibility(page, false);
83+
84+
// Then the expired quote is unavailable while settled history and its receipt time remain intact
85+
await expect(trading.locator('[data-role="current-funding"]')).toHaveText('--');
86+
await expect(trading.locator('[data-role="funding-countdown"]')).toHaveText('倒计时 等待更新');
87+
await expect(trading.locator('[data-metric="funding"] .td-last-value')).toHaveText('0.000029%');
88+
await expect(trading.locator('[data-role="updated-at"]')).toHaveText(updated);
89+
expect(fixture.requests).toHaveLength(10);
90+
expect(fixture.errors).toEqual([]);
91+
});
92+
3093
test('user switches both real panels between Chinese and English while retaining precise rates', async ({ page, context }) => {
3194
// Given the generated panels receive a tiny settled rate and a distinct current rate
3295
const fixture = await createDataPanelsFixture(context, { dataset: presentationDataset() });

0 commit comments

Comments
 (0)