fix(charts): preserve viewport on 30s candle refetch (#336) #822

Merged
PlasticDigits merged 1 commit from cursor/gitlab-issue-workflow-56f6 into main 2026-06-06 07:07:46 +00:00
PlasticDigits commented 2026-06-06 06:53:44 +00:00 (Migrated from gitlab.com)

Summary

Fixes https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/336 — the price chart time-scale viewport was resetting to full history every 30 seconds because PriceChartLightweightCanvas called timeScale().fitContent() after each candle refetch.

Changes

  • Add priceChartLightweightSeriesSync.ts — incremental series.update() from the first changed bar; setData only on first load, interval switch, or truncated history.
  • Remove fitContent() from the background data-refresh effect.
  • Limit indicator overlay effect to toggle changes only (not point updates) so fitContent() does not run on every refetch when overlays are enabled.
  • Document invariant in docs/frontend.md and skills/AGENTS_FRONTEND_PRICE_CHART.md.

Acceptance checklist

Criterion Verification Result
Background candle refetches must not reset user zoom/scroll npm run test:run -- src/components/charts/__tests__/priceChartLightweightSeriesSync.test.ts src/components/charts/__tests__/PriceChartLightweightCanvas.test.tsx — background data refresh does not call fitContent PASS
Use series.update() for live tail updates priceChartLightweightSeriesSync.test.ts — update tail bar when only the last candle changes PASS
fitContent() only on initial mount / indicator toggle / interval switch (setData) Code review + stub tests; init still calls fitContent() once; indicator toggle effect unchanged PASS
Interval switches still work (structural setData) npm run test:run -- src/components/charts/__tests__/PriceChart.test.tsx — reuses one chart instance across many interval switches PASS
Manual: zoom chart, wait 30s, viewport preserved Requires local stack (make setup-cloud-localterra + make dev); scroll/zoom on /trade or /charts, wait ≥30s SKIP (not run in agent VM this session)

Verification for third parties

export PATH="$HOME/.nvm/versions/node/$(cat .nvmrc)/bin:$PATH"
cd frontend-dapp
npm run test:run -- src/components/charts/__tests__/priceChartLightweightSeriesSync.test.ts
npm run test:run -- src/components/charts/__tests__/PriceChartLightweightCanvas.test.tsx
npm run test:run -- src/components/charts/__tests__/PriceChart.test.tsx

Manual QA (optional): open chart, zoom into recent candles, wait 30+ seconds — viewport should stay put while the last candle may update.

Issue #336 left open until merge.


Note

Low Risk
Frontend-only chart UX fix with focused unit tests; no API, auth, or trading logic changes.

Overview
Fixes #336: the trade price chart no longer jumps back to full-range zoom when candles refresh every 30s (refetchInterval on PriceChart).

priceChartLightweightSeriesSync.ts chooses series.update() from the first changed bar when the series prefix is unchanged, and setData only on first load, interval switch (first bar time changes), truncated history, or empty data. PriceChartLightweightCanvas routes candles, volume, and indicator lines through these helpers with previous*Ref snapshots; routine prop updates no longer call timeScale().fitContent() (that stays on initial mount and indicator toggles only). The indicator effect reads points from refs and no longer depends on indicator point arrays, so toggles do not re-trigger full data sync.

Vitest adds priceChartLightweightSeriesSync.test.ts, extends the jsdom mock with update, and asserts background refresh uses update without fitContent. docs/frontend.md and skills/AGENTS_FRONTEND_PRICE_CHART.md document the invariant.

Reviewed by Cursor Bugbot for commit 9c27c35016. Bugbot is set up for automated code reviews on this repo. Configure here.

## Summary Fixes https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/336 — the price chart time-scale viewport was resetting to full history every 30 seconds because `PriceChartLightweightCanvas` called `timeScale().fitContent()` after each candle refetch. ### Changes - Add `priceChartLightweightSeriesSync.ts` — incremental `series.update()` from the first changed bar; `setData` only on first load, interval switch, or truncated history. - Remove `fitContent()` from the background data-refresh effect. - Limit indicator overlay effect to toggle changes only (not point updates) so `fitContent()` does not run on every refetch when overlays are enabled. - Document invariant in `docs/frontend.md` and `skills/AGENTS_FRONTEND_PRICE_CHART.md`. ## Acceptance checklist | Criterion | Verification | Result | |-----------|--------------|--------| | Background candle refetches must not reset user zoom/scroll | `npm run test:run -- src/components/charts/__tests__/priceChartLightweightSeriesSync.test.ts src/components/charts/__tests__/PriceChartLightweightCanvas.test.tsx` — *background data refresh does not call fitContent* | PASS | | Use `series.update()` for live tail updates | `priceChartLightweightSeriesSync.test.ts` — *update tail bar when only the last candle changes* | PASS | | `fitContent()` only on initial mount / indicator toggle / interval switch (`setData`) | Code review + stub tests; init still calls `fitContent()` once; indicator toggle effect unchanged | PASS | | Interval switches still work (structural `setData`) | `npm run test:run -- src/components/charts/__tests__/PriceChart.test.tsx` — *reuses one chart instance across many interval switches* | PASS | | Manual: zoom chart, wait 30s, viewport preserved | Requires local stack (`make setup-cloud-localterra` + `make dev`); scroll/zoom on `/trade` or `/charts`, wait ≥30s | SKIP (not run in agent VM this session) | ## Verification for third parties ```bash export PATH="$HOME/.nvm/versions/node/$(cat .nvmrc)/bin:$PATH" cd frontend-dapp npm run test:run -- src/components/charts/__tests__/priceChartLightweightSeriesSync.test.ts npm run test:run -- src/components/charts/__tests__/PriceChartLightweightCanvas.test.tsx npm run test:run -- src/components/charts/__tests__/PriceChart.test.tsx ``` Manual QA (optional): open chart, zoom into recent candles, wait 30+ seconds — viewport should stay put while the last candle may update. Issue #336 left open until merge. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Frontend-only chart UX fix with focused unit tests; no API, auth, or trading logic changes. > > **Overview** > Fixes **#336**: the trade price chart no longer jumps back to full-range zoom when candles refresh every **30s** (`refetchInterval` on `PriceChart`). > > **`priceChartLightweightSeriesSync.ts`** chooses **`series.update()`** from the first changed bar when the series prefix is unchanged, and **`setData`** only on first load, interval switch (first bar time changes), truncated history, or empty data. **`PriceChartLightweightCanvas`** routes candles, volume, and indicator lines through these helpers with `previous*Ref` snapshots; routine prop updates **no longer call `timeScale().fitContent()`** (that stays on initial mount and indicator toggles only). The indicator effect reads points from refs and no longer depends on indicator point arrays, so toggles do not re-trigger full data sync. > > Vitest adds **`priceChartLightweightSeriesSync.test.ts`**, extends the jsdom mock with **`update`**, and asserts background refresh uses **`update`** without **`fitContent`**. **`docs/frontend.md`** and **`skills/AGENTS_FRONTEND_PRICE_CHART.md`** document the invariant. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 9c27c35016e52e61b3484a2c574f6e95c66da3fe. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
ghost1 commented 2026-06-06 06:53:52 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-06 06:53:58 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-06 06:53:58 +00:00 (Migrated from gitlab.com)

mentioned in issue #336

mentioned in issue #336
ghost1 commented 2026-06-06 06:54:00 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-06 06:55:19 +00:00 (Migrated from gitlab.com)

Security review

Commit reviewed: 9c27c35016e52e61b3484a2c574f6e95c66da3fe

Scope: Frontend chart UX only — priceChartLightweightSeriesSync.ts (new), PriceChartLightweightCanvas.tsx (incremental series.update() vs setData / fitContent() on refetch), Vitest stubs/tests, and docs/skills updates. No API, auth, indexer, contract, or dependency changes.

Method: Traced indexer candle data through existing indexerCandlesToChartPoints trust boundary (Number.isFinite filtering, unchanged) into the new sync helpers and canvas effects. Checked for injection, XSS, authz, secret leakage, SSRF, unsafe deserialization, and supply-chain deltas.

Outcome: FINDINGS: 0 medium+

Inline threads: None — no medium+ issues on this diff.


Notes (informational, below reporting threshold):

  • Candle/OHLC input remains client-side validated in priceChartCandles.ts before reaching lightweight-charts; this MR does not bypass that boundary.
  • Chart rendering stays on canvas (aria-hidden); no new dangerouslySetInnerHTML or DOM sinks.
  • Compromised indexer returning manipulated OHLC is a pre-existing trust-model concern, not introduced here.
## Security review **Commit reviewed:** `9c27c35016e52e61b3484a2c574f6e95c66da3fe` **Scope:** Frontend chart UX only — `priceChartLightweightSeriesSync.ts` (new), `PriceChartLightweightCanvas.tsx` (incremental `series.update()` vs `setData` / `fitContent()` on refetch), Vitest stubs/tests, and docs/skills updates. No API, auth, indexer, contract, or dependency changes. **Method:** Traced indexer candle data through existing `indexerCandlesToChartPoints` trust boundary (`Number.isFinite` filtering, unchanged) into the new sync helpers and canvas effects. Checked for injection, XSS, authz, secret leakage, SSRF, unsafe deserialization, and supply-chain deltas. **Outcome:** `FINDINGS: 0` medium+ **Inline threads:** None — no medium+ issues on this diff. --- **Notes (informational, below reporting threshold):** - Candle/OHLC input remains client-side validated in `priceChartCandles.ts` before reaching lightweight-charts; this MR does not bypass that boundary. - Chart rendering stays on canvas (`aria-hidden`); no new `dangerouslySetInnerHTML` or DOM sinks. - Compromised indexer returning manipulated OHLC is a pre-existing trust-model concern, not introduced here.
PlasticDigits commented 2026-06-06 07:07:46 +00:00 (Migrated from gitlab.com)

mentioned in commit f95ffd174a

mentioned in commit f95ffd174a2b6c7a276f71303c55dfc38e8609ba
PlasticDigits (Migrated from gitlab.com) merged commit f95ffd174a into main 2026-06-06 07:07:46 +00:00
PlasticDigits commented 2026-06-08 08:43:13 +00:00 (Migrated from gitlab.com)

mentioned in commit 05be9cfc82

mentioned in commit 05be9cfc82666c2ce7bb2a542198c308fa15e1b4
PlasticDigits commented 2026-06-08 13:42:29 +00:00 (Migrated from gitlab.com)

mentioned in commit 9377f88b68

mentioned in commit 9377f88b680054da5b92d3ea4a4927c6b253e326
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic!822
No description provided.