---
description: Upgrade to @titancharts/data and @titancharts/react 4.5.0 — most apps need no code change, and this page lists the few that do.
---

# Migrating to v4.5

Upgrade `@titancharts/data` and `@titancharts/react` to 4.5.0. Version 4.5.0
loads chart history, market search and market metadata over a new data
connection. Older versions stop loading them when the old connection is
retired. Everything on this page is imported from `@titancharts/data` unless
noted.

{% hint style="info" %}
**Do I need to change code?** Most apps don't. If you create the DataFeed with
`licenseKey` or `authProxy` and pass it to `<TitanChart>`, upgrade both
packages and you're done. You need edits only if your code:

- reads `getServerUrls()`, or keys a `reconnect` / `reconnecting` (or chart
  `data:reconnect` / `data:reconnecting`) listener on the `'v2'` / `'v2Book'`
  names of the DataFeed's two data connections, now `history` (candles, search
  and metadata) and `books` (orderbooks);
- compares `getConnectionState()` values to `'connected'`: `books` now stays
  `'idle'` until an orderbook is shown (see [Reconnection](#reconnection));
- calls `fetchRawHistoricalData()`, `fetchLatestSnapshots()` or
  `fetchRelatedEvents()`, or imports anything in
  [Removed or changed](#removed-or-changed);
- serves the worker file itself through `workerUrl` (see
  [Self-hosted worker](#self-hosted-worker));
- registers a parser for a built-in data type;
- sets its own `providerSpecific` keys, in a request's `modifiers` or a
  control's `queryModifiers` / `getQueryModifiers`;
- puts HTML in indicator names or legend text, or links in tooltip lines (see
  [Text instead of HTML](#text-instead-of-html));
- has an exhaustive `switch` over `IIndicatorError['source']` or
  `CalendarUnit`.

Apps that set `servers` or use the reconnection types keep working: the old
names are [deprecated](#deprecated) and still work in 4.5. One exception: a
`servers.v2` or `servers.v2Book` address with no `/v2` path segment is used
unchanged, so 4.5 sends its requests to the old server there and they fail.
Set `servers.history` or `servers.books` instead (see
[Server URLs](#server-urls)).
{% endhint %}

## Timeline and testing

Version 4.5.0 works against the current servers, so you can upgrade and test
as soon as it is released. Versions before 4.5.0 keep working until the old
data connection is retired; after that, a chart on an older version draws no
candles and its market search is empty. The old connection is retired on
Tuesday, 10 November 2026. For questions about timing, ask your TitanCharts account contact.

## Install

Upgrade both packages together:

```bash
npm install @titancharts/react@^4.5.0 @titancharts/data@^4.5.0
```

## What your users will notice

- An indicator or heatmap whose data your license does not cover shows a
  license error, such as `Cumulative Volume Delta is not included in your license`, instead of an
  empty pane. See [License refusals](#license-refusals).
- An orderbook heatmap for a prediction-market id that matches no market shows
  `No market or outcome matches <symbol>`.
- The Hyperliquid Liquidation and TP/SL heatmaps are offered on Hyperliquid
  perpetuals and HIP-3 only. Elsewhere they show `Available on Hyperliquid
  perpetuals only`.
- Price-axis pills are 17 px tall and fit their number instead of filling the
  axis, with indicator and reference pills blended into the axis.
- The Ruler dims the chart outside the measured range, shows its readout in a
  card beside the end price and marks the range on both axes.
- The legend folds rows with an arrow before the title, and a row too wide for
  the chart folds its last values behind a `+N` chip.
- A chart moved to a screen with a different pixel density stays sharp without
  a reload.
- Price Range, Date Range and Date & Price Range label in the tool's color on
  two rows, and Anchored VWAP colors its rising and falling stretches with the
  candle colors. See [Chart look and drawing tools](#chart-look-and-drawing-tools).
- The interval menu sorts its intervals into Seconds, Minutes, Hours and Days
  sections and offers up to 25 intervals, from 1 second to 1 year, leaving out
  those the shown market doesn't serve. See
  [Interval Menu](../features/interval-menu.md).

## Coverage

The `DataFeed` from `@titancharts/data` covers crypto (spot, perpetuals and
futures, including Hyperliquid builder perpetuals, HIP-3) and prediction
markets (Polymarket and HIP-4).

## TL;DR

### Deprecated

These still work in 4.5. Change them before the next major version, which
removes them.

| Deprecated | Use instead |
|---|---|
| `servers.v2` / `servers.v2Book` | `servers.history` / `servers.books` ([details](#server-urls)) |
| `V2ReconnectConfig` (type of `reconnect`) | `ReconnectConfig` (same fields and defaults) |
| `V2ReconnectState` | `ReconnectState` (same values) |
| `getConnectionState()` keys `v2` / `v2Book` | `history` / `books`. Both sets of keys are returned, with the same values. |
| `reconnect` options `maxAttempts`, `baseDelay`, `maxDelay`, `queueTimeout`, `reconnectTimeout` | Nothing. They have no effect: the history and books connections retry on their own. |
| DataFeed `error` event (`dataFeed.on('error', …)`) | It never fires. Listen for `auth:expired` (or `onAuthError`) and `reconnecting`. |

### Removed or changed

Most of these were low-level exports outside the documented API. If your code
doesn't use them, skip this table.

| What changed | Replacement |
|---|---|
| `reconnect` / `reconnecting` `slot` values `'v2'` / `'v2Book'` (and the same slots on the chart's `data:reconnect` / `data:reconnecting`) | `'history'` / `'books'`. A listener keyed on the old names matches nothing. See [Reconnection](#reconnection). |
| `IIndicatorError['source']` gains `'entitlement'` | Add a case if you `switch` over every value ([License refusals](#license-refusals)) |
| `CalendarUnit` (from `@titancharts/react`) gains `'halfYear'` | Add a case if you `switch` over every value |
| `getServerUrls()` fields `v2Urls` / `v2BookUrls` | `historyUrls` / `booksUrls`. It returns `{ liveFeedUrls, historyUrls, booksUrls }`. |
| `fetchRawHistoricalData()` / `IRawHistoricalDataResult` (raw response chunks) | `fetchHistoricalData()`, which returns parsed rows |
| `fetchLatestSnapshots()` | No replacement |
| `fetchRelatedEvents()` | No replacement |
| exported `V2Service`, `V2ReconnectService`, `V2ReconnectCallbacks` | No replacement. Use the `DataFeed` methods. |
| `DataFeedDebug.v2Service` / `DataFeedDebug.reconnectService` | No replacement |
| `DataTypes.SYNTHETIC_BLOCK_BOOK_SNAPSHOT_AGG` from `@titancharts/react` (a synthetic orderbook-snapshot data type that no chart feature requested; `BuiltInDataType` no longer includes it) | No replacement |
| exported `parseOHLCVChunks`, `parseOpenInterestChunks`, `parseFundingRateChunks`, `parseLiquidationsChunks`, `parseCVDChunks`, `parseVolumeProfileChunks`, `parseTPOChunks`, `parseMultiOutcomeTimeline` | No replacement. `fetchHistoricalData()` returns parsed rows. |
| a `registerParser` parser for a built-in data type, such as OHLCV | No replacement. It receives data in an internal format; use `fetchHistoricalData()`. |
| a `modifiers.providerSpecific` key a request doesn't take | Each kind of request takes only certain keys; remove any other. A key the DataFeed doesn't know rejects the request. See [`providerSpecific` on the DataFeed](#providerspecific-on-the-datafeed). |
| HTML in indicator names, legend item `text` or `getLegendText` results (from `@titancharts/react`) | Plain text: the legend prints them as written. See [Text instead of HTML](#text-instead-of-html). |
| links, forms, tables, classes or `width` / `height` attributes in tooltip lines (`IAnnotation.tooltip`, `ITrade.tooltip`, `IReferenceLine.tooltip`) | Inline formatting tags and `style` only. See [Text instead of HTML](#text-instead-of-html). |

## Check the upgrade

After upgrading, open a chart and check that:

- it draws candles, and the market search lists markets;
- if you show orderbooks, an orderbook or heatmap draws and
  `dataFeed.getConnectionState().books` reaches `'connected'`. If it stays
  `'reconnecting'` and the heatmap says `No data available for this symbol`,
  the books address can't be reached: see [Server URLs](#server-urls);
- `dataFeed.getConnectionState().history` isn't `'auth_failed'`. (`'idle'`
  and `'disconnected'` are normal: `books` stays `'idle'` until an orderbook is
  shown, and a closed connection reopens on the next request.)

Only `servers.v2` and `servers.v2Book` log a deprecation warning. Search your
code for the other names in [Deprecated](#deprecated) too.

***

## Server URLs

If you let the DataFeed discover its servers (no `servers` option), you need no
change. If you set `servers`, rename `servers.v2` to `servers.history` and
`servers.v2Book` to `servers.books`. Use the addresses your TitanCharts account
contact gave you, or derive them by the rule below. The live-feed keys are
unchanged:

```ts
// v4.4
new DataFeed({
  licenseKey: '...',
  servers: {
    v2: 'wss://data.example.com/v2/',
    v2Book: 'wss://books.example.com/v2/',
    liveFeed: 'wss://your-livefeed-server.example.com',
  },
});

// v4.5
new DataFeed({
  licenseKey: '...',
  servers: {
    history: 'wss://data.example.com/v3/',
    books: 'wss://books.example.com/v3/',
    liveFeed: 'wss://your-livefeed-server.example.com',
  },
});
```

If a proxy of yours forwards these connections, find the addresses to forward
to by creating a DataFeed without `servers` and calling
`await dataFeed.getServerUrls()`. Point your proxy's history and books paths at
the `historyUrls` and `booksUrls` it returns. The books connection reaches the
same server as history, so a proxy forwards it to the history address.

`servers.v2` and `servers.v2Book` still work in 4.5 when `servers.history` and
`servers.books` are not set. The DataFeed derives each new address by changing
a `/v2` path segment to `/v3`: `wss://data.example.com/v2/` becomes
`wss://data.example.com/v3/`, as in the example above. It logs one warning
that names `servers.history` and `servers.books` (see
[Troubleshooting](../datafeed-api/titancharts-datafeed/configuration.md#error-handling)
for its text). If you set both the old and new keys, `servers.history` and
`servers.books` win.

An address with no `/v2` segment, such as `wss://proxy.example.com/tc/v2-book`,
is used unchanged, so 4.5 sends its requests to the old server there and they
fail from the first request. For a `servers.v2Book` address, candles may still
draw while orderbooks fail quietly: the heatmap says `No data available for
this symbol`, requests to that address time out every 45 seconds, and
`getConnectionState().books` stays `'reconnecting'` without a `reconnecting`
event.

To fix it behind a proxy, route a proxy path to the same `/v3` address as your
history path and set `servers.books` to that path, or remove `servers.v2Book`
and leave `servers.books` unset so orderbooks use the history address. Keep the
old `/tc/v2-book` route for apps still on 4.4 until they upgrade. For a
`servers.v2` address with no `/v2` segment, set `servers.history` the same way.

Set `servers.history` and `servers.books` yourself if your proxy serves the new
connection at another path, or if a firewall or Content-Security-Policy rule
allows specific paths. The old keys are deprecated and removed in the next
major version. Each `servers` key replaces only its own URL: the DataFeed
discovers the keys you leave out. If you set `servers.history` but not
`servers.books`, orderbooks use the `servers.history` address.

## Self-hosted worker

If you pass `workerUrl`, the worker file it points to must come from
`@titancharts/data` 4.5.0. The messages between the page and the worker
changed, so a worker file from 4.4 doesn't work with the 4.5.0 DataFeed. When
you upgrade, copy the worker file again from 4.5.0 (`@titancharts/data/worker`,
the file `dist/data.worker.js` in the package), or rebuild a custom worker that
imports it. If the chart connects but its data requests fail after the
upgrade, check that `workerUrl` serves the 4.5.0 file. Apps that don't set
`workerUrl` load the matching worker automatically.

## Reconnection

The reconnection types are now `ReconnectConfig` and `ReconnectState`, with the
same option names and values; five of the options now do nothing (below).
`V2ReconnectConfig` and `V2ReconnectState` still work as deprecated aliases:

```ts
// v4.4
import { type V2ReconnectConfig } from '@titancharts/data';
const reconnect: V2ReconnectConfig = { maxAttempts: 10 };

// v4.5: same options under the new name (maxAttempts now has no effect)
import { type ReconnectConfig } from '@titancharts/data';
const reconnect: ReconnectConfig = { hiddenThreshold: 600 };
```

The DataFeed has connections for `history` (candles, search and metadata),
`books` (orderbook history), and the live connections `liveFeed` and
`liveFeedBook` (the live orderbook).

- `history` and `books` connect when a request needs them. After a drop they
  stay closed until the next request reopens them; while a request waits, a
  failed connection attempt is retried.
- A request with no answer within 45 seconds fails (4.4 waited 15 seconds).
- `reconnecting` fires once per drop for every connection. Its `slot` field
  names the connection: `'liveFeed'`, `'liveFeedBook'`, `'liveFeedInCluster'`,
  `'history'` or `'books'`. The old `'v2'` / `'v2Book'` names are gone.
- `reconnect` follows: for a live feed when it's back, for history and books
  when the next request's connection is ready.
- For history and books, `reconnect` has `gapFill: false` and
  `heatmapRetry: false`: the chart forwards it as `data:reconnect`, with no gap
  fill and no `data:stale`. A live feed's `reconnect` still gap-fills.
- The chart forwards `reconnecting` as `data:reconnecting`, so a
  "reconnecting" banner can listen for it.
- A connection that doesn't answer a check within 10 seconds is closed. A live
  feed reopens on its own; history and books reopen with the next request.
- A wake, a tab resume or the network coming back tests the history and books
  connections at once and closes any that don't answer within 10 seconds.
- A live-feed connect that isn't ready within 25 seconds is retried.

The `reconnect` options `maxAttempts`, `baseDelay`, `maxDelay`,
`queueTimeout` and `reconnectTimeout` are deprecated and have no effect.

`getConnectionState()` returns `{ history, books }`, plus the deprecated
`v2` / `v2Book` keys with the same values. Each key reports its connection's
state whether or not `reconnect.enabled` is set; `'idle'` means the connection
hasn't connected yet:

```ts
const state = dataFeed.getConnectionState();
// 'idle' and 'disconnected' are normal; only 'auth_failed' needs action.
const broken = state.history === 'auth_failed' || state.books === 'auth_failed';
```

A connection that stays `'reconnecting'` while a request waits can't reach its
server; check its address. A connection whose refreshed access token is
refused reports `'auth_failed'` until the DataFeed gets a new token: fix your
license key or the token your `authProxy` returns, then call
`dataFeed.forceReconnect()`. See [Behavior changes](#behavior-changes).

## Removed methods, classes and parsers

`fetchRawHistoricalData()` returned the raw response chunks for you to pass to
the exported chunk parsers, or to read yourself. Both are removed, and raw
chunks are no longer available. Call `fetchHistoricalData()`, which returns
the parsed rows:

```ts
// v4.4
const raw = await dataFeed.fetchRawHistoricalData(params);
const candles = parseOHLCVChunks(raw.chunks);

// v4.5
const { data } = await dataFeed.fetchHistoricalData(params);
// data: [[timeMs, open, high, low, close, volume], ...]
```

`fetchLatestSnapshots()`, `fetchRelatedEvents()`, `V2Service`,
`V2ReconnectService`, `V2ReconnectCallbacks`, `IRawHistoricalDataResult`,
`parseMultiOutcomeTimeline`, `DataFeedDebug.v2Service` and
`DataFeedDebug.reconnectService` are removed with no replacement.

To parse a data type of your own in the worker, register a parser with
`registerParser` from `@titancharts/data/worker-registry`, as in v4.4.

A parser you register for a built-in data type, such as OHLCV, replaces the
built-in one. In 4.5.0 that parser receives data in an undocumented format
that may change, so a v4.4 parser for a built-in type stops working. The
chart's candles then never load: its loading spinner stays. The worker logs a
one-time warning when you register such a parser. Remove it and use the
built-in parsing through `fetchHistoricalData()`, or register parsers only for
data types of your own.

## `providerSpecific` on the DataFeed

`IQueryModifiers.providerSpecific` still accepts any key in
`@titancharts/react`, so a DataFeed you write yourself can read any key from
it. You set it in a request's `modifiers`, or in a control's `queryModifiers`
or `getQueryModifiers`. The DataFeed from `@titancharts/data` takes only these
keys, depending on the request. They are the keys the chart's built-in indicators send, so if you
never set `providerSpecific` yourself, nothing changes.

| Request | Keys it takes |
|---|---|
| Orderbook heatmap | `blockSize`, `maxDepth`, `flatten`, `sortDirection` |
| Hyperliquid liquidation and TP/SL heatmaps | `maxDepth`. `flatten` and `sortDirection` are accepted but have no effect: the chart orders the rows by time and merges them itself. |
| Prediction-market history across several outcomes | `rawSymbol` (an array of outcome ids), `gapfill`, `sortDirection` (`sortDirection` only on a count read) |
| Candle history | `gapfill`, `sortDirection` (`sortDirection` only on a count read) |
| CVD history | `sortDirection`, only on a count read |
| Open interest, funding rate, liquidations, volume profile and TPO history | None |

On a history request, `sortDirection` applies only to a count read (one with
`countBack`), and takes a number: `1` reads the oldest N bars of the window,
`2` the latest N. Without it, a count read with `direction: 'backward'` reads
the latest N and any other count read the oldest N. A count read with
`direction: 'backward'` returns its rows oldest first whatever the
`sortDirection`, and so do CVD reads and prediction-market reads across several
outcomes; any other candle read returns them oldest first with `1` and newest
first with `2`. The orderbook heatmap takes the same two values. The package exports no constant for them.
Any other value, such as the string `'DESCENDING'`, rejects the request with
`[TitanCharts] <dataType> takes "providerSpecific.sortDirection" 1 (ascending) or 2 (descending), not <value>`,
for example
`[TitanCharts] OHLCV takes "providerSpecific.sortDirection" 1 (ascending) or 2 (descending), not "DESCENDING"`.
A history request without `countBack` that sets `sortDirection` rejects with
an error saying the key needs `countBack`.

```ts
modifiers: { providerSpecific: { sortDirection: 2 } } // the latest N bars
```

In 4.4 the DataFeed sent any other key on to the server. In 4.5 there's no
replacement: remove any key a history or orderbook request doesn't take. A key
outside the six above rejects the request with
`modifiers.providerSpecific.<key> is not supported; this DataFeed reads only rawSymbol, gapfill, blockSize, maxDepth, flatten, sortDirection`;
a known key on a request that doesn't take it rejects with an error that
names the key.

Live subscriptions take their options through the `settings` field of
`subscribeLive()`'s params (for example the orderbook `blockSize`). A `providerSpecific` value on a live subscription is
still merged into it if it's a string. A non-string value, for any key, is
left out, with a one-time warning:
`modifiers.providerSpecific.<key> is left out of live updates, which accept only text values`.
A string value for a key outside the six above is passed on unchanged, with a one-time warning:
`modifiers.providerSpecific.<key> is not a setting this DataFeed knows; live updates pass it on unchanged`.

## Behavior changes

These names are unchanged, but what they return differs:

- `getSymbolMetadata()` leaves `pricePrecision`, `quantityPrecision` and
  `contractType` unset.
- `searchCoins()`, `searchSymbols()` and `fetchCategories()` cover crypto
  exchanges only. Without an exchange they list crypto markets.
- With a license scoped to certain exchanges, `searchExchanges()` and
  `searchCoins()` without an exchange cover only the license's crypto
  exchanges (Hyperliquid HIP-3 included). A license with no crypto exchange
  gets an empty list, not an error.
- Search results aren't in a fixed order; sort them yourself if your UI needs
  one.
- Market lists leave out exchanges the DataFeed doesn't serve:
  `searchExchanges()`, `searchMarket()`, `searchCoins()`, `searchSymbols()` and
  `getExchangesForSymbol()`.
- `fetchPolymarketMarkets()` with an empty slug list rejects; skip the call
  when you have no slugs.
- A history request for a crypto market that does not exist rejects, where
  v4.4 returned no rows.
- A request for an exchange the DataFeed doesn't serve rejects with an error
  that names the exchange.
- On a chart that takes the DataFeed, the chart method `getIntervals()` lists
  25 intervals, `1s` to `1y`, instead of the nine built-ins, on every market.
  An interval picker you build yourself can leave out the ones
  `chart.isIntervalSupportedByFeed(code)` returns `false` for, as the chart's
  interval menu does.
- A custom `fetchMainData` or `data:request` handler no longer receives
  `from: 0` for a count read: `from` is the read's estimated start (at least
  1 second). `computeCountBackFrom()` is deprecated but still exported and
  works as before.
- The chart's `setVisibleRange()` and the Objects Panel's `showOnChart()`
  hold to `settings.maxBarCount` (default 900 bars). A wider
  range keeps its newest bars, `viewport:clamped` fires, and the trimmed range
  is what `getState()` saves. Raise `maxBarCount` in `settings` if your app
  shows wider ranges.
- With `settings.axisDragPinsScale` on (the default), a vertical drag in the
  chart body no longer pins the price scale, so it no longer sets
  `getState().yAutoScale` to `false` or fires `pricescale:auto-change`. An axis
  drag, wheel or pinch on the main pane while it is off auto without a pin, as
  after such a drag, pins it and fires `pricescale:auto-change`.
- An orderbook heatmap for a prediction-market id that matches no market shows
  `No market or outcome matches <symbol>` and stops reading, and the read
  rejects with `MarketNotFoundError`, exported from `@titancharts/react`. A real
  outcome with no orderbook shows an empty heatmap.
- When the server refuses the access token, the DataFeed refreshes it once and
  reconnects. This covers every connection, live feed included, and they share
  one refresh per refused token.
- If the refreshed token is also refused, or no new token arrives within 35
  seconds, that connection stops. Its `history` or `books` key in
  `getConnectionState()` reports `'auth_failed'` until the next scheduled token
  refresh, which goes through your `authProxy` if you use one, or until you
  call `forceReconnect()`.
- `onAuthError` is called and `auth:expired` fires once per refused token, even
  when several connections are refused.
- `onAuthStateChange` goes to `'error'` when the refreshed token is refused, or
  when no new token arrives within 35 seconds, and back to `'authenticated'` on
  the next token.
- The DataFeed `error` event is deprecated and never fires. Listen for
  `auth:expired` (or `onAuthError`) and `reconnecting`. Other failures reject
  the request that hit them.

## Chart look and drawing tools

4.5.0 redraws the chart in places your users will see. No method, prop,
event or setting is removed or renamed, but the chart looks different, and
screenshot or pixel tests need new baselines. The
[release notes](react.md#v4.5.0-new-data-connection) list every change. The
ones you can tune:

- **Price-axis pills** fit their number and are 17 px tall at the default axis
  font. For full-width pills, call `chart.setPriceLabelMode('fill')`; for 22 px
  pills, set `priceLabelPaddingY: 5.5` in the main chart settings.
- **Anchored VWAP** colors its rising and falling stretches with the candle
  colors. To draw it in one color, turn off **Color by Direction** in its
  settings, or set `colorByDirection: false` in the drawing's style.
- **The offchart legend eye** keeps its 4.4 behavior: with the default
  `offchartEyeMode` it folds the legend row and leaves the plot drawn;
  `'hide-plot'` hides the plot.
- **Candle corners** keep their rounded default (`canvas.candle.bodyRadius`,
  6), and the chart keeps its default pixel-ratio cap of 2
  (`maxDevicePixelRatio`).

## Text instead of HTML

The legend prints indicator names, including names set with
`renameIndicator()`, legend item `text` and `getLegendText` results as
written, never as HTML: `<b>` shows as the characters `<b>`. In 4.4 the legend
rendered their markup. Remove markup from names and legend text, and set an
item's color with its `color`.

Tooltip lines (`IAnnotation.tooltip`, `ITrade.tooltip` and
`IReferenceLine.tooltip`) still render markup, but keep only the tags `b`,
`strong`, `i`, `em`, `u`, `s`, `span`, `div`, `br`, `img`, `sup`, `sub` and
`code`, with the attributes `style`, `src`, `alt` and `title`. Link, form and
table tags, classes, and `width` and `height` attributes are dropped, and
`style` drops declarations that position, layer or transform an element, such
as `position`, `z-index` and `transform`, and any `url()`. Color and weight
through `<span style="…">` work as before:

```ts
// Works as before
const pnl = ['<span style="color:#23a776;font-weight:600">+$54.72</span>'];

// v4.4: in 4.5 the link prints as plain text and the width attribute is dropped
const before = ['<a href="https://example.com">Details</a>', '<img src="/logo.svg" width="16">'];

// v4.5: size the image in its style, and open links from your own UI
const after = ['Details', '<img src="/logo.svg" style="width:16px;height:16px">'];
```

## License refusals

When the license does not cover an indicator's or a heatmap's data, the chart
shows a license error on it instead of an empty pane. For a
[Tier 2](../features/indicators/tier2-advanced.md) indicator,
the message is `<indicator name> is not included in your license`. The chart
stops reading that data until the market or interval changes.

Listen for `indicator:error` to handle it in your app. A license refusal on an
indicator or a heatmap arrives with:

- `source: 'entitlement'`, a new value of `IIndicatorError['source']`;
- `error`, an `EntitlementError` exported from `@titancharts/react`, whose
  `dataType` names the refused data type as your app requests it (for example
  `CVD`);
- the `phase` (`'fetch'` or `'refetch'`) and `purpose` that a failed history
  load for the same reason reports.

`indicator:data-status` reports `idle` for that indicator, not `empty`.

```ts
import { EntitlementError } from '@titancharts/react';

chart.on('indicator:error', ({ instanceId, error }) => {
  if (error instanceof EntitlementError) showUpgradePrompt(instanceId, error.dataType);
});
```

## Several TPs and SLs, and commit on drop (4.5.1)

`@titancharts/react` 4.5.1 adds `bracketSlots`, `commitMode`, `rejectCommit()` and `commitTimeoutMs`. All are opt-in. If you built these behaviors yourself on 4.5.0, replace the workaround:

| Today (workaround) | With 4.5.1 |
|--------------------|-----------|
| TP/SLs as plain limit and stop orders, with `tpReadOnly` and `slReadOnly` on the position | Push them as TP/SL orders (`parentId` is the position's `id`, plus `bracketRole`) and drop the read-only flags. Set `positions.bracketSlots = { tp: N, sl: N }` to your venue's limits. |
| A custom `segments` pill with the label, size and P&L clamped to the position's size | Optional: the default TP/SL pill shows the size and the clamped P&L. A TP/SL order draws your `segments` if you keep them. |
| Saving on `trading:drag_end`, with Confirm and Discard hidden as zero-width transparent buttons | Set `commitMode: 'drop'` (or `trading={{ commitMode: 'drop' }}`). Save on `trading:bracket_create` (with `size`), `trading:order_modify` and `trading:bracket_cancel`, not on `trading:drag_end`. Remove the `buttons.confirm` and `buttons.discard` overrides. |
| Setting the position's `tpReadOnly` or `slReadOnly` once a slot is filled | Remove it: `bracketSlots` hides the handle when the slots are full. |
| No way to refuse a commit | Place the TP/SL order with the `bracketId` from `trading:bracket_create`. When your venue refuses, call `rejectCommit({ bracketId }, reason)`, or `rejectCommit({ orderId }, reason)` for a move. In `'drop'` mode a commit with no answer rolls back after 10 seconds and fires `trading:commit_timeout`; set `commitTimeoutMs` to change that, or to get the same safety in `'line'` and `'group'`. |
| Sizes chosen by your venue | `trading:bracket_create`'s `size` proposes the part of the parent no TP/SL covers. Place it, or your own size. If your venue would refuse a drop, refuse it in `validatePlacement` so the reason shows on the badge. |

See [Several TPs and SLs per position](../trading/patterns.md#several-tps-and-sls-per-position), [Answering commits](../trading/patterns.md#answering-commits) and [Committing on drop](../trading/patterns.md#committing-on-drop).

## Unchanged

- `supportsDataType()` and `registerIntervalEnumResolver()`.
- `protoKeyToSlug()`, `slugToProtoKey()` and `formatExchangeName()`, and the
  DataFeed's `formatExchange()` method.
- Live data: `subscribeLive()` and the `liveFeed` and `liveFeedBook` servers.
- No `@titancharts/react` prop or method is removed or renamed. A chart that
  takes the DataFeed through `<TitanChart dataFeed>` needs no code change.
