fix(chart): prevent live mark ticks from mutating non-oracle series - #2442
fix(chart): prevent live mark ticks from mutating non-oracle series#2442Bayyan16 wants to merge 2 commits into
Conversation
|
@Bayyan16 is attempting to deploy a commit to the Khubair Nasir's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Independent verification — not an approval (QA/Security own that), just evidence for whoever reviews. I've been mutation-checking correctness PRs here because of a recurring failure mode in this repo: tests that pass without exercising the thing they're named for (11 confirmed instances, including one of my own). The helper logic in this PR is very well covered. Method: removed only the source gate on the PR branch, restoring #2441 (mark ticks mutate every series): if (source !== 'oracle' || !Number.isFinite(markPriceUsd)) return bar;
↓
if (!Number.isFinite(markPriceUsd)) return bar;Result: and they're the right seven — all three non-oracle sources ( One gap, same standard I applied to #2437: the coverage is entirely at the That's milder than #2437's case because the helpers are pure and the wiring is small, so I'm not suggesting a full component test. But if the source resolution at line 455 were ever passed the wrong argument, every test here would stay green while the bug returned in the UI — which is exactly the shape of the original report. Cheapest thing that would close it: one assertion that Not blocking — the fix is real and I verified it. Flagging for consistency with the standard I applied to the sibling PR. |
|
Thank you for the mutation check and for distinguishing the helper coverage from the remaining component-wiring gap. Agreed: the current suite proves the source gate itself, but it does not yet prove that I’ll add the smallest focused component-level regression supported by the existing test harness, covering a non-oracle-backed series and verifying that a live mark tick does not update the visible series. I’ll also mutation-check the component wiring so the test fails if the active source is incorrectly treated as I’ll keep the production logic unchanged and re-run the focused chart tests, TypeScript validation, build, formatting, and patch-integrity checks on the updated head. |
|
Implemented the focused The new test renders the actual component with Percolator data active and verifies that the final call to I also mutation-checked the component wiring: Additional validation on the updated head: The production logic remains unchanged; the second commit is test-only. |
Summary
Fixes #2441.
This PR restores chart source integrity by preventing live protocol mark-price
ticks from mutating a visible chart series backed by a different data source.
Before this change,
TradingChartcorrectly selected one of four possibleseries sources:
However, the live slab-price subscriber did not preserve that source boundary.
Every finite mark-price tick correctly updated the separate
Markoverlay, butthe same tick was also unconditionally merged into the current visible OHLC bar
or line/area point through
series.update().As a result, a mark observation could overwrite:
This produced synthetic chart movement that never occurred in the source
feeding the chart.
On the affected Playground market, DEX candles were approximately
$0.010–$0.022, while the protocol mark was approximately$0.000189–$0.0002. Applying the mark to the last DEX candle created anartificial downward candle greater than 98%.
Root Cause
TradingChartstores the latest rendered value in generic refs:These refs retain the current data shape, but the live tick handler previously
did not know which source produced the data stored in them.
The old OHLC update path was effectively:
The line/area path similarly replaced the last visible value:
Those operations were executed for every active series, even when the series
was populated from DEX, Pyth, or actual Percolator trades.
This violated the following invariant:
The correct ownership model is:
What Changed
1. Added an explicit chart-source model
A new
ChartDataSourcetype identifies the source currently backing thevisible price series:
The source is resolved using the same production-priority rules already used by
TradingChart:The priority remains unchanged:
This PR does not alter source selection. It only makes the selected source
explicit and available to the live-tick path.
2. Added source-aware live-mark merge helpers
The new pure helpers:
apply a mark tick only when the active source is:
For non-oracle sources:
the helpers return the original bar or point unchanged.
This preserves object identity intentionally, allowing
TradingChartto skipseries.update()completely when the live mark does not own the active series.3. Preserved live updates for the Oracle fallback
This fix does not disable live chart updates globally.
When the visible series is the Oracle fallback, the live mark continues to
update the current bar:
The same behavior is retained for Oracle-backed line and area series.
Therefore:
4. Kept the Mark overlay live for every source
The separate
Markprice line still updates on every valid live mark tick,regardless of the source backing the chart.
This preserves the intended trading interface:
The fix separates those responsibilities instead of suppressing the mark
display.
5. Prevented a source-transition mismatch window
The active-source ref is committed only after the structural chart effect has
rebuilt the visible series.
It is not updated prematurely during render.
This matters during transitions such as:
Updating the ref during render would briefly create this inconsistent state:
A live tick arriving in that window could still mutate the previous source
under the new source identity.
The ref is now synchronized with the series after the structural rebuild, so
the live subscriber observes the source corresponding to the series actually
mounted in
seriesRef.current.6. Reused the resolved source for viewport identity
The structural chart effect now uses the resolved
activeDataSourcewhenbuilding the fit key:
`${chartDataKind(chartStyle)}:${timeframe}:${activeDataSource}`This removes duplicate source-resolution logic and ensures that viewport
identity and live-series ownership use the same source-of-truth.
The existing pan/zoom behavior remains unchanged:
Files Changed
app/components/trade/TradingChart.tsxseries.update()calls for unchanged values;app/lib/chart-live-tick.tsAdds pure source-integrity primitives:
ChartDataSource;resolveChartDataSource;mergeMarkPriceIntoBar;mergeMarkPriceIntoPoint;app/__tests__/lib/chart-live-tick.test.tsAdds deterministic regression coverage for:
Behavioral Matrix
Percolator candles continue to receive their live OHLC updates through actual
trades:<slab>events rather than generic protocol mark notifications.Regression Scenario
Representative values from the affected deployment:
The previous implementation produced:
Synthetic drop:
After this change:
No synthetic candle is generated.
Validation
Targeted and related regression tests
Result:
The new source-integrity suite contains:
TypeScript
pnpm exec tsc --noEmitResult:
Formatting
pnpm exec prettier --check \ lib/chart-live-tick.ts \ __tests__/lib/chart-live-tick.test.tsResult:
Production build
Result:
Existing non-blocking warnings related to Next.js configuration,
middleware naming, Node deprecations, and optional BigInt native bindings remain
unchanged and are unrelated to this PR.
Git validation
Result:
The branch contains one focused commit and only the three files required for
this fix.
Runtime Validation Note
The original issue was reproduced on the deployed Playground market:
The local environment could load the on-chain slab and position state, but it
could not load the equivalent DEX chart because the local
/api/markets/[slab]request did not resolve the deployment-specific metadatarequired by GeckoTerminal.
Local result:
Playground result:
Because the DEX chart mint is supplied through
mainnet_ca, the affected DEXseries could not be recreated in the local UI.
The cross-source mutation itself is covered deterministically by the regression
tests.
Deployment-level post-fix validation should be completed against the Vercel PR
preview using the affected slab.
This PR does not claim that the fix has already been deployed to or verified on
the live Playground environment.
Scope
This PR fixes frontend chart source integrity only.
It does not change:
The liquidation line observed in #2441 remains outside this fix because its
direction was consistent with a short position. Its compressed position on the
chart resulted from the large difference between the DEX price domain and the
position's Mark/Entry/Liquidation domain.
Acceptance Criteria
Related Work
This is distinct from previous chart fixes:
actual
trades:<slab>live-update path for Percolator candles.NaNandInfinity.The values involved in this issue are valid finite numbers:
The defect was not malformed data. It was a valid observation being applied to
a series owned by a different source.
Final Result
After this change:
This removes the synthetic giant-candle path while preserving the existing
source priority, live mark display, Oracle fallback behavior, and chart
interaction model.