Skip to content

Clear plugin visuals the moment the market switches - #293

Merged
alexgrover merged 1 commit into
devfrom
fix/market-switch-stale-layer-data
Oct 16, 2026
Merged

alexgrover merged 1 commit into
devfrom
fix/market-switch-stale-layer-data

Conversation

@alexgrover

@alexgrover alexgrover commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Background

Switching the symbol or timeframe on a chart with plugin-drawn data (a chart type with a data engine, or a native indicator that paints its own renderer layer) could stack the old market's visuals in a single column at the right edge until the new bars arrived. While the new market loads, the bar series is empty, and an empty axis maps every timestamp to the same logical slot.

Two paths put old data there during that gap:

  • Chart-type data engines. The active engine is suspended and its channels blanked at the switch, but a fetch already in flight resolved afterwards and pushed through its host, refilling the channel. An engine stopped by the post-load rebuild could also push later, overwriting the new market's data with the old market's.
  • Native indicators. Nothing silenced them at the switch: the old instances kept running and their layer payloads stayed in the scene until the post-load restart.

Summary

  • The host handed to a chart-type engine forwards pushData/pushPending only from the chart's current, unsuspended engine. A suspended engine's re-push on resume() still lands.
  • Each native indicator start gets its own context, revoked once the instance is stopped. Pushes made inside stop() (a layer-backed native clearing its channel) still land; anything after is dropped. This covers removal, destroy and every restart.
  • An identity switch stops running native indicators and blanks their channels together with the series clear, the same treatment the chart-type engine already gets. The post-load restart rebuilds them as before.
  • The SeriesDataEngine and NativeIndicator contract comments say what the chart now enforces. No public API change.

End-to-end verification

Exercised in Chromium (Playwright) with the built package linked into a downstream extension's playground, which registers a chart type with a data engine and two layer-backed native indicators, on live data. Candle responses were delayed to widen the loading gap, and engine responses were delayed to keep a fetch in flight across the switch. Each frame was probed for the layer payloads in the scene and the painted extent of every layer canvas.

  • Before: the old market's cells were stacked in one column at the right edge for the whole load: an in-flight engine fetch on both a symbol and a timeframe switch, and every switch for the two native overlays. A late engine response landing after the reload pushed the old market's cells onto the new one.
  • After: every layer stays blank during the load, and the new market's data paints once its bars land. The late response is dropped.
  • No change elsewhere: leaving the style and re-entering it repaints (the resume re-push lands); hiding, showing and removing a native overlay clear and repaint as before. Volume, the visible-range volume profile and a Pine script all come back after both a symbol and a timeframe switch. The two downstream packages' typecheck and test suites pass against this build.

Checklist

  • This PR does one thing (one fix, one feature, one refactor, or one docs change)
  • npm run typecheck, npm run lint, npm test, and npm run build all pass
  • Tests have been added / updated (for bug fixes / features)
  • Documentation in docs/ has been added / updated (for public interface or feature changes)
  • CHANGELOG.md has an entry under [Unreleased] (for user-visible changes)
  • I have signed the CLA (the bot asks in this PR)
  • I have reviewed this pull request myself (self-review)

Future work

  • docs/contributing/plugin-sdk.md describes the engine lifecycle accurately but does not mention that late pushes are dropped; one sentence there would document the guarantee for plugin authors.

Note

Medium Risk
Changes market-switch and plugin lifecycle in the orchestrator; incorrect gating could hide legitimate plugin updates, but behavior is covered by new tests and matches documented SDK contracts.

Overview
Plugin-drawn layers no longer linger or get corrupted when you change symbol or timeframe. While the new market loads with an empty bar axis, time-keyed plugin payloads used to stack in one column at the right edge; fetches that finished after suspend or after a rebuild could still paint the old market.

On an identity switch the orchestrator now stops running native indicators synchronously, blanks their renderer channels, and routes chart-type pushData / pushPending only from the chart’s current, unsuspended engine (late work from suspended or replaced engines is dropped). Each native start gets a revocable context: teardown inside stop() can still clear a layer, but any later emit / pushData is ignored. Contract comments in the chart-type registry and native-indicator types describe this host behavior; no public API change. Tests cover late pushes after remove, style suspend, and setMarket.

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

A symbol or timeframe switch empties the bar series while the new market
loads. Two kinds of plugin data survived into that gap and, with no bars on
the axis, every time-keyed entry mapped to the same slot - the old market's
cells stacked in one column at the right edge until the new bars landed:

- a chart-type data engine suspended mid-fetch pushed its cache back when the
  fetch resolved, and a stopped engine could push the old market's cells onto
  the new one after the reload;
- native indicators kept running and kept their layer payloads until the
  post-load restart.

The host handed to a chart-type engine now forwards pushes only from the
chart's current, unsuspended engine. Each native indicator start gets its own
context, revoked once the instance is stopped (its own teardown inside stop()
still lands). An identity switch stops running natives and blanks their
channels in the same breath as the series clear; the post-load restart
rebuilds them as before.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Oct 16, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 751c2ead-e5ad-492e-999e-f09bc9a57181)

@alexgrover
alexgrover merged commit d5e5366 into dev Oct 16, 2026
3 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant