Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions docs/userscript-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ primary button already released is canceled without saving.

For every resize, project the saved preference into the current viewport while
keeping the header reachable. Enlarging the viewport restores the saved
coordinates. The trading panel is 480 px wide and the CMC panel is 500 px wide,
each limited by the viewport. Without a saved preference, one panel uses the
coordinates. Both panels are 384 px wide, limited by the viewport. Without a
saved preference, one panel uses the
right edge with a 16 px margin and a 60 px top offset. When both are present,
viewports at least 1028 px wide place them side by side, with CMC to the left of
viewports at least 816 px wide place them side by side, with CMC to the left of
trading and a 16 px gap. Smaller viewports split the available height into two
scrollable panels with 8 px outer margins and an 8 px vertical gap. These defaults
are never persisted. Saved positions remain authoritative, even when a user's
Expand Down Expand Up @@ -150,6 +150,14 @@ Taker activity uses separate buy and sell volume bars; its numeric field is clea
labelled as the buy/sell ratio. Click or focus a chart to inspect its values and
dated observations; arrow keys select points and Escape closes the detail.

Compact rows use 4 px vertical padding and 26 px charts. The footer states the
shared five-minute sampling period and distinguishes settled funding history;
individual observation counts appear in the opened history detail. The funding
row also retains its settled-history caption, last settled value, current rate,
confirmed interval, and countdown. The composite heading opens the voting rules
with native `details`/`summary`; its current open state survives display refreshes.
Body text remains 13 px on desktop and annotations remain at least 11 px.

All Binance requests use the existing `https://www.binance.com` origin:

| Display | Endpoint | Source fields |
Expand Down Expand Up @@ -237,6 +245,17 @@ denominators do not produce a ratio. Null, empty, or invalid provider numbers st
unavailable, while genuine zero stays zero. Treasury holdings retain their token
unit and cannot become a holder-address count.

Compact rows keep their brief interpretation visible. Secondary notes are in the
interpretation button's tooltip and expanded explanation, and token units share
the value line when space allows. Automatic table layout reserves the complete
width of a small token price or 24-hour change before wrapping prose; browser
checks compare text ranges against their own cells as well as overall overflow.
Body text remains 12 px on desktop and
annotations remain at least 11 px. The generated-panel browser scenarios require
both default panels, all metric rows, and both footers to fit a 1366-by-768 desktop
without scrolling in Chinese and English. Narrow screens retain separate scroll
areas and no horizontal overflow.

Interpretations describe what the value supports. The volume ratio's 50% threshold
is explicitly a descriptive convention, not a statistical anomaly or trading
signal. Valuation comparisons need peers, volume changes need history, DEX
Expand All @@ -251,6 +270,23 @@ embeds unchanged generated artifacts and labelled example data. It supports both
languages, light/dark themes, and each panel separately, without external network
requests. It is not live Binance or Tampermonkey evidence.

### Compact layout validation (2026-10-09)

Trading-data `1.2.1` and CMC-data `0.2.1` passed the full 2528-test Node suite,
all 39 panel browser scenarios, both builds, generated syntax checks, test lint,
metadata/install URL checks, and independent source review. Six new browser
scenarios verify complete default panels on a 1366-by-768 screen and precise
small prices staying inside their value cells at 1366/320 px in both languages.
Resize scenarios wait for browser rendering frames before reading geometry.

At the same 1600-by-1056 preview viewport, the Chinese trading panel decreased
from approximately 897 to 500 px high and CMC from 779 to 567 px. English heights
decreased from 913 to 534 px and 826 to 618 px. Both widths are now 384 px, with
unchanged 13/12 px desktop body text and a minimum 11 px annotation size. The
four preview combinations again have no panel overlap, horizontal overflow,
page errors, or external HTTP requests. These are offline rendering results;
the installed Tampermonkey scripts and live Binance pages were not operated.

### Data-panel implementation validation (2026-10-09)

The trading-data `1.2.0` and CMC-data `0.2.0` implementations passed independent
Expand Down
4 changes: 2 additions & 2 deletions e2e/binance-orderbook/helpers/data-panels-host.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ export const DATA_PANEL_NOW = Date.UTC(2026, 9, 9, 0, 0, 0);
export const POSITION_AUDIT_KEY = '__data_panel_fixture_position_writes__';
export const DATA_PANELS = Object.freeze([
{
name: 'trading', id: 'jh-binance-trading-data-panel', width: 480,
name: 'trading', id: 'jh-binance-trading-data-panel', width: 384,
key: 'jh_binance_trading_data_pos', artifact: 'binance-trading-data',
},
{
name: 'cmc', id: 'jh-binance-cmc-data-panel', width: 500,
name: 'cmc', id: 'jh-binance-cmc-data-panel', width: 384,
key: 'jh_binance_cmc_data_pos', artifact: 'binance-coinmarketcap-data',
},
]);
Expand Down
14 changes: 7 additions & 7 deletions e2e/binance-orderbook/specs/data-panel-position.pw.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ test('user restores both separate panel preferences after a smaller viewport', a
await resize(page, SMALL);

// Then only the displayed positions clamp, leaving each original preference unchanged.
await assertPositions(page, { trading: { left: 160, top: 372 }, cmc: { left: 140, top: 372 } });
await assertPositions(page, { trading: { left: 256, top: 372 }, cmc: { left: 256, top: 372 } });
expect(await readPositions(page)).toEqual(INITIAL);
expect(await readWrites(page)).toEqual([]);
await page.screenshot({ path: testInfo.outputPath('small-clamped.png') });
Expand All @@ -145,7 +145,7 @@ test('user keeps large-screen preferences when a small tab opens reloads and clo
await fixture.open(small, { viewport: SMALL });

// Then the small tab displays reachable headers while both tabs retain the original preferences.
await assertPositions(small, { trading: { left: 160, top: 372 }, cmc: { left: 140, top: 372 } });
await assertPositions(small, { trading: { left: 256, top: 372 }, cmc: { left: 256, top: 372 } });
expect(await readPositions(page)).toEqual(INITIAL);
expect(await readWrites(page)).toEqual([]);

Expand All @@ -154,7 +154,7 @@ test('user keeps large-screen preferences when a small tab opens reloads and clo
await installPanels(small);

// Then reload and initialization leave the shared preferences untouched.
await assertPositions(small, { trading: { left: 160, top: 372 }, cmc: { left: 140, top: 372 } });
await assertPositions(small, { trading: { left: 256, top: 372 }, cmc: { left: 256, top: 372 } });
expect(await readPositions(page)).toEqual(INITIAL);
expect(await readWrites(page)).toEqual([]);

Expand Down Expand Up @@ -304,7 +304,7 @@ for (const panel of PANELS) {
await nextFrames(page);

// Then the viewport adjustment is temporary and cannot become a completed drag preference.
await assertPositions(page, { trading: { left: 160, top: 372 }, cmc: { left: 140, top: 372 } });
await assertPositions(page, { trading: { left: 256, top: 372 }, cmc: { left: 256, top: 372 } });
expect(await readPositions(page)).toEqual(INITIAL);
expect(await readWrites(page)).toEqual([]);

Expand Down Expand Up @@ -359,14 +359,14 @@ test('user keeps responsive default panel positions without creating a saved pre
await resize(page, SMALL);
const trading = await page.locator(`#${PANELS[0].id}`).boundingBox();
const cmc = await page.locator(`#${PANELS[1].id}`).boundingBox();
expect(trading).toMatchObject({ x: 144, y: 8, width: 480 });
expect(cmc).toMatchObject({ x: 124, width: 500 });
expect(trading).toMatchObject({ x: 240, y: 8, width: 384 });
expect(cmc).toMatchObject({ x: 240, width: 384 });
expect(cmc.y - trading.y - trading.height).toBeCloseTo(8, 1);
expect(cmc.y + cmc.height).toBeLessThanOrEqual(SMALL.height);
await resize(page, LARGE);

// Then the original right inset and separate desktop columns return without new position keys.
await assertPositions(page, { trading: { left: 1104, top: 60 }, cmc: { left: 588, top: 60 } });
await assertPositions(page, { trading: { left: 1200, top: 60 }, cmc: { left: 800, top: 60 } });
expect(await readPositions(page)).toEqual({ trading: null, cmc: null });
expect(await readWrites(page)).toEqual([]);

Expand Down
79 changes: 72 additions & 7 deletions e2e/binance-orderbook/specs/data-panels-presentation.pw.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test, expect } from '../test.js';
import { tradingDataset } from '../../../test/helpers/data-media-migration-host.js';
import { cmcDetail, tradingDataset } from '../../../test/helpers/data-media-migration-host.js';
import {
createDataPanelsFixture, installDataPanels, DATA_PANEL_NOW, DATA_PANELS, POSITION_AUDIT_KEY,
} from '../helpers/data-panels-host.js';
Expand Down Expand Up @@ -146,6 +146,68 @@ test('user sees the calibrated countdown advance while historical nodes and upda
});

for (const locale of ['zh-CN', 'en']) {
for (const width of [1366, 320]) {
test(`user reads a tiny CMC price and ${locale} changes inside their own column at ${width}px`, async ({ page, context }) => {
// Given a token price needs all fourteen fractional digits to remain distinguishable from zero
const detail = cmcDetail();
detail.statistics.price = 0.00000000001234;
const fixture = await createDataPanelsFixture(context, { detail });

// When the complete CMC panel renders at the requested width
await fixture.open(page, { locale, panels: ['cmc'], viewport: { width, height: 768 } });
const cmc = page.locator('#jh-binance-cmc-data-panel');

// Then the precise price and changes remain readable without crossing into another column
await expect(cmc.locator('[data-metric="price"] .cmc-number')).toHaveText('$0.00000000001234');
const crossing = await cmc.evaluate(panel => [...panel.querySelectorAll('.cmc-number, .cmc-change, .cmc-unit')].flatMap(node => {
const range = document.createRange();
range.selectNodeContents(node);
const text = range.getBoundingClientRect();
const cell = node.closest('td').getBoundingClientRect();
return text.left < cell.left || text.right > cell.right
? [{ text: node.textContent, left: text.left - cell.left, right: text.right - cell.right }]
: [];
}));
expect(crossing).toEqual([]);
const body = await cmc.locator('[id$="-body"]').evaluate(element => ({ width: element.clientWidth, scrollWidth: element.scrollWidth }));
expect(body.scrollWidth).toBeLessThanOrEqual(body.width);
expect(fixture.errors).toEqual([]);
});
}

test(`user reads every ${locale} data row on a laptop without scrolling and can open the signal rules`, async ({ page, context }) => {
// Given both panels have complete data and no saved position on a laptop-sized screen
const fixture = await createDataPanelsFixture(context, { dataset: presentationDataset() });

// When the generated scripts render their default expanded panels
await fixture.open(page, { locale, viewport: { width: 1366, height: 768 } });
const trading = page.locator('#jh-binance-trading-data-panel');
await expect(trading.locator('[data-role="current-funding"]')).toHaveText('0.00378%');

// Then all rows and both footers fit without vertical or horizontal scrolling
await expect(trading.locator('tr[data-metric]')).toHaveCount(8);
await expect(page.locator('#jh-binance-cmc-data-panel tr[data-metric]')).toHaveCount(12);
for (const { id } of DATA_PANELS) {
const body = page.locator(`#${id}-body`);
const dimensions = await body.evaluate(element => ({
height: element.clientHeight, scrollHeight: element.scrollHeight,
width: element.clientWidth, scrollWidth: element.scrollWidth,
}));
expect(dimensions.scrollHeight).toBeLessThanOrEqual(dimensions.height);
expect(dimensions.scrollWidth).toBeLessThanOrEqual(dimensions.width);
await expect(page.locator(`#${id}-footer`)).toBeInViewport({ ratio: 1 });
}
await expect(trading.locator('.td-method-note')).toBeHidden();

// When the user asks for the composite signal methodology
await trading.locator('.td-composite-line').click();

// Then the settled-rate voting rule is available on demand
await expect(trading.locator('.td-method-note')).toBeVisible();
await expect(trading.locator('.td-method-note')).toContainText(locale === 'zh-CN' ? '资金费率投票使用最新已结算值' : 'the funding vote uses the latest settled rate');
expect(fixture.errors).toEqual([]);
});

test(`user reads ${locale} panels in both themes on a narrow screen without tiny text or horizontal overflow`, async ({ page, context }) => {
// Given both generated panels are rendered on a narrow viewport in the light theme
const fixture = await createDataPanelsFixture(context, { dataset: presentationDataset() });
Expand All @@ -155,6 +217,7 @@ for (const locale of ['zh-CN', 'en']) {
// When the user changes to the dark theme and narrows the viewport further
await page.emulateMedia({ colorScheme: 'dark' });
await page.setViewportSize({ width: 320, height: 680 });
await completedLayoutFrames(page);

// Then theme colors change while every visible annotation stays readable and within its panel width
const readings = await page.evaluate(ids => ids.map(id => {
Expand Down Expand Up @@ -206,11 +269,12 @@ for (const installation of [['trading', 'cmc'], ['cmc', 'trading']]) {
await expect(cmc.locator('#jh-binance-cmc-data-panel-footer a')).toHaveText('CMC data-api');

// Then each desktop panel has its own column regardless of installation order
expect(await trading.boundingBox()).toMatchObject({ x: 1104, y: 60, width: 480 });
expect(await cmc.boundingBox()).toMatchObject({ x: 588, y: 60, width: 500 });
expect(await trading.boundingBox()).toMatchObject({ x: 1200, y: 60, width: 384 });
expect(await cmc.boundingBox()).toMatchObject({ x: 800, y: 60, width: 384 });

// When the viewport becomes too narrow for adjacent columns
await page.setViewportSize({ width: 320, height: 680 });
await completedLayoutFrames(page);

// Then both headers stay reachable above separate scrolling bodies with an eight-pixel gap
const readings = await page.evaluate(ids => ids.map(id => {
Expand Down Expand Up @@ -262,6 +326,7 @@ for (const panel of DATA_PANELS) {
// When the user closes one panel and resizes the surviving panel's viewport
await page.locator(`#${panel.id}-close`).click();
await page.setViewportSize({ width: 1360, height: 900 });
await completedLayoutFrames(page);

// Then the remaining panel projects its saved preference and closing writes no coordinates
await expect(page.locator(`#${panel.id}`)).toBeHidden();
Expand Down Expand Up @@ -298,7 +363,7 @@ test('user releases a stationary CMC header after the trading panel mounts witho

// When trading mounts and automatically moves the CMC panel into the adjacent column
await installDataPanels(page, { panels: ['trading'] });
await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(588);
await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(800);
await page.mouse.up();
await completedLayoutFrames(page);

Expand All @@ -318,7 +383,7 @@ test('user releases a stationary CMC header after its peer closes without saving
// When keyboard activation closes trading while the mouse remains pressed on CMC
await page.locator('#jh-binance-trading-data-panel-close').focus();
await page.keyboard.press('Enter');
await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(1084);
await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(1200);
await page.mouse.up();
await completedLayoutFrames(page);

Expand All @@ -335,7 +400,7 @@ test('user keeps the one saved panel coordinate when the other panel uses an aut

// When the desktop shrinks and then returns to its original size
await page.setViewportSize({ width: 1000, height: 760 });
await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(500);
await expect.poll(async () => (await page.locator('#jh-binance-cmc-data-panel').boundingBox()).x).toBe(616);
await page.setViewportSize({ width: 1600, height: 1200 });
await completedLayoutFrames(page);

Expand All @@ -353,7 +418,7 @@ test('user saves a collapsed panel lower once and expands a body that fits the r
await expect(panel.locator('[data-role="current-funding"]')).toHaveText('0.00378%');
await expect(panel.locator('[data-role="funding-period"]')).toHaveText('当前 · 4小时');
const initial = await panel.boundingBox();
const target = { left: 80, top: 600 };
const target = { left: 80, top: 900 };
await page.locator('#jh-binance-trading-data-panel-collapse').click();
await expect(page.locator('#jh-binance-trading-data-panel-body')).toBeHidden();
await page.mouse.move(initial.x + 14, initial.y + 14);
Expand Down
Loading
Loading