A market dashboard can render perfectly while showing the wrong symbol, stale data or a live candle as if it were final. These are data-contract failures, and CSS cannot fix them.
I build rule-based analysis tools at AGProLabs. This article presents a small, standalone JavaScript example for testing the boundary between an incoming snapshot and its UI. It is a design example, not an extract from a production system or a trading signal.
Make the expected context explicit
A symbol name alone is an incomplete identity. A row should carry a source, instrument identifier and timeframe. The UI should know which combination it currently expects. If the user switches tabs while a request is in flight, a late response for the previous context must not silently replace the new one.
Use timestamps with documented units. In the example below, observedAtMs is the source observation time in Unix milliseconds; nowMs is an injected comparison clock. Neither is inferred from when a component rendered.
Return a state instead of a reassuring number
function classifySnapshot(row, expected, policy, nowMs) {
if (!row || !expected || !policy) return "INVALID";
const numbers = [row.value, row.observedAtMs, nowMs,
policy.maxAgeMs, policy.clockToleranceMs];
if (!numbers.every(Number.isFinite)) return "INVALID";
if (row.observedAtMs < 0 || nowMs < 0 ||
policy.maxAgeMs < 0 || policy.clockToleranceMs < 0 ||
typeof row.confirmed !== "boolean") return "INVALID";
for (const key of ["source", "instrument", "timeframe"]) {
if (typeof row[key] !== "string" || row[key].trim() === "" ||
typeof expected[key] !== "string" ||
expected[key].trim() === "") return "INVALID";
if (row[key] !== expected[key]) return "CONTEXT_MISMATCH";
}
const ageMs = nowMs - row.observedAtMs;
if (ageMs < -policy.clockToleranceMs) return "CLOCK_MISMATCH";
if (ageMs > policy.maxAgeMs) return "STALE";
return row.confirmed ? "CONFIRMED" : "LIVE_PREVIEW";
}
Number.isFinite deliberately rejects numeric strings, NaN and infinity; see the MDN reference. Parse external data at a separate boundary if your contract permits strings. Do not make the validator silently guess their meaning.
Zero is not automatically invalid: an indicator value can legitimately be zero. Price positivity, permitted timeframes and instrument availability need separate domain rules. This function does not validate an entire market feed.
Inject the clock and test the boundaries
An injected clock makes tests reproducible. This table uses a fixed nowMs, an example maximum age of 60,000 ms and a clock tolerance of 1,000 ms. Those thresholds are test inputs, not universal market recommendations.
| Input change | Expected state |
|---|---|
| Matching context, age 1,000 ms, confirmed | CONFIRMED |
| Same row, confirmed false | LIVE_PREVIEW |
| Age exactly 60,000 ms | CONFIRMED |
| Age 60,001 ms | STALE |
| Observation 1,001 ms in the future | CLOCK_MISMATCH |
| Response for a different instrument | CONTEXT_MISMATCH |
| Value is a numeric string or NaN | INVALID |
| Missing confirmation flag | INVALID |
Also test negative policy values, blank identities and missing snapshots. A function that returns CONFIRMED for an ordinary sample tells you little about its failure behavior.
Map each state to a useful interface
CONFIRMED can show the measurement with its source and observation time. LIVE_PREVIEW should explain that the value can change. STALE should retain any useful last-known value with a visible age label and keep it out of a current ranking. CONTEXT_MISMATCH should discard the response for the active view. INVALID and CLOCK_MISMATCH need a clear unavailable state plus diagnostic information outside the normal user flow.
The confirmation flag also needs a documented producer meaning. TradingView distinguishes historical, realtime and closing updates in its bar-state documentation. Confirmation for a remote timeframe should not be guessed from the receiving browser clock.
For a trader-facing example of why these distinctions matter, our completed-candle reading guide explains source, clock and state. The dashboard reading guide separates a ranked candidate from its quality conditions.
Keep performance measurable
Validate when a new snapshot arrives, store its normalized result and avoid reparsing the same payload on every render. Then measure validation time and rendering time separately using representative row counts. A smaller code sample or fewer lines is not evidence of a faster application.
The useful acceptance criterion is concrete: every displayed value has the right context, an explainable age and an honest state. Once that contract is reliable, visual design can make the information easier to read.
Top comments (1)
One boundary the table does not have: age exactly -1,000 ms (observation 1,000 ms in the future). With
ageMs < -clockToleranceMsthat is accepted, and the 1,001 ms row is the first rejection, so a row at the edge on each side (-1,000 and -1,001) would pin the operator.The bigger design question is
maxAgeMsfor CONFIRMED rows. IfobservedAtMsfor a closed 1h candle is its close time, that row turns STALE 60 seconds later although it is still the newest confirmed candle until the next close. For a confirmed row the useful bound is closer to timeframe plus a grace period, while a LIVE_PREVIEW row does want a short maximum age. Two policies keyed onconfirmed, or on the timeframe, would stop the state from flipping to STALE for a reason unrelated to the data being old.On the in-flight tab switch: CONTEXT_MISMATCH is only right if
expectedis read when the response arrives, not captured when the request was sent. A test that fires request A, switches to B, then resolves A and asserts the view stays on B covers that, and is a different test from the classifier table.