@trdrs/quickcharts 1.3.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +209 -200
  3. package/THIRD-PARTY-NOTICES.md +4 -4
  4. package/dist/adapters/rest.d.ts +3 -3
  5. package/dist/adapters/rest.js +2 -2
  6. package/dist/adapters/rest.js.map +1 -1
  7. package/dist/{ar-G7MVXRE4.js → ar-KSXQ2USS.js} +20 -20
  8. package/dist/ar-KSXQ2USS.js.map +1 -0
  9. package/dist/{assets-ySndlUS3.d.ts → assets-DOuBBsOp.d.ts} +50 -50
  10. package/dist/{ca_ES-EUSRU4VG.js → ca_ES-JG4FM55E.js} +20 -20
  11. package/dist/ca_ES-JG4FM55E.js.map +1 -0
  12. package/dist/{chunk-KFBQSPNA.js → chunk-D3S26ARP.js} +2 -2
  13. package/dist/chunk-D3S26ARP.js.map +1 -0
  14. package/dist/{chunk-AKXIFHQN.js → chunk-KLPTCWA2.js} +1 -1
  15. package/dist/{chunk-AKXIFHQN.js.map → chunk-KLPTCWA2.js.map} +1 -1
  16. package/dist/{chunk-AA7XKXH4.js → chunk-YR7NT5ZW.js} +67 -33
  17. package/dist/chunk-YR7NT5ZW.js.map +1 -0
  18. package/dist/{de-2SVTG25S.js → de-VSW2LHNF.js} +20 -20
  19. package/dist/de-VSW2LHNF.js.map +1 -0
  20. package/dist/drawings.d.ts +16 -16
  21. package/dist/drawings.js +12 -12
  22. package/dist/{es-UDHZM4W7.js → es-5ZXIPVOB.js} +20 -20
  23. package/dist/es-5ZXIPVOB.js.map +1 -0
  24. package/dist/feature-manifest.json +3 -3
  25. package/dist/{fr-35VN4G4S.js → fr-4F6DAWGD.js} +20 -20
  26. package/dist/fr-4F6DAWGD.js.map +1 -0
  27. package/dist/{he_IL-JPZMCURP.js → he_IL-B7PBQUEV.js} +20 -20
  28. package/dist/he_IL-B7PBQUEV.js.map +1 -0
  29. package/dist/{id_ID-XSTFHHJQ.js → id_ID-CYJRMXPC.js} +20 -20
  30. package/dist/id_ID-CYJRMXPC.js.map +1 -0
  31. package/dist/index.d.ts +167 -163
  32. package/dist/index.js +342 -268
  33. package/dist/index.js.map +1 -1
  34. package/dist/{it-EIY6JESQ.js → it-KTX4SG5P.js} +20 -20
  35. package/dist/it-KTX4SG5P.js.map +1 -0
  36. package/dist/{ja-42N4XRCW.js → ja-V7OURU5D.js} +20 -20
  37. package/dist/ja-V7OURU5D.js.map +1 -0
  38. package/dist/{ko-3VRPTIP3.js → ko-2RNEBYVE.js} +20 -20
  39. package/dist/ko-2RNEBYVE.js.map +1 -0
  40. package/dist/{ms_MY-SKN7O6M5.js → ms_MY-EY235E7Q.js} +20 -20
  41. package/dist/ms_MY-EY235E7Q.js.map +1 -0
  42. package/dist/{pl-TYARHCLT.js → pl-ADNVRP55.js} +20 -20
  43. package/dist/pl-ADNVRP55.js.map +1 -0
  44. package/dist/{pt-72IIFLL4.js → pt-OL2DR3DO.js} +20 -20
  45. package/dist/pt-OL2DR3DO.js.map +1 -0
  46. package/dist/quickcharts.css +97 -96
  47. package/dist/{resources-Ch8eA1Mw.d.ts → resources-Le6flcEn.d.ts} +3 -3
  48. package/dist/rest-openapi.json +11 -11
  49. package/dist/{ru-TPH4F7IB.js → ru-V6YFEPAF.js} +20 -20
  50. package/dist/ru-V6YFEPAF.js.map +1 -0
  51. package/dist/{sv-DC5RS67Z.js → sv-TORXQVE7.js} +20 -20
  52. package/dist/sv-TORXQVE7.js.map +1 -0
  53. package/dist/{th-I6H6AOZ3.js → th-K6SJSYSY.js} +20 -20
  54. package/dist/th-K6SJSYSY.js.map +1 -0
  55. package/dist/theme-manifest.json +10 -10
  56. package/dist/{tr-7FSUVCNW.js → tr-4AEM66KW.js} +20 -20
  57. package/dist/tr-4AEM66KW.js.map +1 -0
  58. package/dist/{vi-DLRPHLLH.js → vi-YU5XCEUU.js} +20 -20
  59. package/dist/vi-YU5XCEUU.js.map +1 -0
  60. package/dist/{zh-ZXIQBBEP.js → zh-QXKRYHU5.js} +20 -20
  61. package/dist/zh-QXKRYHU5.js.map +1 -0
  62. package/dist/{zh_TW-TYNUJU7U.js → zh_TW-AYPDKG4Q.js} +20 -20
  63. package/dist/zh_TW-AYPDKG4Q.js.map +1 -0
  64. package/package.json +2 -2
  65. package/dist/ar-G7MVXRE4.js.map +0 -1
  66. package/dist/ca_ES-EUSRU4VG.js.map +0 -1
  67. package/dist/chunk-AA7XKXH4.js.map +0 -1
  68. package/dist/chunk-KFBQSPNA.js.map +0 -1
  69. package/dist/de-2SVTG25S.js.map +0 -1
  70. package/dist/es-UDHZM4W7.js.map +0 -1
  71. package/dist/fr-35VN4G4S.js.map +0 -1
  72. package/dist/he_IL-JPZMCURP.js.map +0 -1
  73. package/dist/id_ID-XSTFHHJQ.js.map +0 -1
  74. package/dist/it-EIY6JESQ.js.map +0 -1
  75. package/dist/ja-42N4XRCW.js.map +0 -1
  76. package/dist/ko-3VRPTIP3.js.map +0 -1
  77. package/dist/ms_MY-SKN7O6M5.js.map +0 -1
  78. package/dist/pl-TYARHCLT.js.map +0 -1
  79. package/dist/pt-72IIFLL4.js.map +0 -1
  80. package/dist/ru-TPH4F7IB.js.map +0 -1
  81. package/dist/sv-DC5RS67Z.js.map +0 -1
  82. package/dist/th-I6H6AOZ3.js.map +0 -1
  83. package/dist/tr-7FSUVCNW.js.map +0 -1
  84. package/dist/vi-DLRPHLLH.js.map +0 -1
  85. package/dist/zh-ZXIQBBEP.js.map +0 -1
  86. package/dist/zh_TW-TYNUJU7U.js.map +0 -1
package/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # @trdrs/quickcharts
2
2
 
3
- Quick Charts is a charting library that draws over the datafeed and the storage you supply. The chart
4
- consumes the `ChartDatafeed` interface and never a concrete backend, so your feed drives it without a
5
- change to the chart. It does not include market data, trading, accounts, execution, community, news
6
- or hosting: the host supplies data and storage, and the chart draws.
3
+ Quick Charts is a datafeed-driven charting library with drawings, indicators, layouts and replay.
4
+ It draws over the datafeed and the storage you supply: the chart consumes the `ChartDatafeed`
5
+ interface, so your feed drives it without a change to the chart.
7
6
 
8
7
  ## Install
9
8
 
@@ -21,8 +20,8 @@ runtime. Use default peer resolution; do not suppress peer checks.
21
20
  Quick Charts is licensed under the Apache License 2.0: see `LICENSE` and `NOTICE`. Because the
22
21
  renderer is *your* dependency, its own Apache-2.0 NOTICE
23
22
  obligations attach to **your** bundle: `THIRD-PARTY-NOTICES.md` in this package spells out exactly
24
- what to carry and how. The chart includes no trading, accounts or executions; an application that
25
- trades composes those outside the chart, through the extension seam below.
23
+ what to carry and how. An application composes its own features on the chart through the extension
24
+ seam below.
26
25
 
27
26
  Quickstart, the smallest working chart (see [The widget](#the-widget) for the full options). The
28
27
  stylesheet import is not optional: it carries the chart's layout as well as its look, and without it
@@ -44,9 +43,8 @@ await widget.ready()
44
43
  Implement `ChartDatafeed` (see `datafeed.ts`). Required methods: `search`, `resolve`, `history`,
45
44
  `subscribeBars`. Optional: `serverTime` (countdown skew correction) and `config` (a feed-level
46
45
  capability declaration, described [below](#capability-declaration-config-optional)). The datafeed serves
47
- symbol metadata, bars and bar updates and nothing else: a quote board (last, change, volume) or a
48
- top-of-book is not a chart concern, and your host fans quotes to its own consumers from its own
49
- source.
46
+ symbol metadata, bars and bar updates; your host serves quotes (last, change, volume, the top of
47
+ book) to its own consumers from its own source.
50
48
 
51
49
  When `appearance.countdown` is enabled, a live streaming time bar replaces the native last-value
52
50
  label with one price-and-time label. The price uses the resolved symbol formatter. Replay, stale,
@@ -176,8 +174,8 @@ export const feed: ChartDatafeed = {
176
174
  ```
177
175
 
178
176
  Declare only what is **true**. Absent method / absent field / empty list = unconstrained, so a feed that
179
- serves any interval must not declare a finite `resolutions` list, because the widget then enforces it.
180
- The UDF adapter declares automatically from the server's own `/config` (and only ever declares
177
+ serves any timeframe must not declare a finite `resolutions` list, because the widget then enforces
178
+ it. The UDF adapter declares automatically from the server's own `/config` (and only ever declares
181
179
  timeframes it would actually serve).
182
180
 
183
181
  ## The UDF on-ramp
@@ -213,10 +211,11 @@ What the adapter honors of the protocol:
213
211
 
214
212
  Symbology is the set of display facts that decide how a market's prices are written. Your datafeed
215
213
  owns them (`resolve` answers with `SymbolInfo`), and one package formatter uses them everywhere a
216
- price appears: the price scale, the crosshair and last-price labels, the legend, the level menu,
217
- every drawing label, study scales, and the extension seam. Precision comes from the symbol, never
218
- from the size of the price, so the same market reads at the same width on every surface. A study
219
- that declares its own precision keeps it; every other value writes through the symbol formatter.
214
+ price appears: the price scale, the crosshair and last-price labels, the legend, the context menu,
215
+ every drawing label, indicator scales, and the extension seam. Precision comes from the symbol,
216
+ never from the size of the price, so the same market reads at the same width on every surface. An
217
+ indicator that declares its own precision keeps it; every other value writes through the symbol
218
+ formatter.
220
219
 
221
220
  `PriceFormat` expresses every supported form in five facts:
222
221
 
@@ -303,8 +302,8 @@ symbolNames({ symbol: 'NASDAQ:AAPL', name: 'Apple Inc', exchange: 'NASDAQ', type
303
302
  symbolNames('HYPERLIQUID:ETH').mark // 'ETH', before the symbol resolves
304
303
  ```
305
304
 
306
- Symbology is display truth, not trading truth. It carries no order quantity, price step, lot size,
307
- or pip value. Your broker integration owns those, and a broker's execution grid can differ from a
305
+ Symbology is display truth: how the chart writes a market's prices. Execution facts, such as lot
306
+ size and pip value, stay with your trading integration, and its execution grid can differ from the
308
307
  chart's display grid.
309
308
 
310
309
  If your data comes from a UDF server, map its `/symbols` answer without collapsing the facts:
@@ -322,14 +321,14 @@ info?.dataStatus // 'streaming'
322
321
 
323
322
  Quick Charts persists two different kinds of thing, and the difference decides where each one lives.
324
323
 
325
- Saved charts, layouts, drawing documents and templates are **entities**: a user names them, opens
324
+ Saved charts, layouts, drawing documents and templates are **entities**: a viewer names them, opens
326
325
  them in two tabs, and can lose them. They have identity and versions, and they live on the
327
326
  save/load adapter you supply. Viewer preferences (the last symbol and timeframe, the scale mode, the
328
327
  replay speed) are **flat settings**: they need no identity, and they live on the small `ChartStorage`
329
328
  port beside it.
330
329
 
331
330
  The chart never writes on its own. It tells you when a save would be worth making, through the
332
- `saveNeeded` event, and it runs a write only when a user asks for one through the built-in UI or
331
+ `saveNeeded` event, and it runs a write only when a viewer asks for one through the built-in UI or
333
332
  when you call a save method yourself.
334
333
 
335
334
  ### What a saved entity carries
@@ -367,7 +366,7 @@ changes, and a menu with Save, an Autosave switch, Make a copy, Rename, Download
367
366
  new layout, the layouts used most recently, and Open layout: the Layouts dialog, with search, a sort
368
367
  by name or by date modified, a star that keeps a layout at the top, and delete behind a
369
368
  confirmation. The sort and the stars are the viewer's, kept in the widget's `ChartStorage`. Each
370
- saved layout is listed by the market and interval its active chart showed when
369
+ saved layout is listed by the market and timeframe its active chart showed when
371
370
  it was saved, or by its age where the store kept neither.
372
371
 
373
372
  Every row runs a command (`widget.layout.save`, `rename`, `load`, `open`, `delete`, `create`, `autosave`,
@@ -417,7 +416,7 @@ const adapter = memorySaveLoadAdapter()
417
416
  const controller = new AbortController()
418
417
 
419
418
  await adapter.layouts.list(controller.signal)
420
- await adapter.templates('study').list()
419
+ await adapter.templates('indicator').list()
421
420
  const context = { version: 1, kind: 'symbol-global', symbol: 'ESZ2026' } as const
422
421
  await adapter.drawings(context).create(emptyDrawingDocument(context))
423
422
 
@@ -428,8 +427,9 @@ outcome.kind // 'not-found'
428
427
 
429
428
  An aborted call rejects with an error named `AbortError` and changes nothing.
430
429
 
431
- `memorySaveLoadAdapter` persists nothing. Use it for tests, server rendering and an intentionally
432
- ephemeral embed, and implement the same contract over your own backend for durable storage.
430
+ `memorySaveLoadAdapter` keeps everything in memory for the life of the page. Use it for tests,
431
+ server rendering and an intentionally ephemeral embed, and implement the same contract over your
432
+ own backend for durable storage.
433
433
 
434
434
  ### The REST adapter and its wire contract
435
435
 
@@ -447,10 +447,11 @@ const restSaves = createRestSaveLoadAdapter({
447
447
  await restSaves.layouts.list()
448
448
  ```
449
449
 
450
- You supply two values and nothing else. `baseUrl` is where you mounted the routes, and `request` is
451
- how a request is made. Authorization, cookies, CORS, retries, timeouts and tenancy stay in your
452
- request function, because their consequences are yours: the adapter holds no credential, no token
453
- store, no header policy and no default origin, and it makes no request until you call a verb.
450
+ You supply two values. `baseUrl` is where you mounted the routes, and `request` is how a request is
451
+ made. Authorization, cookies, CORS, retries, timeouts and tenancy stay in your request function,
452
+ because their consequences are yours: the adapter builds each request from `baseUrl` and hands it to
453
+ your function, which adds the credentials and headers your service needs, and it makes a request
454
+ only when you call a verb.
454
455
 
455
456
  Your service serves four collections under that base URL. Every path below is relative to it.
456
457
 
@@ -532,8 +533,8 @@ remain in step while the menu is open; transient tile maximize does not change t
532
533
  names.
533
534
 
534
535
  Growing a layout keeps every existing chart instance and initializes each new tile from pane 0's
535
- safe presentation: symbol, timeframe, style, studies and comparisons. Mutable study configuration
536
- is copied per tile; replay, session state and saved-chart identity are not copied.
536
+ safe presentation: symbol, timeframe, style, indicators and comparisons. Mutable indicator
537
+ configuration is copied per tile; replay, session state and saved-chart identity are not copied.
537
538
 
538
539
  `load` answers rather than rejecting. Five kinds, told apart without reading a message:
539
540
 
@@ -587,13 +588,13 @@ switch (opened.kind) {
587
588
  A load that a later load supersedes is `cancelled`, with no signal of your own involved, and only
588
589
  the later load binds. This includes a later load begun synchronously by a chart callback during
589
590
  application. A widget disposed while the store is still answering answers the same way. That is
590
- what keeps a slow answer for the chart a user has left from landing on the chart they are looking
591
+ what keeps a slow answer for the chart a viewer has left from landing on the chart they are looking
591
592
  at.
592
593
 
593
594
  Where a body fails after part of it has been applied, the chart puts back the content it held. If
594
595
  that fails too the chart is holding neither, and it stops saving: `saveLoad.notSaving()` reports it,
595
596
  `save` answers `not-saving` and writes nothing, the built-in layouts menu says so on the toolbar and
596
- stands its Save and its autosave down, and the sentence for the trader arrives on `saveConflict`.
597
+ stands its Save and its autosave down, and the sentence for the viewer arrives on `saveConflict`.
597
598
 
598
599
  Saving starts again when a load lands, and only then: a whole content out of the store, applied in
599
600
  full, under the resource it came from. `save(name, { asNew: true })` stays available throughout, and
@@ -623,7 +624,7 @@ Complete recovery requires every typed appearance leaf with a valid concrete col
623
624
  in combined mode, a drawings array. Separate-mode chart content does not need that array.
624
625
 
625
626
  Hydration and rollback do not emit `saveNeeded` for their own storage writes. An event already
626
- queued by a user edit is preserved. A late save result does not replace a newer load's binding;
627
+ queued by a viewer's edit is preserved. A late save result does not replace a newer load's binding;
627
628
  the built-in commands announce a saved layout only while that result is still current.
628
629
 
629
630
  ### Drawings: combined or separate
@@ -676,18 +677,18 @@ tile.
676
677
 
677
678
  ### Templates
678
679
 
679
- `adapter.templates(kind)` is a store per kind: `'study'` for indicator settings, `'drawing'` for a
680
- drawing tool's look, and `'palette'` for chart appearance. A drawing template carries the `tool` it
681
- belongs to, because a trend-line template means nothing on a rectangle. Their content is opaque,
680
+ `adapter.templates(kind)` is a store per kind: `'indicator'` for indicator settings, `'drawing'` for
681
+ a drawing tool's look, and `'palette'` for chart appearance. A drawing template carries the `tool`
682
+ it belongs to, because a trend-line template means nothing on a rectangle. Their content is opaque,
682
683
  like a chart's.
683
684
 
684
- ### User settings
685
+ ### Viewer settings
685
686
 
686
687
  The widget keeps the viewer's flat preferences in a `ChartStorage`. Every key is an opaque string in
687
688
  the `quickcharts.` namespace, and every value is an opaque string; a store routes or scopes them and
688
- reads neither. The default is an in-memory store that lasts the page; supply your own to keep them
689
- per device or per account. A browser store is a few lines you write; it is not part of the package,
690
- because a device-local default is not a persistence architecture:
689
+ treats both as opaque. The default is an in-memory store that lasts the page; supply your own to
690
+ keep them per device or per viewer. A browser store is a few lines you write and own, so where
691
+ preferences live stays your decision:
691
692
 
692
693
  ```ts
693
694
  import { memoryChartStorage, type ChartStorage } from '@trdrs/quickcharts'
@@ -710,7 +711,7 @@ live on the saved-resource adapter above, never here.
710
711
  A conflict means someone else wrote the same entity after the copy on screen was read. The chart
711
712
  never resolves that by overwriting. It reports the case with a sentence from its own catalog and the
712
713
  ref that stands now, and leaves the decision to you: reload and lose the local edit, save a copy
713
- under a new name, or show the user both.
714
+ under a new name, or show the viewer both.
714
715
 
715
716
  Reach the report in two places. A verb you called answers a typed `conflict` outcome. A write the
716
717
  widget made for the viewer, through the built-in UI or its own drawing sync, arrives on the
@@ -863,7 +864,7 @@ widget.dispose() // teardown; every handle and subscription is inert afterwards
863
864
  The widget paints the main series and volume, applies live updates by the bar rules above, pages
864
865
  older history in as the viewer scrolls left (stopping at the feed's `noData`), persists the viewer's
865
866
  preferences through `ChartStorage`, runs configured indicator instances through the manifest
866
- pipeline, and mounts the drawing layer, the legend and the level menu.
867
+ pipeline, and mounts the drawing layer, the legend and the context menu.
867
868
 
868
869
  When older main or comparison history extends the renderer's shared timeline, the chart retains
869
870
  the latest fractional logical window, spacing and permitted off-data padding. Its compensating
@@ -875,7 +876,7 @@ the maintenance span or either endpoint. Public range methods and built-in navig
875
876
  enter that same epoch, so every supported navigation door has the same precedence.
876
877
 
877
878
  `toolbarContainer` optionally supplies a separate element for the top toolbar, such as a space
878
- between a host's navigation and account controls. The widget appends one package-owned, themed
879
+ between a host's navigation and its other controls. The widget appends one package-owned, themed
879
880
  child there; it never clears or styles the supplied element. The toolbar follows the widget's
880
881
  theme, language and active chart, and its child is removed at disposal. The toolbar's menus and
881
882
  dialogs open in the widget's layer on the document body, at viewport coordinates, so each menu
@@ -885,12 +886,13 @@ toolbar available in fullscreen. Without `toolbarContainer`, the toolbar sits ab
885
886
  part of the default composition. When horizontal space is limited, the toolbar scrolls without
886
887
  dropping controls from the keyboard order.
887
888
 
888
- `drawingToolbarContainer` supplies separate host space for a single drawing rail. The package
889
+ `drawingToolbarContainer` supplies separate host space for a single drawing toolbar. The package
889
890
  fills that space's height with its own themed child, follows the active chart when a layout
890
- changes, and removes the child on disposal. An external rail leaves the full chart width for the
891
- canvas; without the option, each chart keeps its internal rail and reserved leading column.
892
- Drawing flyouts stay beside the external rail within the widget's bounds regardless of the active
893
- pane. Switching charts closes an open flyout before the rail targets the newly active chart.
891
+ changes, and removes the child on disposal. An external drawing toolbar leaves the full chart width
892
+ for the canvas; without the option, each chart keeps its internal drawing toolbar and reserved
893
+ leading column. Drawing flyouts stay beside the external drawing toolbar within the widget's bounds
894
+ regardless of the active pane. Switching charts closes an open flyout before the drawing toolbar
895
+ targets the newly active chart.
894
896
  Include this container in the custom fullscreen target alongside the chart and top toolbar.
895
897
 
896
898
  ### Widget and chart
@@ -982,7 +984,7 @@ delete, and there is no custom-timeframe composer. The viewer's saved chips show
982
984
  them. When you list none of them, the chips are your five smallest timeframes, and the viewer's
983
985
  stored chips stay stored for a chart that offers them. With one timeframe offered the picker is not
984
986
  shown. `ui.topBar.timeframes: false` hides the picker on its own terms. A range preset whose
985
- interval you leave out reads its span at your nearest coarser timeframe, or at your largest when
987
+ timeframe you leave out reads its span at your nearest coarser timeframe, or at your largest when
986
988
  none is coarser. Within what you offer, the feed's `resolutions` and the symbol's
987
989
  `supportedResolutions` still apply: a chip the feed or the symbol does not serve is disabled, and
988
990
  the picker's list leaves its row out.
@@ -1052,7 +1054,7 @@ picture or as data.
1052
1054
  By default a chart offers every drawing tool. `drawingTools` names the tools you offer, by the type
1053
1055
  `access.drawingTool` and the `tool.<type>` icon ids use: a type `drawingTools.all()` lists (from
1054
1056
  `@trdrs/quickcharts/drawings`), `zoom` or `eraser`. The order you give does not matter, because the
1055
- rail keeps its own groups and sections:
1057
+ drawing toolbar keeps its own groups and sections:
1056
1058
 
1057
1059
  ```ts
1058
1060
  import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
@@ -1068,14 +1070,14 @@ createChart({ container, datafeed, drawingTools: ['eraser'] })
1068
1070
 
1069
1071
  A tool you leave out is not drawn anywhere a tool is chosen: not in its group's flyout, not as a
1070
1072
  group's face, which wears the first tool the group offers, not on the favorites bar, and not on the
1071
- rail for `measure` and `zoom`. A section or group it empties goes with it. The glyph picker's kinds
1072
- are the `emoji`, `sticker` and `icon` tools, so a kind you leave out has no tab, and leaving out all
1073
- three removes the glyph group. Every door that would arm it refuses: `chart.drawings.arm` answers
1074
- `denied`, and `armTool`, `placeImage` and an image pasted over the chart place nothing. A copy is a
1075
- new drawing, so a drawing of a tool you leave out is not copied into another: `chart.drawings.clone`
1076
- and `chart.drawings.paste` are unavailable for it, `clone` and `paste` make nothing, and a
1077
- Control- or Command-drag moves the drawing instead of duplicating it. Copying it to the clipboard is
1078
- not refused, so it pastes on a chart that offers its tool.
1073
+ drawing toolbar for `measure` and `zoom`. A section or group it empties goes with it. The glyph
1074
+ picker's kinds are the `emoji`, `sticker` and `icon` tools, so a kind you leave out has no tab, and
1075
+ leaving out all three removes the glyph group. Every door that would arm it refuses:
1076
+ `chart.drawings.arm` answers `denied`, and `armTool`, `placeImage` and an image pasted over the
1077
+ chart place nothing. A copy is a new drawing, so a drawing of a tool you leave out is not copied
1078
+ into another: `chart.drawings.clone` and `chart.drawings.paste` are unavailable for it, `clone` and
1079
+ `paste` make nothing, and a Control- or Command-drag moves the drawing instead of duplicating it.
1080
+ Copying it to the clipboard is not refused, so it pastes on a chart that offers its tool.
1079
1081
 
1080
1082
  Drawings of a tool you leave out that are already on the chart, from a saved chart or layout, a
1081
1083
  drawings document or another host, render and stay fully editable. They select, restyle through the
@@ -1095,7 +1097,7 @@ that names no tool and a repeated type are setup errors that `createChart` throw
1095
1097
 
1096
1098
  By default a chart offers every built-in indicator. `builtInIndicators` names the built-ins you
1097
1099
  offer, by the definition id `access.indicator` receives: an id `BUILT_IN_INDICATORS` lists, such as
1098
- `sma`. The order you give does not matter, because the indicator browser keeps its own:
1100
+ `sma`. The order you give does not matter, because the indicator picker keeps its own:
1099
1101
 
1100
1102
  ```ts
1101
1103
  import { BUILT_IN_INDICATORS, createChart, type ChartDatafeed } from '@trdrs/quickcharts'
@@ -1105,16 +1107,16 @@ declare const datafeed: ChartDatafeed
1105
1107
  // Moving averages and RSI only.
1106
1108
  createChart({ container, datafeed, builtInIndicators: ['sma', 'ema', 'rsi'] })
1107
1109
 
1108
- // A chart that opens with a study it offers.
1110
+ // A chart that opens with an indicator it offers.
1109
1111
  const sma = BUILT_IN_INDICATORS.find((definition) => definition.id === 'sma')!
1110
1112
  createChart({ container, datafeed, builtInIndicators: ['sma'], indicators: [{ id: 'sma-1', definition: sma }] })
1111
1113
  ```
1112
1114
 
1113
- A built-in you leave out is not listed in the indicator browser: not among the built-ins, not
1115
+ A built-in you leave out is not listed in the indicator picker: not among the built-ins, not
1114
1116
  among the favorites, and not in search results. Every door that would add one refuses:
1115
- `chart.indicators.add` (from the browser, the keyboard or your own control) answers `denied`,
1117
+ `chart.indicators.add` (from the picker, the keyboard or your own control) answers `denied`,
1116
1118
  `indicators.add` adds nothing, `indicators.set` leaves out an instance of it the chart does not
1117
- already hold, and a new pane that a re-tile adds copies the first chart's studies without it. The
1119
+ already hold, and a new pane that a re-tile adds copies the first chart's indicators without it. The
1118
1120
  chart has no verb that duplicates an indicator, so a second instance is an add like any other.
1119
1121
 
1120
1122
  Instances of a built-in you leave out that are already on the chart, from a saved chart or layout,
@@ -1154,7 +1156,7 @@ A preset you leave out has no `chart.range.<key>` command, so no menu, shortcut
1154
1156
  reaches it, `chart.range.set` ignores its key, and the bottom bar draws no button for it. An empty
1155
1157
  list offers none, which is how you keep the rest of the bottom bar without range buttons;
1156
1158
  `ui.bottomBar: false` removes the whole bar. `chart.range.set` still takes an explicit
1157
- `{ from, to }` window either way. A preset whose interval your `timeframes` leave out reads its
1159
+ `{ from, to }` window either way. A preset whose timeframe your `timeframes` leave out reads its
1158
1160
  span at your nearest coarser timeframe, as it does without a list. A non-list, a key that names no
1159
1161
  preset and a repeated key are setup errors that `createChart` throws.
1160
1162
 
@@ -1270,10 +1272,10 @@ over it is absent. A control you hide in `ui` is the only thing that changes. Th
1270
1272
  Two presentation flags also close the one command whose only job is opening their dialog.
1271
1273
  `ui.symbolSearch: false` removes the search dialog and `chart.symbol.search`, while
1272
1274
  `chart.symbol.set` still changes the symbol. `ui.indicatorPicker: false` removes the indicator
1273
- browser and `chart.indicators.open`, while `chart.indicators.add` still adds an indicator.
1275
+ picker and `chart.indicators.open`, while `chart.indicators.add` still adds an indicator.
1274
1276
 
1275
- Neither plane is authorization. A hidden control's command still answers to `access`, and no flag
1276
- supplies a capability the datafeed lacks: a feed with no search keeps every search door closed.
1277
+ Authorization belongs to `access`: a hidden control's command still answers to it, and a flag
1278
+ reaches only what the datafeed can do, so a feed with no search keeps every search door closed.
1277
1279
 
1278
1280
  ```ts
1279
1281
  import { createChart, createUdfDatafeed } from '@trdrs/quickcharts'
@@ -1320,11 +1322,11 @@ createChart({
1320
1322
  With `'hide'`, a refused drawing tool is not in its group's flyout, on the favorites bar or in the
1321
1323
  glyph picker; a section, a group or a favorites bar it leaves empty goes with it, and a group's face
1322
1324
  that would wear a refused tool wears the first one the group still offers. A refused indicator is
1323
- not in the indicator browser. A control or menu row whose command the policy refuses is not drawn
1325
+ not in the indicator picker. A control or menu row whose command the policy refuses is not drawn
1324
1326
  (top bar, bottom bar, menus, the drawing toolbar and the selection's bar, the navigation cluster,
1325
- the replay transport, legend row controls and the level menu), and a rule left with nothing beside it
1326
- goes too. A picker always keeps its current choice: the chosen style, timeframe, timezone or scale
1327
- is listed even when its command is refused.
1327
+ the replay transport, legend row controls and the context menu), and a rule left with nothing beside
1328
+ it goes too. A picker always keeps its current choice: the chosen style, timeframe, timezone or
1329
+ scale is listed even when its command is refused.
1328
1330
 
1329
1331
  Only a refusal hides. A permitted command that cannot run now (nothing to undo, no bars loaded,
1330
1332
  nothing selected) is drawn disabled as before. The policy is asked whenever the chrome syncs (a
@@ -1344,11 +1346,11 @@ without them. An indicator whose definition `access.indicator` refuses, once on
1344
1346
  before the policy changed, or put back by a restore), renders and stays fully editable and
1345
1347
  removable:
1346
1348
  `indicators.set`, `chart.indicators.update` and the settings dialog edit it, an edit of any other
1347
- study keeps it, and an edit that would move it, or another study, onto a refused definition leaves
1348
- that study as it stands. A new instance of a refused definition is still left out by every door. A
1349
- drawing whose tool `access.drawingTool` refuses selects, restyles, locks, hides and deletes as any
1350
- drawing does; a copy is a new drawing, so it is not cloned, pasted or duplicated by a Control- or
1351
- Command-drag, while copying it to the clipboard is not refused.
1349
+ indicator keeps it, and an edit that would move it, or another indicator, onto a refused definition
1350
+ leaves that indicator as it stands. A new instance of a refused definition is still left out by
1351
+ every door. A drawing whose tool `access.drawingTool` refuses selects, restyles, locks, hides and
1352
+ deletes as any drawing does; a copy is a new drawing, so it is not cloned, pasted or duplicated by a
1353
+ Control- or Command-drag, while copying it to the clipboard is not refused.
1352
1354
 
1353
1355
  ### A policy that changes
1354
1356
 
@@ -1379,7 +1381,7 @@ Every surface that reads the policy reads it again at once: the top and bottom b
1379
1381
  controls, the drawing toolbar's tools and groups, the favorites bar, the selection's bar, the
1380
1382
  navigation cluster, the replay transport and the legend's row controls, shown or left out as
1381
1383
  `access.refused` says and enabled or disabled. A menu, flyout or dialog that is open reads it too:
1382
- the bars' menus, the level menu, the indicator browser and the saved layouts dialog rebuild their
1384
+ the bars' menus, the context menu, the indicator picker and the saved layouts dialog rebuild their
1383
1385
  rows in place, and a drawing toolbar flyout (the glyph picker among them) or a selection bar panel
1384
1386
  opens again from its control, or closes when that control is now left out or disabled. Listeners of `widget.commands.onChange`
1385
1387
  hear it as well, so a control of your own can read `commands.available` again. Nothing stored
@@ -1430,14 +1432,15 @@ The chart's own dialogs still open from their commands while its bars are hidden
1430
1432
  them in the widget's layer on the document, and `widget.layout.save` asks a layout that was never
1431
1433
  saved for its name. Hide a dialog in `ui` when your interface provides its own.
1432
1434
 
1433
- The library does not export its internal components, replace its renderer, or take markup for its
1434
- controls. The class names under the widget's root are private, apart from the styling hooks listed
1435
+ The library draws its own controls over its own renderer and keeps its internal components to
1436
+ itself. The class names under the widget's root are private, apart from the styling hooks listed
1435
1437
  under Theme.
1436
1438
 
1437
1439
  ### Fullscreen and image
1438
1440
 
1439
- Chart-root fullscreen fills the screen with the widget's own element. It never takes over your
1440
- application shell, which is a different thing from a fit-to-container layout your CSS owns.
1441
+ Chart-root fullscreen fills the screen with the widget's own element, or with the
1442
+ `fullscreen.target` you name; your application shell and any fit-to-container layout stay in your
1443
+ CSS.
1441
1444
 
1442
1445
  ```ts
1443
1446
  import { type ChartWidget } from '@trdrs/quickcharts'
@@ -1456,24 +1459,24 @@ async function shareable(widget: ChartWidget): Promise<void> {
1456
1459
  identity and the attribution you configured through `image`. Nothing is uploaded, shared or stored:
1457
1460
  you receive a blob and decide.
1458
1461
 
1459
- ### Indicator browser content
1462
+ ### Indicator picker content
1460
1463
 
1461
- The Indicators button and `commands.execute('chart.indicators.open')` open the same browser.
1464
+ The Indicators button and `commands.execute('chart.indicators.open')` open the same picker.
1462
1465
  Pass `{ collection: 'saved' }` as the command argument to open a declared host collection directly.
1463
- An unknown collection falls back to the shipped catalog. Reopening an already open browser keeps its current selection and query.
1466
+ An unknown collection falls back to the shipped catalog. Reopening an already open picker keeps its current selection and query.
1464
1467
  It lists the 23 shipped definitions, with search and Favorites. Add creates a new instance on the
1465
- currently active chart and keeps the browser open. Favorites use your `ChartStorage` port.
1468
+ currently active chart and keeps the picker open. Favorites use your `ChartStorage` port.
1466
1469
 
1467
1470
  Supply `ChartWidgetOptions.indicatorPicker` to include additional localized collections and rows
1468
- in that browser. You supply data and opaque actions, not DOM, indicator definitions, or a renderer.
1471
+ in that picker. You supply data and opaque actions, not DOM, indicator definitions, or a renderer.
1469
1472
  Built-in annotations can add favorite state, counts and actions; they cannot replace a definition,
1470
- its name, or Add. Without a source, the browser has only shipped built-ins and Favorites.
1473
+ its name, or Add. Without a source, the picker has only shipped built-ins and Favorites.
1471
1474
 
1472
1475
  ```ts
1473
1476
  import type { IndicatorPickerSource } from '@trdrs/quickcharts'
1474
1477
 
1475
1478
  const indicatorPicker: IndicatorPickerSource = {
1476
- collections: [{ id: 'saved', label: 'Saved studies' }],
1479
+ collections: [{ id: 'saved', label: 'Saved indicators' }],
1477
1480
  async list({ collection, query, builtInIds }, signal) {
1478
1481
  if (signal.aborted) return { kind: 'unavailable', message: 'Content unavailable.' }
1479
1482
  return {
@@ -1492,10 +1495,10 @@ const indicatorPicker: IndicatorPickerSource = {
1492
1495
  }
1493
1496
  ```
1494
1497
 
1495
- The source receives the collection, query and the shipped definition ids the browser lists (only
1498
+ The source receives the collection, query and the shipped definition ids the picker lists (only
1496
1499
  those `builtInIndicators` offers, when you name a list). Its own collections and rows are never
1497
- filtered by that list. It owns filtering and any
1498
- service limits; the browser does not promise an unlimited catalog or implement paging for you.
1500
+ filtered by that list. It owns filtering, paging and any
1501
+ service limits; the picker lists what it answers.
1499
1502
  Collection ids `builtin` and `favorites`, and action ids `add` and `favorite`, are reserved.
1500
1503
  Item ids cannot collide with shipped definition ids or these reserved ids. Duplicate ids,
1501
1504
  malformed rows and missing primary actions refuse the response; the shipped catalog remains usable.
@@ -1504,7 +1507,7 @@ name a localized navigation group and use `layout: 'list'` for name/secondary-ac
1504
1507
  An absent count stays blank. Omit `favorite` to omit that action on a contributed item.
1505
1508
 
1506
1509
  A favorite action receives the requested next boolean. Successful actions refresh the current
1507
- collection unless they return `close: true`. Refusals and exceptions keep the browser open with
1510
+ collection unless they return `close: true`. Refusals and exceptions keep the picker open with
1508
1511
  plain text, never rendered markup. Icons reuse `ChartExtensionIcon`; invalid icons draw no glyph.
1509
1512
  The host localizes contributed labels and messages; the chart localizes its own controls.
1510
1513
 
@@ -1520,7 +1523,7 @@ chrome is painted from it and renders nothing without it.
1520
1523
  - **The top bar.** The symbol pill opens the symbol search for the active chart; the compare door
1521
1524
  opens it in compare mode. The timeframe picker shows the saved timeframes as chips and a list of
1522
1525
  the timeframes the chart offers (by default the 26 presets in five groups), each row savable as a
1523
- chip, with a composer for a custom interval under the unit's ceiling while custom timeframes are
1526
+ chip, with a composer for a custom timeframe under the unit's ceiling while custom timeframes are
1524
1527
  offered. The style picker lists the styles the chart offers. Indicators opens the picker
1525
1528
  over the 23 built-in definitions, and the legend's gear opens the settings dialog for an instance
1526
1529
  (inputs, style, visibility). Bar replay enters and leaves replay for the active chart; entering
@@ -1555,7 +1558,7 @@ chrome is painted from it and renders nothing without it.
1555
1558
  replay entry can claim the row. Chart notices report a feed that cannot serve the symbol, an
1556
1559
  image that could not be copied, or a save the store refused.
1557
1560
 
1558
- Undo and redo step through the chart's own content: the symbol, the interval, the style, the price
1561
+ Undo and redo step through the chart's own content: the symbol, the timeframe, the style, the price
1559
1562
  scale and whether it frames itself, the appearance a viewer authored, the comparisons, the
1560
1563
  indicators and the drawings. A step is one reading of that content, so a step back puts the whole
1561
1564
  reading back rather than reversing a single verb, and the two controls name the change they would
@@ -1694,8 +1697,8 @@ is a factory that returns a fresh `<svg>` element. An id you leave out keeps the
1694
1697
  as a `TypeError` when the widget is created.
1695
1698
 
1696
1699
  An id names what a glyph means, not one control. `settings` is the top bar's settings button, a
1697
- study's gear in the legend and the drawing settings bar's gear, so one drawing stands in all three.
1698
- Drawing tools take `tool.<type>`, chart styles `style.<style>` and layout arrangements
1700
+ indicator's gear in the legend and the drawing settings bar's gear, so one drawing stands in all
1701
+ three. Drawing tools take `tool.<type>`, chart styles `style.<style>` and layout arrangements
1699
1702
  `layout.<code>`, from the registries the rest of the API uses.
1700
1703
 
1701
1704
  ```ts
@@ -1745,15 +1748,16 @@ before costs that one glyph its artwork. The control draws the chart's own glyph
1745
1748
 
1746
1749
  `icons` covers the chart's own controls. The emoji, sticker and icon glyphs a drawing places come
1747
1750
  through the asset port, and a chart extension draws its rows and layers from the
1748
- `ChartExtensionIcon` descriptors it contributes. The product's mark in the plot's corner is not an
1749
- icon. `mountContextMenu`, `openSymbolSearch`, `mountSymbolSearch`, `openTimeframePicker` and
1750
- `mountTimeframePicker` take the same `icons` for a menu, search or picker you mount without a widget.
1751
+ `ChartExtensionIcon` descriptors it contributes. The product's mark in the plot's corner is the
1752
+ chart's own artwork, outside `icons`. `mountContextMenu`, `openSymbolSearch`, `mountSymbolSearch`,
1753
+ `openTimeframePicker` and `mountTimeframePicker` take the same `icons` for a menu, search or picker
1754
+ you mount without a widget.
1751
1755
 
1752
1756
  ### Neutral marks
1753
1757
 
1754
1758
  Serve `marks` and `timescaleMarks` from your datafeed and the chart draws them. A mark is a note
1755
1759
  about a moment: its color is a semantic theme role rather than a literal, its words are yours, and
1756
- the chart neither interprets nor acts on it.
1760
+ the chart draws it as given.
1757
1761
 
1758
1762
  ```ts
1759
1763
  import { type BarMark, type ChartDatafeed } from '@trdrs/quickcharts'
@@ -1791,13 +1795,13 @@ void withMarks
1791
1795
  display name, venue, timeframe, OHLC and change against the previous painted close. Prices use
1792
1796
  the symbol's formatter. Hover selects a bar; leaving restores the latest painted reading. Replay
1793
1797
  does not expose bars beyond its cursor. Missing metadata leaves the supplied symbol unchanged.
1794
- Legend and search use a decorative monogram; they do not fetch symbol logos or infer a provider.
1795
- Study and separate-pane comparison rows follow their renderer panes. Stable row controls retain
1796
- focus during value updates. Study values read the first plot, using declared precision or the
1797
- symbol formatter; Volume reads bar volume using resolved volume precision. The row-list toggle,
1798
- study eye and pane collapse are independent controls. Pane restore remembers study identity
1799
- across pane removal. The status control opens session details, retains an unknown-session state
1800
- and is hidden during replay. Price-scale chips use the same commands as `setScaleMode`.
1798
+ Legend and search rows wear your `symbolMark`, or a decorative monogram without one.
1799
+ Indicator and separate-pane comparison rows follow their renderer panes. Stable row controls
1800
+ retain focus during value updates. Indicator values read the first plot, using declared precision
1801
+ or the symbol formatter; Volume reads bar volume using resolved volume precision. The row-list
1802
+ toggle, indicator eye and pane collapse are independent controls. Pane restore remembers indicator
1803
+ identity across pane removal. The status control opens session details, retains an unknown-session
1804
+ state and is hidden during replay. Price-scale chips use the same commands as `setScaleMode`.
1801
1805
  - **An interface language** (`locale`, English by default), one of the 21 the package ships.
1802
1806
  `BUILT_IN_LOCALES` lists them for a picker: each carries its stable code, its canonical BCP 47
1803
1807
  `tag`, its reading direction (`ar` and `he_IL` are `rtl`), and its endonym. The chart's own chrome
@@ -1889,9 +1893,9 @@ theme.resetCustom()
1889
1893
  stop()
1890
1894
  ```
1891
1895
 
1892
- Switching modes keeps the symbol, timeframe, visible range, drawings and studies the chart already
1893
- has. A palette you supply is a tint rather than a replacement: a role you do not name keeps its
1894
- built-in value for that mode.
1896
+ Switching modes keeps the symbol, timeframe, visible range, drawings and indicators the chart
1897
+ already has. A palette you supply is a tint rather than a replacement: a role you do not name keeps
1898
+ its built-in value for that mode.
1895
1899
 
1896
1900
  ### Roles
1897
1901
 
@@ -1912,19 +1916,22 @@ same values:
1912
1916
  | `motion.durationBase` | `150ms` | A modal dialog and its backdrop opening and closing, and the replay row entering and leaving. |
1913
1917
  | `motion.durationModerate` | `250ms` | A control moving to its new state, such as a switch knob sliding. |
1914
1918
  | `motion.durationSlow` | `350ms` | A small mark settling, such as a disclosure caret turning or a checkbox filling. |
1915
- | `motion.durationSlower` | `500ms` | A larger mark turning, such as the drawing rail's chevron. |
1919
+ | `motion.durationSlower` | `500ms` | A larger mark turning, such as the drawing toolbar's chevron. |
1916
1920
  | `motion.easingStandard` | `ease` | A modal dialog box, the replay row, a caret dip, a checkbox fill. |
1917
1921
  | `motion.easingOut` | `ease-out` | A modal backdrop fading, a switch knob sliding. |
1918
1922
  | `motion.easingLinear` | `linear` | A color or opacity change under the pointer. |
1919
1923
  | `motion.easingSpring` | `cubic-bezier(0.175, 0.885, 0.32, 1.275)` | A caret or chevron turning with a slight overshoot. |
1920
1924
  | `motion.scaleEnter` | `0.97` | The scale a modal dialog box grows from as it opens. |
1921
1925
 
1922
- A modal dialog, such as the symbol search or chart settings, opens with its backdrop fading in over
1926
+ Every modal dialog the chart opens (the symbol search, chart settings, the indicator browser and
1927
+ indicator settings, the saved-layouts browser, the name and confirm prompts, go to date, and the
1928
+ drawing settings, image picker and template prompts) opens with its backdrop fading in over
1923
1929
  `motion.durationBase` on `motion.easingOut`, and its box fading in and growing from
1924
1930
  `motion.scaleEnter` to full size over `motion.durationBase` on `motion.easingStandard`. It closes
1925
1931
  with the same motion reversed. A closing dialog stops taking input and returns focus at once, and the
1926
1932
  chart removes it when the same `motion.durationBase` has elapsed, so a duration you set times both
1927
- the transition and the removal.
1933
+ the transition and the removal. A dialog opened over another moves on its own and closes first, and
1934
+ a dialog the chart replaces or tears down goes at once.
1928
1935
 
1929
1936
  A duration takes a CSS duration such as `200ms` or `0.2s`; an easing takes any CSS timing function,
1930
1937
  such as `ease-in-out`, `steps(4)` or `cubic-bezier(0.2, 0, 0, 1)`; a scale takes a unitless factor
@@ -1949,23 +1956,23 @@ once.
1949
1956
  ### Cascade layers
1950
1957
 
1951
1958
  The stylesheet declares two cascade layers and puts everything it contains in them: the built-in
1952
- palettes in `trdrs.tokens`, every recipe in `trdrs.chart`. Nothing in it is unlayered. It marks a
1953
- declaration `!important` only where a later layer or an inline palette must not undo it: an
1954
- element's `hidden` attribute, the zero motion durations under a reduced-motion preference, and the
1955
- missing focus ring of a search field, which shows its focus by its caret. Your first stylesheet must
1956
- declare the complete order before any product stylesheet loads, because a layer's position is fixed
1957
- by the first statement that names it:
1959
+ palettes in `quickcharts.tokens`, every recipe in `quickcharts.chart`. Nothing in it is unlayered.
1960
+ It marks a declaration `!important` only where a later layer or an inline palette must not undo it:
1961
+ an element's `hidden` attribute, the zero motion durations under a reduced-motion preference, and
1962
+ the missing focus ring of a search field, which shows its focus by its caret. Your first stylesheet
1963
+ must declare the complete order before any product stylesheet loads, because a layer's position is
1964
+ fixed by the first statement that names it:
1958
1965
 
1959
1966
  ```css
1960
- @layer reset, trdrs.tokens, trdrs.chart, trdrs.platform, host;
1967
+ @layer reset, quickcharts.tokens, quickcharts.chart, host;
1961
1968
  ```
1962
1969
 
1963
1970
  Put your reset in `reset`. A reset left unlayered outranks every layered rule and strips the
1964
1971
  chart's controls of their borders and grounds. Put an intentional override of a supported hook in
1965
- `host`, or leave it unlayered; either wins over the chart's recipe. A chart-only host declares only
1966
- `reset`, the two chart layers and `host`; `trdrs.platform` is the layer a later product declares
1967
- for itself. Inline styles the chart writes for measured geometry outrank any stylesheet rule, and
1968
- a rule marked `!important` reverses layer order, so neither is a way to restyle the chart.
1972
+ `host`, or leave it unlayered; either wins over the chart's recipe. Layers of your own product go
1973
+ between `quickcharts.chart` and `host`. Inline styles the chart writes for measured geometry
1974
+ outrank any stylesheet rule, and a rule marked `!important` reverses layer order, so neither is a
1975
+ way to restyle the chart.
1969
1976
 
1970
1977
  ### Supported styling hooks
1971
1978
 
@@ -1977,9 +1984,9 @@ applying in any release. Removing a hook, a state or a customization is a breaki
1977
1984
  | Hook | Purpose | States | Customization |
1978
1985
  |---|---|---|---|
1979
1986
  | `.qc-topbar` | The top toolbar: symbol search, timeframe, chart style, indicators, layouts, replay and the widget menus. | | background-color, border, padding, gap, box-shadow |
1980
- | `.qc-drawing-toolbar` | The drawing rail beside the plot. | `aria-orientation`: vertical beside the plot, horizontal in a host row. | background-color, border, padding, gap, box-shadow |
1987
+ | `.qc-drawing-toolbar` | The drawing toolbar beside the plot. | `aria-orientation`: vertical beside the plot, horizontal in a host row. | background-color, border, padding, gap, box-shadow |
1981
1988
  | `.qc-bottombar` | The bottom bar: range shortcuts, the session clock and the timezone. | | background-color, border, padding, gap, box-shadow |
1982
- | `.qc-legend` | The legend over the plot: the symbol, its reading and each study row. | | background-color, border, border-radius, padding, box-shadow, inset |
1989
+ | `.qc-legend` | The legend over the plot: the symbol, its reading and each indicator row. | | background-color, border, border-radius, padding, box-shadow, inset |
1983
1990
  | `.qc-menu-panel` | A floating menu opened from a toolbar control. | `hidden`: present while the menu is closed. | background-color, border, border-radius, padding, box-shadow |
1984
1991
  | `.qc-dialog` | A modal dialog: settings, search, layouts and the drawing editors. | `data-role`: which dialog this is, in the chart's own vocabulary. | background-color, border, border-radius, padding, box-shadow, max-width |
1985
1992
 
@@ -2011,8 +2018,8 @@ problem. Configuration errors never reach a render.
2011
2018
  ### Theme and appearance are two ladders
2012
2019
 
2013
2020
  The theme palette is the broad brand surface. Chart appearance is the specific one: series colors,
2014
- candle anatomy, grid visibility and study visuals in `ChartOverrides.appearance`. Where both could
2015
- affect the same pixel, appearance wins.
2021
+ candle anatomy, grid visibility and indicator visuals in `ChartOverrides.appearance`. Where both
2022
+ could affect the same pixel, appearance wins.
2016
2023
 
2017
2024
  Theme palette precedence, lowest first:
2018
2025
 
@@ -2023,7 +2030,7 @@ Chart appearance precedence, lowest first:
2023
2030
 
2024
2031
  1. the built-in appearance for the selected mode;
2025
2032
  2. the constructor's `appearance` partial;
2026
- 3. restored user appearance;
2033
+ 3. restored viewer appearance;
2027
2034
  4. runtime `chart.applyAppearance` patches.
2028
2035
 
2029
2036
  Resetting custom palettes returns the chart to the built-in mode and leaves saved chart appearance
@@ -2035,23 +2042,24 @@ opens in those colors on any theme.
2035
2042
 
2036
2043
  ## Compare
2037
2044
 
2038
- Every chart can draw OTHER symbols beside its own. A compare is study-like: legend-managed, three
2039
- placements, persisted in the chart content blob. `same-percent` shares the
2040
- main price scale and flips it to percent while any such compare lives (the prior scale mode comes
2041
- back when the last one leaves); `new-scale` binds the LEFT scale with absolute prices (the left
2042
- axis exists only while such a compare does); `new-pane` takes a pane of its own. Compared bars
2043
- clip to the main series window, so a compare never extends the time axis. `features.compareSymbols` supplies
2044
- a curated quick-add list for the compare dialog; `compare.symbols()` reads it back.
2045
+ Compare draws OTHER symbols beside a chart's own. Each comparison is managed like an indicator:
2046
+ from the legend, at one of three placements, persisted in the chart content blob. `same-percent`
2047
+ shares the main price scale and flips it to percent while any such comparison lives (the prior
2048
+ scale mode comes back when the last one leaves); `new-scale` binds the LEFT scale with absolute
2049
+ prices (the left axis exists only while such a comparison does); `new-pane` takes a pane of its
2050
+ own. Compared bars clip to the main series window, so a comparison never extends the time axis.
2051
+ `features.compareSymbols` supplies a curated quick-add list for the compare dialog;
2052
+ `compare.symbols()` reads it back.
2045
2053
 
2046
2054
  The widget's standard Compare door lives in the top bar, and `ui: { topBar: { compare: false } }`
2047
2055
  hides it. The legend header does not duplicate that door. Commands and comparison-row actions remain
2048
- available when the top-bar door is hidden, and `features: { compare: false }` removes comparing
2049
- altogether. The built-in dialog's search rows add at any of the three placements, curated `compareSymbols` rows sit
2050
- above results, and the ADDED section removes. Each compare takes a legend row whose title reopens
2051
- the dialog in change-symbol mode (the pick re-keys the compare in place), with an eye and a remove
2052
- beside the value (% under `same-percent`, the last close otherwise). In a layout, compares belong to
2053
- each chart of their own (`widget.activeChart().compare`) and ride the layout blob with the rest of
2054
- that chart's content.
2056
+ available when the top-bar door is hidden, and `features: { compare: false }` removes compare
2057
+ altogether. The built-in dialog's search rows add at any of the three placements, curated
2058
+ `compareSymbols` rows sit above results, and the ADDED section removes. Each comparison takes a
2059
+ legend row whose title reopens the dialog in change-symbol mode (the pick re-keys the comparison in
2060
+ place), with an eye and a remove beside the value (% under `same-percent`, the last close
2061
+ otherwise). In a layout, each chart holds its own comparisons (`widget.activeChart().compare`), and
2062
+ they ride the layout blob with the rest of that chart's content.
2055
2063
 
2056
2064
  ```ts
2057
2065
  import { createChart, createUdfDatafeed } from '@trdrs/quickcharts'
@@ -2067,15 +2075,15 @@ compare.add('CL', { placement: 'new-pane' })
2067
2075
  compare.setVisible('CL', false)
2068
2076
  const active = compare.list() // [{ symbol, placement, color, visible }]
2069
2077
  note(`NQ last: ${compare.latest('NQ') ?? '-'}`)
2070
- compare.remove('NQ') // the scale mode the trader held comes back
2078
+ compare.remove('NQ') // the scale mode the viewer held comes back
2071
2079
  ```
2072
2080
 
2073
2081
  ## Timeframes
2074
2082
 
2075
- A timeframe token names a bar interval: a count and a unit, `1m`, `4h`, `1d`, `3mo`, `500t`. The
2076
- units are ticks (`t`), seconds (`s`), minutes (`m`), hours (`h`), days (`d`), weeks (`w`) and
2077
- months (`mo`), and each unit has a ceiling (`TIMEFRAME_MAX`). A token outside the grammar parses
2078
- as null, and the chart never asks a feed for it.
2083
+ A timeframe token names how much one bar spans: a count and a unit, `1m`, `4h`, `1d`, `3mo`, `500t`.
2084
+ The units are ticks (`t`), seconds (`s`), minutes (`m`), hours (`h`), days (`d`), weeks (`w`) and
2085
+ months (`mo`), and each unit has a ceiling (`TIMEFRAME_MAX`). A token outside the grammar parses as
2086
+ null, and the chart never asks a feed for it.
2079
2087
 
2080
2088
  ```ts
2081
2089
  import { formatTimeframe, isIntradayTimeframe, parseTimeframe, timeframeSeconds } from '@trdrs/quickcharts'
@@ -2183,7 +2191,7 @@ if (equity) {
2183
2191
 
2184
2192
  ## Ranges
2185
2193
 
2186
- `RANGE_PRESETS` lists the nine range presets, each a visible span and the interval it reads best
2194
+ `RANGE_PRESETS` lists the nine range presets, each a visible span and the timeframe it reads best
2187
2195
  at: `1D` over one-minute bars through `All` over monthly bars. `rangeAvailable` withholds a preset
2188
2196
  deeper than the history a symbol has, and `frameRange` sets a pane's visible window for a span,
2189
2197
  anchored on the last real bar so a future whitespace horizon never frames as empty space. The
@@ -2211,7 +2219,7 @@ host's own search surface subscribes to the same state.
2211
2219
  The widget prefetches the default catalog and reuses completed pages across search, compare and
2212
2220
  change-symbol dialogs. Each opening owns its query, class filter and pending work. Closing it
2213
2221
  discards late results without clearing completed pages. Locale changes and widget disposal clear
2214
- the widget cache. A replacement widget never inherits a previous provider's cache.
2222
+ the widget cache. A replacement widget starts with a cache of its own.
2215
2223
 
2216
2224
  Completed reuse holds at most 32 queries and 5,000 rows, evicting least-recent entries. Retained
2217
2225
  multi-page results reopen without a first-page refetch. Eviction does not truncate an active
@@ -2371,9 +2379,9 @@ catalog.dispose()
2371
2379
 
2372
2380
  ### The timeframe picker, away from a chart
2373
2381
 
2374
- A page that asks for an interval where no chart is mounted (a backtest's timeframe, an alert's
2375
- interval) opens the list the chart's timeframe caret drops: the same unit groups, rows, labels and
2376
- custom composer, built by the same code, so an interval reads there exactly as it reads on a chart.
2382
+ A page that asks for a timeframe where no chart is mounted (a backtest's timeframe, an alert's
2383
+ timeframe) opens the list the chart's timeframe caret drops: the same unit groups, rows, labels and
2384
+ custom composer, built by the same code, so a timeframe reads there exactly as it reads on a chart.
2377
2385
  `openTimeframePicker` drops it from a control you own, which gets `aria-expanded` while it is open
2378
2386
  and focus back when it closes; `mountTimeframePicker` builds it bare into a box you own and
2379
2387
  position, as a card on a phone. Picking a row, or composing a custom timeframe, hands the token to
@@ -2390,7 +2398,7 @@ do not serve is left out, and the composer does not offer one. `theme`, `locale`
2390
2398
  reaches it outside any chart in either mode.
2391
2399
 
2392
2400
  It carries no saved chips and no stars, and keeps nothing: the chips are a chart's quick-select row,
2393
- stored with the chart's preferences, and a field that asks for one interval has no row to put them
2401
+ stored with the chart's preferences, and a field that asks for one timeframe has no row to put them
2394
2402
  in. A composed timeframe is handed back and not added to any list. The drop-down takes the keyboard
2395
2403
  as the chart's does (arrow keys, Home and End rove the rows; Escape and a press outside close
2396
2404
  it) and moves as every menu does.
@@ -2398,13 +2406,13 @@ it) and moves as every menu does.
2398
2406
  ```ts
2399
2407
  import { mountTimeframePicker, openTimeframePicker } from '@trdrs/quickcharts'
2400
2408
 
2401
- declare const intervalField: HTMLButtonElement
2402
- declare const intervalBox: HTMLElement
2409
+ declare const timeframeField: HTMLButtonElement
2410
+ declare const timeframeBox: HTMLElement
2403
2411
  declare const backtest: { timeframe: string; setTimeframe(timeframe: string): void }
2404
2412
 
2405
- intervalField.addEventListener('click', () => {
2413
+ timeframeField.addEventListener('click', () => {
2406
2414
  const picker = openTimeframePicker({
2407
- anchor: intervalField,
2415
+ anchor: timeframeField,
2408
2416
  timeframe: backtest.timeframe,
2409
2417
  resolutions: ['1m', '5m', '15m', '1h', '4h', '1d'], // what the feed serves
2410
2418
  theme: { mode: 'light' },
@@ -2414,18 +2422,18 @@ intervalField.addEventListener('click', () => {
2414
2422
  })
2415
2423
 
2416
2424
  const card = mountTimeframePicker({
2417
- container: intervalBox,
2425
+ container: timeframeBox,
2418
2426
  timeframes: ['5m', '15m', '1h', '4h', '1d'],
2419
2427
  timeframe: '1h',
2420
2428
  onPick: (timeframe) => backtest.setTimeframe(timeframe),
2421
- onClose: () => intervalBox.remove(),
2429
+ onClose: () => timeframeBox.remove(),
2422
2430
  })
2423
2431
  card.focus()
2424
2432
  ```
2425
2433
 
2426
2434
  ### The legend
2427
2435
 
2428
- Every chart carries its own legend over its plot: an identity row naming the market, the interval
2436
+ Every chart carries its own legend over its plot: an identity row naming the market, the timeframe
2429
2437
  and the venue, and a values row carrying the hovered bar's O H L C and its move against the previous
2430
2438
  close. The numbers are written through that chart's own formatter, so a level in the legend is the
2431
2439
  level its price axis writes, and they follow the pointer while the crosshair is on the plot and the
@@ -2441,10 +2449,10 @@ The `size` is the chart's, not a suggestion: the box is already laid out, and pa
2441
2449
  you were given is what lines the marks up down a list. The chart ships no artwork and fetches none,
2442
2450
  so without this every mark is the neutral monogram it draws itself.
2443
2451
 
2444
- `venueMark` and `providerMark` paint the source at the end of every symbol search and compare row
2445
- the same way: the venue a market lists on, or the data provider where a row names no venue. Each
2446
- receives the name the row writes (`exchange` or `provider`), the element and the size, and returns
2447
- a disposer. Without them a source wears its initial on a neutral disc.
2452
+ `venueMark` and `dataSourceMark` paint the source at the end of every symbol search and compare
2453
+ row the same way: the venue a market lists on, or the data source where a row names no venue. Each
2454
+ receives the name the row writes (`exchange` or `dataSource`), the element and the size, and
2455
+ returns a disposer. Without them a source wears its initial on a neutral disc.
2448
2456
 
2449
2457
  ```ts
2450
2458
  import { createChart, createUdfDatafeed } from '@trdrs/quickcharts'
@@ -2488,7 +2496,7 @@ re-tile, a restore. Pointing your own surface at a chart moves no chart's symbol
2488
2496
  concepts stay separate: each chart keeps charting what it charts, and one of them is the one you are
2489
2497
  looking at.
2490
2498
 
2491
- Five sync toggles fan changes across the charts: `symbol`, `interval` and `dateRange` replay a
2499
+ Five sync toggles fan changes across the charts: `symbol`, `timeframe` and `dateRange` replay a
2492
2500
  change onto every chart, `crosshair` mirrors continuously by time, and `time` centers every chart on
2493
2501
  a clicked moment. The whole layout serializes as ONE opaque content blob (arrangement, sync flags,
2494
2502
  durable chart-entity identities, active chart, every chart's own content), so a saved multi-chart
@@ -2565,9 +2573,9 @@ const saved = drawings?.export() // the persistence wire format (SerializedDrawi
2565
2573
  note(`${selected?.type ?? 'nothing'} selected, ${saved?.length ?? 0} drawings`)
2566
2574
  ```
2567
2575
 
2568
- ### The toolbar
2576
+ ### The drawing toolbar
2569
2577
 
2570
- The toolbar is the rail down the chart's leading edge. Its buttons are the cursor with its three
2578
+ The drawing toolbar runs down the chart's leading edge. Its buttons are the cursor with its three
2571
2579
  pointer modes and the eraser; seven tool groups, each opening a flyout of the group's sections
2572
2580
  with a star on every row that lands the tool on the favorites bar; Measure and Zoom; the magnet
2573
2581
  with its weak and strong strengths; stay in drawing mode; lock all; the eye that hides drawings,
@@ -2596,7 +2604,7 @@ chosen.
2596
2604
  Selecting a drawing shows the settings bar: templates, the stroke color with its opacity, the
2597
2605
  background for tools that have one, the text color and font size for text tools, thickness and
2598
2606
  line style, the settings gear, lock, delete, and a More menu with the stacking moves, the
2599
- per-interval visibility presets, clone, copy and hide. Every edit persists at once and becomes the
2607
+ per-timeframe visibility presets, clone, copy and hide. Every edit persists at once and becomes the
2600
2608
  tool's default for the next drawing of that type. The settings dialog opens from the gear with
2601
2609
  Inputs, Style, Text, Table, Coordinates and Visibility pages as the tool has them; its edits apply
2602
2610
  live, Cancel restores the drawing, and Ok commits the session as one edit.
@@ -2605,7 +2613,7 @@ Text-bearing tools open an inline editor where the text sits, in the drawing's o
2605
2613
  placement committed empty is removed; an existing note committed empty is blanked. Ctrl or Cmd
2606
2614
  with Enter commits, Escape cancels, and a press on the chart commits.
2607
2615
 
2608
- Templates are named setups a trader saves from either surface and applies on demand. They and
2616
+ Templates are named setups a viewer saves from either surface and applies on demand. They and
2609
2617
  the remembered defaults ride `ChartSaveLoadAdapter.templates('drawing')`, so a host that keeps
2610
2618
  saved charts on a server keeps these there too. Same-tool default changes are written in issue
2611
2619
  order, and a delayed read or failed write does not replace the newest local choice. Store work is
@@ -2644,14 +2652,14 @@ Everything about drawings a host builds its own UI from is one subpath. A host t
2644
2652
  never imports any of it.
2645
2653
 
2646
2654
  ```ts
2647
- import { buildRailGroups, drawingTools, parseDrawingsStore, restoreDrawings, serializeDrawingsStore } from '@trdrs/quickcharts/drawings'
2655
+ import { buildDrawingToolbarGroups, drawingTools, parseDrawingsStore, restoreDrawings, serializeDrawingsStore } from '@trdrs/quickcharts/drawings'
2648
2656
 
2649
2657
  // The catalog: 90 tools in 14 categories, read-only.
2650
2658
  const trendLine = drawingTools.get('trend_line')
2651
2659
  const fibs = drawingTools.byCategory('fibonacci')
2652
2660
 
2653
- // The rail's own structure, as data: seven groups, their sections, and a catalog key per heading.
2654
- for (const group of buildRailGroups()) {
2661
+ // The drawing toolbar as data: seven groups, their sections, and a catalog key per heading.
2662
+ for (const group of buildDrawingToolbarGroups()) {
2655
2663
  for (const section of group.sections) note(`${group.id}/${section.label}: ${section.tools.length}`)
2656
2664
  }
2657
2665
 
@@ -2661,10 +2669,10 @@ const drawings = restoreDrawings(store['ES'] ?? [])
2661
2669
  localStorage.setItem('acme.chart.drawings', serializeDrawingsStore(store))
2662
2670
  ```
2663
2671
 
2664
- The subpath carries the workflow models too: what the rail's eye blanks (`HideMode`, which reaches
2665
- chart-owned drawings and indicators and nothing else), the cursor modes and the two transient
2666
- tools, the magnet policy over `magnetSnap`, the remove menu, favorites over a `FavoritesPort`, the
2667
- standing preference record, and per-tool defaults and named templates over
2672
+ The subpath carries the workflow models too: what the drawing toolbar's eye blanks (`HideMode`,
2673
+ which reaches chart-owned drawings and indicators and nothing else), the cursor modes and the two
2674
+ transient tools, the magnet policy over `magnetSnap`, the remove menu, favorites over a
2675
+ `FavoritesPort`, the standing preference record, and per-tool defaults and named templates over
2668
2676
  `ChartSaveLoadAdapter.templates('drawing')`. Each is a pure function or a plain record, so a host
2669
2677
  builds its own controls without reimplementing the decisions behind them.
2670
2678
 
@@ -2672,7 +2680,7 @@ What a lock refuses is the library's own. A host locks and unlocks through the d
2672
2680
  reads what they answer: `chart.drawings.lock` holds one drawing where it stands,
2673
2681
  `chart.drawings.lockAll` suspends the whole chart, and every editing command answers `ok` or
2674
2682
  `unavailable` for the state the chart is actually in, so a control built on those outcomes says
2675
- exactly what the chart will accept. The gesture list below is what a trader meets.
2683
+ exactly what the chart will accept. The gesture list below is what a viewer meets.
2676
2684
 
2677
2685
  ```ts
2678
2686
  import { blanks, chooseHideMode, DEFAULT_HIDE_STATE, DrawingTemplates } from '@trdrs/quickcharts/drawings'
@@ -2707,9 +2715,9 @@ note(IMAGE_ACCEPT)
2707
2715
 
2708
2716
  What to know:
2709
2717
 
2710
- - **The subpath is a subset, not a re-export.** The drawing classes, the model store and the
2711
- mutable registry stay inside the library. There is no door for a host-authored tool: tool
2712
- contribution belongs to the access-policy plane, not to a bare registration call.
2718
+ - **The subpath is a deliberate subset.** The drawing classes, the model store and the
2719
+ mutable registry stay inside the library. A host chooses among the catalog's tools through
2720
+ `drawingTools` and the access policy.
2713
2721
  - **Every tool places.** Fixed-anchor tools place by press-drag-release or click then click; an
2714
2722
  instant tool (the position tools) lands whole from one press; a multipoint tool adds a point per
2715
2723
  click until a double-click ends the run; a freehand tool captures the drag as a stroke; a
@@ -2730,13 +2738,13 @@ What to know:
2730
2738
  so anchors never drift apart); grab an anchor handle to reshape; hold Shift to constrain a
2731
2739
  two-point placement or an anchor drag to 45 degree rays; Ctrl-drag duplicates; the magnet pulls
2732
2740
  a placed or dragged anchor onto the bar's own open, high, low or close; a locked drawing
2733
- selects, takes the Delete and Clone a trader asks for by name, and refuses a move, a resize, a
2741
+ selects, takes the Delete and Clone a viewer asks for by name, and refuses a move, a resize, a
2734
2742
  text edit, the eraser and a Ctrl-drag copy; lock all suspends every edit, Delete and Clone
2735
2743
  included, until it is released.
2736
2744
 
2737
2745
  ## Extensions
2738
2746
 
2739
- An extension is host code that draws on the chart, adds rows to its level menu, offers commands,
2747
+ An extension is host code that draws on the chart, adds rows to its context menu, offers commands,
2740
2748
  and stores viewer state in the chart's own save blob. The chart attaches it at mount, pushes its
2741
2749
  changes at it, and takes it down at teardown, along with everything it drew.
2742
2750
 
@@ -2790,9 +2798,9 @@ What to know:
2790
2798
  layer on the document body, painted with the widget's theme: a menu or an editor mounted there at
2791
2799
  viewport coordinates stands over every pane and over whatever the page stacks around the widget,
2792
2800
  which the pane's own overlay cannot promise. `ctx.symbolTitle()` is the name symbology gives the
2793
- charted market, the name the legend and the level menu print; `ctx.chart.symbol()` stays the
2801
+ charted market, the name the legend and the context menu print; `ctx.chart.symbol()` stays the
2794
2802
  ticker an action is sent for. `ctx.painters` carries the mark painters you supplied as `.symbol`,
2795
- `.venue` and `.provider`, so a surface that names a market paints it with the same mark the
2803
+ `.venue` and `.dataSource`, so a surface that names a market paints it with the same mark the
2796
2804
  legend and the search rows wear rather than shipping artwork of its own; a painter you did not
2797
2805
  supply is `null`, and the surface writes the name alone.
2798
2806
  - **A contributed row may bring its own glyph.** `ChartExtensionMenuItem.icon` is a
@@ -2807,19 +2815,20 @@ What to know:
2807
2815
  default), an action on the price the pointer landed on, placed under Copy price and Paste; or
2808
2816
  `view`, a switch over what the chart shows, placed under the remove rows. Rows keep the order
2809
2817
  they were contributed in within their group.
2810
- - **A drawn layer can join the rail's eye.** `ctx.contributeHideLayer({ id, label, icon, apply })`
2811
- lists the layer in the eye's menu after the chart's own drawings and indicators and before
2812
- "Hide all", which blanks it with them. `label` carries the row's wording in both states, `icon`
2813
- the two marks the eye wears while the layer is its subject, and `apply(hidden)` is called with
2814
- the current state at contribution and on every change. The handle reads `hidden()`, flips
2815
- `setHidden()` through the same eye the rail drives, and `remove()` withdraws the layer.
2818
+ - **A drawn layer can join the drawing toolbar's eye.**
2819
+ `ctx.contributeHideLayer({ id, label, icon, apply })` lists the layer in the eye's menu after the
2820
+ chart's own drawings and indicators and before "Hide all", which blanks it with them. `label`
2821
+ carries the row's wording in both states, `icon` the two marks the eye wears while the layer is
2822
+ its subject, and `apply(hidden)` is called with the current state at contribution and on every
2823
+ change. The handle reads `hidden()`, flips `setHidden()` through the same eye the drawing toolbar
2824
+ drives, and `remove()` withdraws the layer.
2816
2825
  - **A printed chord is a real binding.** `ChartExtensionMenuItem.shortcut` prints on the row and
2817
2826
  answers to the key. The chart's dispatcher offers a press no built-in verb claims to the rows your
2818
- provider contributes for the level under the pointer and runs that row's own `run`, the same call
2819
- the click makes. The press acts on the tile the pointer is inside, active or not; outside every
2820
- tile, on a point with no readable level, while the viewer is typing, or while a modal holds the
2821
- keyboard, the key is left to your page. A row your provider withholds claims no key, and a row
2822
- carries one chord.
2827
+ `contributeContextMenu` callback returns for the level under the pointer and runs that row's own
2828
+ `run`, the same call the click makes. The press acts on the tile the pointer is inside, active or
2829
+ not; outside every tile, on a point with no readable level, while the viewer is typing, or while
2830
+ a modal holds the keyboard, the key is left to your page. A row your callback withholds claims no
2831
+ key, and a row carries one chord.
2823
2832
  - **A glyph is bounded, and a refusal is free.** Up to 8 shapes, each up to 2048 characters of path
2824
2833
  data with every number in it finite, and an `outline` stroke width above 0 and up to 8. A shape
2825
2834
  outside that is dropped and the rest of the glyph still draws; a descriptor outside it draws
@@ -2842,7 +2851,7 @@ What to know:
2842
2851
  re-attaches it on every switch, so a market-scoped overlay cannot carry one market's drawing onto
2843
2852
  another's bars.
2844
2853
  - **A failing extension is its own problem.** A throw in `attach` drops that extension and the
2845
- chart still mounts; a throw in a subscriber, a menu provider, a command or a teardown is
2854
+ chart still mounts; a throw in a subscriber, a menu builder, a command or a teardown is
2846
2855
  contained.
2847
2856
  - **Layouts attach per chart.** A widget hands its shared options to every chart it tiles, so each
2848
2857
  chart gets its own attachment, its own context and its own state slot.
@@ -2870,7 +2879,7 @@ Every claim in this document maps to a test or a generated artifact in the packa
2870
2879
  and the tarball, and the third-party notices are held by the boundary tests and
2871
2880
  `THIRD-PARTY-NOTICES.md`.
2872
2881
 
2873
- ## Versioning & deprecation
2882
+ ## Versioning
2874
2883
 
2875
2884
  - **SemVer, enforced at the gate.** The public surface is pinned by an API-surface test (every
2876
2885
  exported name and its runtime kind), the shipped type declarations are compiled against by a
@@ -2882,8 +2891,8 @@ Every claim in this document maps to a test or a generated artifact in the packa
2882
2891
  and fields (`config` and `serverTime` are the pattern): an existing
2883
2892
  implementation keeps compiling, and the widget treats absence as "unconstrained / not supported".
2884
2893
  Your integration never breaks by standing still within a major.
2885
- - **Deprecation runs a full major.** A deprecated export keeps working for the remainder of the
2886
- current major, is marked `@deprecated` in the types with its replacement named in the note (your
2887
- editor flags every call site), and is removed only in the next major, never silently.
2894
+ - **A major version may rename or remove public names.** Its upgrading guide in
2895
+ [CHANGELOG.md](CHANGELOG.md) lists each one, old to new, with how saved state carries over: what
2896
+ the previous major saved still opens, and the next save writes the current names.
2888
2897
  - **The wire timeframe grammar is stable vocabulary.** `<N><unit>` with units `t s m h d w mo`.
2889
2898
  Extensions may add units; an existing token never changes meaning.