@tony01/astroneum 0.4.1-beta.2 → 0.4.1-beta.24

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 (57) hide show
  1. package/CHANGELOG.md +280 -237
  2. package/LICENSE +21 -21
  3. package/README.md +250 -198
  4. package/dist/{Indicator-BBI3L_lx.d.ts → Indicator-BQQcVGGT.d.ts} +51 -227
  5. package/dist/astroneum.css +1 -1
  6. package/dist/chunk-47YGQEL5.js +399 -0
  7. package/dist/chunk-67XM5LOK.js +2 -0
  8. package/dist/{chunk-2YYGUSVX.js → chunk-DFJMDDL5.js} +1 -1
  9. package/dist/chunk-EFAS6F4C.js +59 -0
  10. package/dist/chunk-EHZN52CY.js +2 -0
  11. package/dist/chunk-HKLWNPMP.js +35 -0
  12. package/dist/chunk-ONPPQW65.js +2 -0
  13. package/dist/chunk-SCTHEM27.js +3 -0
  14. package/dist/chunk-UQXYMB6X.js +2 -0
  15. package/dist/chunk-WSIR5T2M.js +3 -0
  16. package/dist/entries/alerts.d.ts +36 -10
  17. package/dist/entries/alerts.js +1 -1
  18. package/dist/entries/datafeeds/crypto.d.ts +5 -1
  19. package/dist/entries/datafeeds/crypto.js +1 -1
  20. package/dist/entries/datafeeds/polygon.d.ts +3 -1
  21. package/dist/entries/datafeeds/polygon.js +1 -1
  22. package/dist/entries/headless.d.ts +78 -0
  23. package/dist/entries/headless.js +2 -0
  24. package/dist/entries/multichart.d.ts +12 -1
  25. package/dist/entries/multichart.js +1 -1
  26. package/dist/entries/replay.d.ts +3 -1
  27. package/dist/entries/script.d.ts +2 -68
  28. package/dist/entries/script.js +1 -1
  29. package/dist/entries/watchlist.d.ts +32 -2
  30. package/dist/entries/watchlist.js +1 -1
  31. package/dist/entries/workspace.d.ts +61 -0
  32. package/dist/entries/workspace.js +2 -0
  33. package/dist/index-BiXdqLA4.d.ts +45 -0
  34. package/dist/index-_JgHTy8X.d.ts +19 -0
  35. package/dist/index.d.ts +188 -27
  36. package/dist/index.js +1 -1
  37. package/dist/script-BU9StbN-.d.ts +120 -0
  38. package/dist/types-W6SIYQ9m.d.ts +232 -0
  39. package/dist/types-yETgeYsE.d.ts +28 -0
  40. package/dist/zh-CN-NDTUDZGZ.js +3 -0
  41. package/docs/MIRROR-PLAN-SYMBOL-SEARCH-WATCHLIST.md +255 -0
  42. package/docs/STRUCTURE.md +51 -37
  43. package/docs/TODO-DESIGN.md +111 -37
  44. package/docs/TODO.md +26 -16
  45. package/docs/WORKSTREAMS.md +55 -0
  46. package/docs/api.md +655 -528
  47. package/docs/datafeed-guide.md +329 -311
  48. package/docs/design-astroneum.md +204 -65
  49. package/docs/plugin-development.md +569 -565
  50. package/docs/tv-functions-skill.md +88 -64
  51. package/package.json +158 -147
  52. package/dist/chunk-4YOLQ5DC.js +0 -2
  53. package/dist/chunk-I4YQBP2M.js +0 -3
  54. package/dist/chunk-OOMZRONH.js +0 -35
  55. package/dist/chunk-PV2GAINM.js +0 -2
  56. package/dist/chunk-YW3STIPY.js +0 -653
  57. package/dist/zh-CN-Y2C6RSOI.js +0 -3
@@ -0,0 +1,120 @@
1
+ import { a as CandleData, f as IndicatorTemplate } from './Indicator-BQQcVGGT.js';
2
+
3
+ type StrategySide = 'long' | 'short';
4
+ interface BacktestConfig {
5
+ initialCapital: number;
6
+ commissionPercent: number;
7
+ slippagePercent: number;
8
+ positionSizePercent: number;
9
+ currency: string;
10
+ }
11
+ interface BacktestTrade {
12
+ id: string;
13
+ side: StrategySide;
14
+ entryTime: number;
15
+ entryPrice: number;
16
+ exitTime: number;
17
+ exitPrice: number;
18
+ quantity: number;
19
+ grossProfit: number;
20
+ commission: number;
21
+ netProfit: number;
22
+ barsHeld: number;
23
+ }
24
+ interface EquityPoint {
25
+ timestamp: number;
26
+ equity: number;
27
+ drawdownPercent: number;
28
+ }
29
+ interface BacktestMetrics {
30
+ netProfit: number;
31
+ netProfitPercent: number;
32
+ grossProfit: number;
33
+ grossLoss: number;
34
+ profitFactor: number | null;
35
+ totalTrades: number;
36
+ percentProfitable: number;
37
+ maxDrawdown: number;
38
+ maxDrawdownPercent: number;
39
+ buyAndHoldPercent: number;
40
+ }
41
+ interface BacktestResult {
42
+ config: BacktestConfig;
43
+ metrics: BacktestMetrics;
44
+ trades: BacktestTrade[];
45
+ equity: EquityPoint[];
46
+ bars: CandleData[];
47
+ mode: 'fixture' | 'runtime';
48
+ }
49
+
50
+ /**
51
+ * Client-side JavaScript indicator scripting engine.
52
+ *
53
+ * Lets users write Pine-Script–inspired indicator logic in plain JS
54
+ * and compile it into an IndicatorTemplate that is registered
55
+ * with the chart engine during compilation.
56
+ *
57
+ * Security: scripts run inside a Function scope with a curated API
58
+ * surface (forbidden identifiers shadowed, Object/Array prototypes frozen).
59
+ * This is a best-effort sandbox suitable for trusted user scripts only —
60
+ * constructor-chain escapes (e.g. (0).constructor.constructor) are not
61
+ * fully blocked. Do NOT run untrusted third-party scripts without a
62
+ * real isolate (sandboxed iframe or Web Worker).
63
+ *
64
+ * Usage:
65
+ * const engine = ScriptEngine.getInstance()
66
+ * const template = engine.compile(`
67
+ * study('My MA', { overlay: true })
68
+ * const len = input('Length', 14)
69
+ * plot(ta.sma(close, len), { title: 'SMA', color: '#2962ff' })
70
+ * `)
71
+ * chart.createIndicator(template.name)
72
+ */
73
+
74
+ interface StudyOptions {
75
+ /** Display name shown in tooltip */
76
+ shortName?: string;
77
+ /** Whether to overlay on the price pane (default false) */
78
+ overlay?: boolean;
79
+ /** Precision of displayed values */
80
+ precision?: number;
81
+ }
82
+ interface PlotOptions {
83
+ title?: string;
84
+ color?: string;
85
+ lineWidth?: number;
86
+ visible?: boolean;
87
+ }
88
+ interface InputOptions {
89
+ type?: 'int' | 'float' | 'bool' | 'string';
90
+ options?: (number | string)[];
91
+ }
92
+ interface CompiledIndicator extends IndicatorTemplate {
93
+ /** Original source for editing */
94
+ _source: string;
95
+ }
96
+ interface CompiledStrategy {
97
+ name: string;
98
+ source: string;
99
+ run: (bars: CandleData[], config?: Partial<BacktestConfig>) => BacktestResult;
100
+ }
101
+ declare class ScriptEngine {
102
+ private static _instance;
103
+ private _registry;
104
+ private constructor();
105
+ static getInstance(): ScriptEngine;
106
+ /**
107
+ * Compile a script string into an IndicatorTemplate.
108
+ * Throws a descriptive Error if compilation or type validation fails.
109
+ */
110
+ compile(source: string, name?: string): CompiledIndicator;
111
+ compileStrategy(source: string, name?: string): CompiledStrategy;
112
+ /** Get a previously compiled template by name */
113
+ get(name: string): CompiledIndicator | undefined;
114
+ /** All registered script names */
115
+ list(): string[];
116
+ /** Remove a compiled indicator */
117
+ remove(name: string): void;
118
+ }
119
+
120
+ export { type BacktestConfig as B, type CompiledIndicator as C, type EquityPoint as E, type InputOptions as I, type PlotOptions as P, type StrategySide as S, type BacktestResult as a, type BacktestMetrics as b, type BacktestTrade as c, type CompiledStrategy as d, ScriptEngine as e, type StudyOptions as f };
@@ -0,0 +1,232 @@
1
+ import react__default from 'react';
2
+ import { D as DeepPartial, S as Styles, C as Chart, a as CandleData, I as IndicatorCreate, P as PaneOptions, N as Nullable, b as IndicatorFilter, c as Indicator, O as OverlayCreate, d as OverlayFilter, e as Coordinate, Z as ZoomAnchor } from './Indicator-BQQcVGGT.js';
3
+ import { I as IndicatorPlugin, P as Price, V as Volume, T as Timestamp } from './types-yETgeYsE.js';
4
+
5
+ interface IndicatorDef {
6
+ name: string;
7
+ calcParams?: number[];
8
+ }
9
+ interface SerializedIndicator extends IndicatorDef {
10
+ visible: boolean;
11
+ styles?: unknown;
12
+ }
13
+ /** Canonical timespan identifiers — aligned with engine PeriodType */
14
+ type Timespan = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year';
15
+ interface SymbolInfo {
16
+ ticker: string;
17
+ name?: string;
18
+ shortName?: string;
19
+ exchange?: string;
20
+ market?: string;
21
+ pricePrecision?: number;
22
+ volumePrecision?: number;
23
+ priceCurrency?: string;
24
+ type?: string;
25
+ logo?: string;
26
+ [key: string]: unknown;
27
+ }
28
+ interface Period {
29
+ multiplier: number;
30
+ /** Canonical timespan — maps directly to engine PeriodType */
31
+ timespan: Timespan;
32
+ text: string;
33
+ }
34
+ type DatafeedSubscribeCallback = (data: CandleData) => void;
35
+ interface QuoteSnapshot {
36
+ ticker: string;
37
+ last: Price;
38
+ change?: number;
39
+ changePercent?: number;
40
+ volume?: Volume;
41
+ open?: Price;
42
+ high?: Price;
43
+ low?: Price;
44
+ timestamp?: Timestamp;
45
+ }
46
+ interface Datafeed {
47
+ searchSymbols(search?: string): Promise<SymbolInfo[]>;
48
+ getHistoryData(symbol: SymbolInfo, period: Period, from: number, to: number): Promise<CandleData[]>;
49
+ subscribe(symbol: SymbolInfo, period: Period, callback: DatafeedSubscribeCallback): void;
50
+ unsubscribe(symbol: SymbolInfo, period: Period): void;
51
+ getQuotes?(symbols: SymbolInfo[]): Promise<QuoteSnapshot[]>;
52
+ }
53
+ interface ChartToolbarActions {
54
+ toggleDrawingBar: () => void;
55
+ openSymbolSearch: () => void;
56
+ openIndicators: () => void;
57
+ openTimezone: () => void;
58
+ openSettings: () => void;
59
+ openAlert: () => void;
60
+ captureScreenshot: () => void;
61
+ }
62
+ interface AstroneumOptions {
63
+ styles?: DeepPartial<Styles>;
64
+ watermark?: string | Node;
65
+ theme?: string;
66
+ locale?: string;
67
+ drawingBarVisible?: boolean;
68
+ toolbarVisible?: boolean;
69
+ toolbarActionsRef?: react__default.MutableRefObject<ChartToolbarActions | null>;
70
+ symbol: SymbolInfo;
71
+ period: Period;
72
+ periods?: Period[];
73
+ timezone?: string;
74
+ mainIndicators?: IndicatorDef[];
75
+ subIndicators?: string[];
76
+ plugins?: ChartPlugin[];
77
+ datafeed: Datafeed;
78
+ /** Inline styles applied to the root `.astroneum` container element. */
79
+ style?: react__default.CSSProperties;
80
+ /** Additional class names applied to the root `.astroneum` container element. */
81
+ className?: string;
82
+ /**
83
+ * Make the chart canvas focusable + emit an `aria-live` summary of the
84
+ * crosshair OHLCV. Off by default to avoid surprising existing apps.
85
+ * When `true`, the chart container gets `tabindex=0`, `role="img"`,
86
+ * and an `aria-label` describing the current symbol. Cursor moves
87
+ * announce OHLCV through a visually-hidden polite live region.
88
+ */
89
+ accessible?: boolean;
90
+ /**
91
+ * Optional ARIA label override for the chart canvas. Only used when
92
+ * `accessible` is true. Defaults to a string built from the symbol
93
+ * ticker and current period.
94
+ */
95
+ ariaLabel?: string;
96
+ /**
97
+ * Bar rendering style. Defaults to 'candle' (standard candlesticks).
98
+ * 'heikin_ashi' transforms OHLC data with the Heikin-Ashi formula
99
+ * before rendering — no datafeed changes required.
100
+ */
101
+ barStyle?: 'candle' | 'heikin_ashi';
102
+ /**
103
+ * Price axis scale mode. Defaults to 'linear' (absolute price).
104
+ * 'log' uses logarithmic scale (useful for long-term charts).
105
+ * 'percent' shows percentage change from the first visible bar.
106
+ */
107
+ priceScale?: 'linear' | 'log' | 'percent';
108
+ initialHistory?: 'all' | 'none';
109
+ }
110
+ /**
111
+ * Serialised chart state — drawings, indicators, and the user-facing
112
+ * surface settings (theme, locale, timezone, symbol, period, styles).
113
+ *
114
+ * The shape is intentionally simple JSON so it can be persisted to
115
+ * `localStorage`, a URL, or a backend. The `version` field lets the
116
+ * library evolve the format without breaking older payloads.
117
+ */
118
+ interface SerializedChartState {
119
+ version: 1;
120
+ theme: string;
121
+ locale: string;
122
+ timezone: string;
123
+ symbol: SymbolInfo;
124
+ period: Period;
125
+ styles: DeepPartial<Styles>;
126
+ mainIndicators: SerializedIndicator[];
127
+ subIndicators: SerializedIndicator[];
128
+ overlays: Array<{
129
+ name: string;
130
+ points?: ReadonlyArray<{
131
+ timestamp?: number;
132
+ value?: number;
133
+ dataIndex?: number;
134
+ }>;
135
+ styles?: unknown;
136
+ lock?: boolean;
137
+ visible?: boolean;
138
+ }>;
139
+ }
140
+ interface AstroneumHandle {
141
+ setTheme(theme: string): void;
142
+ getTheme(): string;
143
+ setStyles(styles: DeepPartial<Styles>): void;
144
+ getStyles(): Styles;
145
+ setLocale(locale: string): void;
146
+ getLocale(): string;
147
+ setTimezone(timezone: string): void;
148
+ getTimezone(): string;
149
+ setSymbol(symbol: SymbolInfo): void;
150
+ getSymbol(): SymbolInfo;
151
+ setPeriod(period: Period): void;
152
+ getPeriod(): Period;
153
+ getDataListLength(): number;
154
+ getLastDataTimestamp(): number | null;
155
+ /**
156
+ * Capture the chart's current user-visible state as a JSON-safe object.
157
+ * Includes theme, locale, timezone, symbol, period, styles, indicators,
158
+ * and drawing overlays. Pair with `loadState()` to restore.
159
+ */
160
+ serializeState(): SerializedChartState;
161
+ /**
162
+ * Restore a previously-captured state. Best-effort: missing or unknown
163
+ * indicator / overlay names are silently skipped, matching the engine's
164
+ * behaviour for unregistered names.
165
+ */
166
+ loadState(state: SerializedChartState): void;
167
+ /**
168
+ * Subscribe to crosshair moves. Returns an unsubscribe function.
169
+ * Callback receives the crosshair data (or null when it leaves the chart).
170
+ * Returns a Coordinate-like object with the timestamp + price at cursor.
171
+ */
172
+ onCrosshairMove(callback: (data: unknown) => void): () => void;
173
+ /**
174
+ * Programmatically set the crosshair position on this chart.
175
+ * Used internally by MultiChartLayout for crosshair sync.
176
+ */
177
+ setCrosshair(data: unknown): void;
178
+ /** Lock or unlock ALL drawing overlays at once. */
179
+ lockAllDrawings(locked: boolean): void;
180
+ createIndicator(value: string | IndicatorCreate, isStack?: boolean, paneOptions?: PaneOptions): Nullable<string>;
181
+ removeIndicator(filter?: IndicatorFilter): boolean;
182
+ getIndicators(filter?: IndicatorFilter): Indicator[];
183
+ createOverlay(value: string | OverlayCreate | Array<string | OverlayCreate>): Nullable<string> | Array<Nullable<string>>;
184
+ removeOverlay(filter?: OverlayFilter): boolean;
185
+ getDataList(): CandleData[];
186
+ resetData(): void;
187
+ resize(): void;
188
+ }
189
+ interface ChartPluginContext {
190
+ /** The chart instance mounted by Astroneum. */
191
+ chart: Chart;
192
+ /** Register a typed indicator plugin at runtime. */
193
+ registerIndicatorPlugin: (plugin: IndicatorPlugin<unknown>) => void;
194
+ /** Batch register typed indicator plugins at runtime. */
195
+ registerIndicatorPlugins: (plugins: ReadonlyArray<IndicatorPlugin<unknown>>) => void;
196
+ }
197
+ interface ChartPlugin {
198
+ /** Optional plugin label for diagnostics and debugging. */
199
+ name?: string;
200
+ /** Indicator plugins to register before this chart starts creating indicators. */
201
+ indicators?: ReadonlyArray<IndicatorPlugin<unknown>>;
202
+ /** Optional lifecycle hook called once after the chart is initialized. */
203
+ onInit?: (context: ChartPluginContext) => void | (() => void);
204
+ }
205
+ interface ChartEventMap {
206
+ /** Fired when the active symbol changes. */
207
+ 'symbol-change': SymbolInfo;
208
+ /** Fired when the active period / timeframe changes. */
209
+ 'period-change': Period;
210
+ /** Fired on every crosshair move; null when the crosshair leaves the chart. */
211
+ 'crosshair-move': Coordinate | null;
212
+ /** Fired when the user zooms the chart. */
213
+ 'zoom': {
214
+ scale: number;
215
+ anchor: ZoomAnchor;
216
+ };
217
+ /** Fired when a historical data batch is loaded. */
218
+ 'data-load': {
219
+ type: 'init' | 'forward' | 'backward';
220
+ count: number;
221
+ };
222
+ /** Fired when a real-time tick arrives from the datafeed subscription. */
223
+ 'tick': CandleData;
224
+ /** Fired when the user starts drawing an overlay (value = overlay type name). */
225
+ 'drawing-start': string;
226
+ /** Fired when the user finishes drawing (value = overlay id). */
227
+ 'drawing-end': string;
228
+ /** Fired when the active theme changes (value = theme name). */
229
+ 'theme-change': string;
230
+ }
231
+
232
+ export type { AstroneumOptions as A, ChartEventMap as C, Datafeed as D, Period as P, QuoteSnapshot as Q, SymbolInfo as S, AstroneumHandle as a, SerializedChartState as b, ChartPlugin as c, ChartPluginContext as d, ChartToolbarActions as e, DatafeedSubscribeCallback as f, SerializedIndicator as g };
@@ -0,0 +1,28 @@
1
+ import { a as CandleData } from './Indicator-BQQcVGGT.js';
2
+
3
+ type Price = number & {
4
+ readonly _brand: 'Price';
5
+ };
6
+ type Volume = number & {
7
+ readonly _brand: 'Volume';
8
+ };
9
+ type Timestamp = number & {
10
+ readonly _brand: 'Timestamp';
11
+ };
12
+ interface Viewport {
13
+ priceMin: Price;
14
+ priceMax: Price;
15
+ timeMin: Timestamp;
16
+ timeMax: Timestamp;
17
+ resolution: readonly [width: number, height: number];
18
+ }
19
+ interface IndicatorPlugin<TOutput> {
20
+ name: string;
21
+ shortName?: string;
22
+ calcParams?: number[];
23
+ calc(data: CandleData[], params: number[]): TOutput[];
24
+ render2D?(ctx: CanvasRenderingContext2D, output: TOutput[], viewport: Viewport): void;
25
+ renderGL?(gl: WebGL2RenderingContext, output: TOutput[], viewport: Viewport, vbo: WebGLBuffer): void;
26
+ }
27
+
28
+ export type { IndicatorPlugin as I, Price as P, Timestamp as T, Volume as V, Viewport as a };
@@ -0,0 +1,3 @@
1
+ 'use client';
2
+ var t="Indicator",r="Main Indicator",o="Sub Indicator",i="Setting",l="Timezone",n="Screenshot",_="Full Screen",c="Exit",s="Save",d="Confirm",h="Color",g="Show",m="\u53D6\u6D88",p="MA(Moving Average)",b="EMA(Exponential Moving Average)",v="SMA",u="BOLL(Bolinger Bands)",w="BBI(Bull And Bear Index)",f="SAR(Stop and Reverse)",C="VOL(Volume)",k="MACD(Moving Average Convergence / Divergence)",y="KDJ(KDJ Index)",S="RSI(Relative Strength Index)",T="BIAS(Bias Ratio)",x="BRAR(Bull and Bear Sentiment)",A="CCI(Commodity Channel Index)",P="DMI(Directional Movement Index)",L="CR(Energy Index)",R="PSY(Psychological Line)",U="DMA(Different of Moving Average)",B="TRIX(Triple Exponentially Smoothed Moving Average)",M="OBV(On Balance Volume)",I="VR(Volatility Volume Ratio)",V="WR(Williams %R)",D="MTM(Momentum Index)",W="EMV(Ease of Movement Value)",E="ROC(Price Rate of Change)",F="PVT(Price and Volume Trend)",O="AO(Awesome Oscillator)",z="UTC",H="(UTC-10) Honolulu",J="(UTC-8) Juneau",K="(UTC-7) Los Angeles",G="(UTC-5) Chicago",j="(UTC-4) Toronto",N="(UTC-3) Sao Paulo",X="(UTC+1) London",q="(UTC+2) Berlin",Y="(UTC+3) Bahrain",Q="(UTC+4) Dubai",Z="(UTC+5) Ashkhabad",$="(UTC+6) Almaty",ee="(UTC+7) Bangkok",ae="(UTC+8) Shanghai",te="(UTC+9) Tokyo",re="(UTC+10) Sydney",oe="(UTC+12) Norfolk",ie="Horizontal Line",le="Horizontal Ray",ne="Horizontal Segment",_e="Vertical Line",ce="Vertical Ray",se="Vertical Segment",de="Trend Line",he="Ray",ge="Segment",me="Arrow",pe="Price Line",be="Price Channel Line",ve="Parallel Line",ue="Fibonacci Line",we="Fibonacci Segment",fe="Fibonacci Circle",Ce="Fibonacci Spiral",ke="Fibonacci Sector",ye="Fibonacci Extension",Se="Gann Box",Te="Gann Fan",xe="Andrews Pitchfork",Ae="Rect",Pe="Parallelogram",Le="Circle",Re="Triangle",Ue="Three Waves",Be="Five Waves",Me="Eight Waves",Ie="Any Waves",Ve="ABCD Pattern",De="XABCD Pattern",We="Weak Magnet",Ee="Strong Magnet",Fe="Symbol Search",Oe="Symbol Code",ze="Parameter 1",He="Parameter 2",Je="Parameter 3",Ke="Parameter 4",Ge="Parameter 5",je="Line 1",Ne="Line 2",Xe="Line 3",qe="Line 4",Ye="Line 5",Qe="Period",Ze="Standard Deviation",$e="Candle Type",ea="Candle Solid",aa="Candle Stroke",ta="Candle Up Stroke",ra="Candle Down Stroke",oa="OHLC",ia="Area",la="Show Last Price",na="Show Highest Price",_a="Show Lowest Price",ca="Show indicator's last value",sa="Price Axis Type",da="Normal",ha="Percentage",ga="Log",ma="Reverse Coordinate",pa="Show Grids",ba="\u6062\u590D\u9ED8\u8BA4",va="\u4EF7\u683C\u63D0\u9192",ua="\u521B\u5EFA",wa="\u521B\u5EFA\u63D0\u9192",fa="\u521B\u5EFA\u63D0\u9192\u4E8E",Ca="\u4FDD\u5B58",ka="\u5386\u53F2",ya="\u6761\u4EF6",Sa="\u4EF7\u683C",Ta="\u6570\u636E\u6E90",xa="\u4EF7\u683C",Aa="\u8FD0\u7B97\u7B26",Pa="\u503C",La="\u4EA4\u53C9",Ra="\u4ECB\u4E8E",Ua="\u6DFB\u52A0\u6761\u4EF6",Ba="\u5907\u6CE8",Ma="\u53EF\u9009\u6807\u7B7E\u2026",Ia="\u6DFB\u52A0\u63D0\u9192",Va="\u521B\u5EFA",Da="\u521B\u5EFA\u4E2D\u2026",Wa="\u4FDD\u5B58\u4E2D\u2026",Ea="\u8BF7\u8F93\u5165\u6709\u6548\u6570\u5B57",Fa="\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u6709\u6548\u6761\u4EF6",Oa="\u63D0\u9192\u6570\u91CF\u5DF2\u8FBE\u4E0A\u9650",za="\u91CD\u8BD5",Ha="\u6D3B\u8DC3",Ja="\u6682\u65E0\u6D3B\u8DC3\u63D0\u9192",Ka="\u6682\u65E0\u89E6\u53D1\u8BB0\u5F55",Ga="\u91CD\u65B0\u6FC0\u6D3B",ja="\u6682\u505C",Na="\u6062\u590D",Xa="\u7F16\u8F91",qa="\u5220\u9664",Ya="\u641C\u7D22\u63D0\u9192\u2026",Qa="\u6240\u6709\u54C1\u79CD",Za="\u5F53\u524D\u54C1\u79CD",$a="\u5DF2\u6682\u505C",et="\u5DF2\u8FC7\u671F",at="\u5DF2\u5FFD\u7565",tt="\u9AD8\u4E8E",rt="\u4F4E\u4E8E",ot="\u4E0A\u7A7F \u2191",it="\u4E0B\u7A7F \u2193",lt="\u89E6\u53D1\u65B9\u5F0F",nt="\u4EC5\u4E00\u6B21",_t="\u6BCF\u6839K\u7EBF",ct="K\u7EBF\u6536\u76D8\u65F6\u4E00\u6B21",st="\u5230\u671F",dt="\u65E0\u9650\u671F",ht="1\u5929\u540E",gt="1\u5468\u540E",mt="1\u4E2A\u6708\u540E",pt="2\u4E2A\u6708\u540E",bt="\u6D88\u606F",vt="\u8BBE\u7F6E Webhook URL \u540E\u5C06\u4F5C\u4E3A\u8BF7\u6C42\u4F53\u53D1\u9001\u3002",ut="Webhook \u8BF7\u6C42\u4F53",wt="\u901A\u77E5",ft="\u8FD4\u56DE",Ct="\u5E94\u7528",kt="\u5E94\u7528\u5185\u901A\u77E5",yt="\u5728\u79FB\u52A8\u5E94\u7528\u4E2D\u63A8\u9001\u901A\u77E5\u3002",St="\u663E\u793A\u6D6E\u5C42\u63D0\u793A",Tt="\u5728\u9875\u9762\u89D2\u843D\u663E\u793A\u7AD9\u5185\u901A\u77E5\u3002",xt="\u53D1\u9001\u90AE\u4EF6",At="\u53D1\u9001\u5230\u8D26\u6237\u8BBE\u7F6E\u4E2D\u7684\u90AE\u7BB1\u5730\u5740\u3002",Pt="Webhook URL",Lt="\u63D0\u9192\u89E6\u53D1\u65F6\u5411\u4F60\u7684 URL \u53D1\u9001 POST \u8BF7\u6C42\u3002",Rt="\u64AD\u653E\u58F0\u97F3",Ut="\u63D0\u9192\u89E6\u53D1\u65F6\u64AD\u653E\u63D0\u793A\u97F3\u3002",Bt="\u53D1\u9001\u7EAF\u6587\u672C",Mt="\u5411\u5907\u7528\u90AE\u7BB1\u53D1\u9001\u7EAF\u6587\u672C\u3002",It="\u65E0",Vt="URL \u6709\u6548",Dt="URL \u65E0\u6548 \u2014 \u8BF7\u4F7F\u7528 http(s)://\uFF0C\u4E0D\u652F\u6301\u79C1\u6709\u6216\u672C\u5730\u5730\u5740",Wt="\u4E00\u6B21",Et="\u91CD\u590D",Ft="\u901A\u77E5\u8BA1\u5212",Ot="\u5728\u8BA1\u5212\u4E4B\u5916\u7684\u65F6\u95F4\u9759\u97F3\u901A\u77E5\u3002",zt="\u5168\u5929",Ht="\u5DE5\u4F5C\u65E5",Jt="\u5DE5\u4F5C\u65F6\u95F4\uFF08\u5468\u4E00\u81F3\u5468\u4E94 09:00\u201318:00\uFF09",Kt="\u81EA\u5B9A\u4E49",Gt="\u5DF2\u9001\u8FBE",jt="\u5931\u8D25",Nt="\u53D1\u9001\u4E2D",Xt="\u65E5\u5FD7",qt="\u6E05\u9664\u65E5\u5FD7",Yt="\u64CD\u4F5C",Qt="\u6682\u65E0\u89E6\u53D1\u8BB0\u5F55",Zt="Webhook \u5DF2\u6210\u529F\u9001\u8FBE",$t="Webhook \u53D1\u9001\u5931\u8D25",er="\u8BF7\u6C42\u8D85\u65F6",ar="\u4E86\u89E3\u66F4\u591A",tr="\u5438\u9644\u5230\u4EF7\u683C\u6C34\u5E73",rr="\u4E0D\u5438\u9644",or="\u811A\u672C\u7F16\u8F91\u5668",ir="\u7F16\u5199 JavaScript \u2014 \u4F7F\u7528 Pine Script \u98CE\u683C\u7684 API\uFF08ta.* \u8F85\u52A9\u51FD\u6570\uFF09",lr="\u7F16\u8BD1",nr="\u6DFB\u52A0\u5230\u56FE\u8868",_r="\u5DF2\u7F16\u8BD1",cr="API \u53C2\u8003",sr="\u81EA\u9009\u80A1",dr="\u65B0\u5EFA\u81EA\u9009",hr="\u6DFB\u52A0\u81EA\u9009\u5217\u8868",gr="\u5220\u9664\u81EA\u9009\u5217\u8868",mr="\u6DFB\u52A0",pr="\u79FB\u9664",br="\u8F93\u5165\u4EE3\u7801\u2026",vr={indicator:t,main_indicator:r,sub_indicator:o,setting:i,timezone:l,screenshot:n,full_screen:_,exit_full_screen:c,save:s,confirm:d,color:h,show:g,cancel:m,ma:p,ema:b,sma:v,boll:u,bbi:w,sar:f,vol:C,macd:k,kdj:y,rsi:S,bias:T,brar:x,cci:A,dmi:P,cr:L,psy:R,dma:U,trix:B,obv:M,vr:I,wr:V,mtm:D,emv:W,roc:E,pvt:F,ao:O,utc:z,honolulu:H,juneau:J,los_angeles:K,chicago:G,toronto:j,sao_paulo:N,london:X,berlin:q,bahrain:Y,dubai:Q,ashkhabad:Z,almaty:$,bangkok:ee,shanghai:ae,tokyo:te,sydney:re,norfolk:oe,horizontal_straight_line:ie,horizontal_ray_line:le,horizontal_segment:ne,vertical_straight_line:_e,vertical_ray_line:ce,vertical_segment:se,straight_line:de,ray_line:he,segment:ge,arrow:me,price_line:pe,price_channel_line:be,parallel_straight_line:ve,fibonacci_line:ue,fibonacci_segment:we,fibonacci_circle:fe,fibonacci_spiral:Ce,fibonacci_speed_resistance_fan:ke,fibonacci_extension:ye,gann_box:Se,gann_fan:Te,pitchfork:xe,rect:Ae,parallelogram:Pe,circle:Le,triangle:Re,three_waves:Ue,five_waves:Be,eight_waves:Me,any_waves:Ie,abcd:Ve,xabcd:De,weak_magnet:We,strong_magnet:Ee,symbol_search:Fe,symbol_code:Oe,params_1:ze,params_2:He,params_3:Je,params_4:Ke,params_5:Ge,line_1:je,line_2:Ne,line_3:Xe,line_4:qe,line_5:Ye,period:Qe,standard_deviation:Ze,candle_type:$e,candle_solid:ea,candle_stroke:aa,candle_up_stroke:ta,candle_down_stroke:ra,ohlc:oa,area:ia,last_price_show:la,high_price_show:na,low_price_show:_a,indicator_last_value_show:ca,price_axis_type:sa,normal:da,percentage:ha,log:ga,reverse_coordinate:ma,grid_show:pa,restore_default:ba,alerts:va,alert_create:ua,alert_create_alert:wa,alert_create_on:fa,alert_save:Ca,alert_history:ka,alert_condition:ya,alert_price:Sa,alert_source:Ta,alert_source_price:xa,alert_operator:Aa,alert_value:Pa,alert_crosses:La,alert_is_between:Ra,alert_add_condition:Ua,alert_note:Ba,alert_note_placeholder:Ma,alert_add:Ia,alert_create_button:Va,alert_submitting:Da,alert_saving:Wa,alert_invalid_price:Ea,alert_invalid_condition:Fa,alert_quota_exceeded:Oa,alert_retry:za,alert_active:Ha,alert_no_active:Ja,alert_no_history:Ka,alert_reactivate:Ga,alert_pause:ja,alert_resume:Na,alert_edit:Xa,alert_delete:qa,alert_search:Ya,alert_filter_all:Qa,alert_filter_current:Za,alert_paused:$a,alert_expired:et,alert_dismissed:at,alert_above:tt,alert_below:rt,alert_crosses_above:ot,alert_crosses_below:it,alert_trigger:lt,alert_trigger_once:nt,alert_trigger_every_bar:_t,alert_trigger_bar_close:ct,alert_expiration:st,alert_expiration_open:dt,alert_expiration_1d:ht,alert_expiration_1w:gt,alert_expiration_1m:mt,alert_expiration_2m:pt,alert_message:bt,alert_message_hint:vt,alert_webhook_body:ut,alert_notifications:wt,alert_back:ft,alert_apply:Ct,alert_notif_app:kt,alert_notif_app_desc:yt,alert_notif_toast:St,alert_notif_toast_desc:Tt,alert_notif_email:xt,alert_notif_email_desc:At,alert_notif_webhook:Pt,alert_notif_webhook_desc:Lt,alert_notif_sound:Rt,alert_notif_sound_desc:Ut,alert_notif_plaintext:Bt,alert_notif_plaintext_desc:Mt,alert_notif_none:It,alert_webhook_valid:Vt,alert_webhook_invalid:Dt,alert_sound_once:Wt,alert_sound_repeating:Et,alert_schedule:Ft,alert_schedule_desc:Ot,"alert_schedule_24/7":zt,alert_schedule_weekdays:Ht,alert_schedule_working_hours:Jt,alert_schedule_custom:Kt,alert_webhook_delivered:Gt,alert_webhook_failed:jt,alert_webhook_sending:Nt,alert_log:Xt,alert_clear_log:qt,alert_actions:Yt,alert_no_log:Qt,alert_webhook_success:Zt,alert_webhook_failed_prefix:$t,alert_webhook_timeout:er,alert_webhook_learn_more:ar,snap_levels:tr,no_magnet:rr,script_editor:or,script_editor_hint:ir,script_compile:lr,script_apply:nr,script_compiled:_r,script_api_ref:cr,watchlist:sr,watchlist_new:dr,watchlist_add_list:hr,watchlist_delete_list:gr,watchlist_add_symbol:mr,watchlist_remove_symbol:pr,watchlist_enter_symbol:br};
3
+ export{Ve as abcd,tt as alert_above,Yt as alert_actions,Ha as alert_active,Ia as alert_add,Ua as alert_add_condition,Ct as alert_apply,ft as alert_back,rt as alert_below,qt as alert_clear_log,ya as alert_condition,ua as alert_create,wa as alert_create_alert,Va as alert_create_button,fa as alert_create_on,La as alert_crosses,ot as alert_crosses_above,it as alert_crosses_below,qa as alert_delete,at as alert_dismissed,Xa as alert_edit,st as alert_expiration,ht as alert_expiration_1d,mt as alert_expiration_1m,gt as alert_expiration_1w,pt as alert_expiration_2m,dt as alert_expiration_open,et as alert_expired,Qa as alert_filter_all,Za as alert_filter_current,ka as alert_history,Fa as alert_invalid_condition,Ea as alert_invalid_price,Ra as alert_is_between,Xt as alert_log,bt as alert_message,vt as alert_message_hint,Ja as alert_no_active,Ka as alert_no_history,Qt as alert_no_log,Ba as alert_note,Ma as alert_note_placeholder,kt as alert_notif_app,yt as alert_notif_app_desc,xt as alert_notif_email,At as alert_notif_email_desc,It as alert_notif_none,Bt as alert_notif_plaintext,Mt as alert_notif_plaintext_desc,Rt as alert_notif_sound,Ut as alert_notif_sound_desc,St as alert_notif_toast,Tt as alert_notif_toast_desc,Pt as alert_notif_webhook,Lt as alert_notif_webhook_desc,wt as alert_notifications,Aa as alert_operator,ja as alert_pause,$a as alert_paused,Sa as alert_price,Oa as alert_quota_exceeded,Ga as alert_reactivate,Na as alert_resume,za as alert_retry,Ca as alert_save,Wa as alert_saving,Ft as alert_schedule,zt as "alert_schedule_24/7",Kt as alert_schedule_custom,Ot as alert_schedule_desc,Ht as alert_schedule_weekdays,Jt as alert_schedule_working_hours,Ya as alert_search,Wt as alert_sound_once,Et as alert_sound_repeating,Ta as alert_source,xa as alert_source_price,Da as alert_submitting,lt as alert_trigger,ct as alert_trigger_bar_close,_t as alert_trigger_every_bar,nt as alert_trigger_once,Pa as alert_value,ut as alert_webhook_body,Gt as alert_webhook_delivered,jt as alert_webhook_failed,$t as alert_webhook_failed_prefix,Dt as alert_webhook_invalid,ar as alert_webhook_learn_more,Nt as alert_webhook_sending,Zt as alert_webhook_success,er as alert_webhook_timeout,Vt as alert_webhook_valid,va as alerts,$ as almaty,Ie as any_waves,O as ao,ia as area,me as arrow,Z as ashkhabad,Y as bahrain,ee as bangkok,w as bbi,q as berlin,T as bias,u as boll,x as brar,m as cancel,ra as candle_down_stroke,ea as candle_solid,aa as candle_stroke,$e as candle_type,ta as candle_up_stroke,A as cci,G as chicago,Le as circle,h as color,d as confirm,L as cr,vr as default,U as dma,P as dmi,Q as dubai,Me as eight_waves,b as ema,W as emv,c as exit_full_screen,fe as fibonacci_circle,ye as fibonacci_extension,ue as fibonacci_line,we as fibonacci_segment,ke as fibonacci_speed_resistance_fan,Ce as fibonacci_spiral,Be as five_waves,_ as full_screen,Se as gann_box,Te as gann_fan,pa as grid_show,na as high_price_show,H as honolulu,le as horizontal_ray_line,ne as horizontal_segment,ie as horizontal_straight_line,t as indicator,ca as indicator_last_value_show,J as juneau,y as kdj,la as last_price_show,je as line_1,Ne as line_2,Xe as line_3,qe as line_4,Ye as line_5,ga as log,X as london,K as los_angeles,_a as low_price_show,p as ma,k as macd,r as main_indicator,D as mtm,rr as no_magnet,oe as norfolk,da as normal,M as obv,oa as ohlc,ve as parallel_straight_line,Pe as parallelogram,ze as params_1,He as params_2,Je as params_3,Ke as params_4,Ge as params_5,ha as percentage,Qe as period,xe as pitchfork,sa as price_axis_type,be as price_channel_line,pe as price_line,R as psy,F as pvt,he as ray_line,Ae as rect,ba as restore_default,ma as reverse_coordinate,E as roc,S as rsi,N as sao_paulo,f as sar,s as save,n as screenshot,cr as script_api_ref,nr as script_apply,lr as script_compile,_r as script_compiled,or as script_editor,ir as script_editor_hint,ge as segment,i as setting,ae as shanghai,g as show,v as sma,tr as snap_levels,Ze as standard_deviation,de as straight_line,Ee as strong_magnet,o as sub_indicator,re as sydney,Oe as symbol_code,Fe as symbol_search,Ue as three_waves,l as timezone,te as tokyo,j as toronto,Re as triangle,B as trix,z as utc,ce as vertical_ray_line,se as vertical_segment,_e as vertical_straight_line,C as vol,I as vr,sr as watchlist,hr as watchlist_add_list,mr as watchlist_add_symbol,gr as watchlist_delete_list,br as watchlist_enter_symbol,dr as watchlist_new,pr as watchlist_remove_symbol,We as weak_magnet,V as wr,De as xabcd};
@@ -0,0 +1,255 @@
1
+ # Mirror Plan: Symbol Search + Watchlist Parity
2
+
3
+ > **Status:** Phases 2–5 executed 2026-07-13 (commit `c14a88c` local / `4bfe8fd` server). **Phase 1 (dedicated capture scripts) was not executed** — see note below.
4
+ > **Date:** 2026-07-13
5
+ > **CDP source:** Chrome port 9223, chart `8Wh6yRIo`, BTC.D active
6
+ > **Scope:** Capture live TV DOM for Symbol Search + Watchlist; implement functional parity in Astroneum
7
+ >
8
+ > **Execution note:** Phase 1's `cdp-symbol-search.js` / `cdp-watchlist.js` / `ref-symbol-search.html` / `ref-watchlist.html` were never created. The geometry and DOM structure used for Phases 2–5 came from ad hoc CDP `Runtime.evaluate` calls during the working session, recorded inline in `tv-mirror-reference/structure.md` §4.1 and `watchlist.md` §2.4/§3/§8. This is an honest gap, not a silent one: the values are real (captured live), but the capture is not re-runnable from a checked-in script. See `tv-mirror-reference/README.md` for the current, corrected evidence-files table. A follow-up should build the two capture scripts before the next visual refresh.
9
+ >
10
+ > **What actually shipped (Phases 2–3):** rewrote `SymbolSearchModal` (840px, 11 asset tabs, 4-cell rows, debounce, token-driven styling) and fixed the `Modal`/`Input` primitives it depends on (initial-focus targeting, native attribute forwarding, removed a hardcoded TradingView-blue focus glow). Rebuilt the Watchlist header to TV's actual 4-control anatomy (list selector / Add symbol / Advanced view / Settings) instead of the previous 5-tab layout, added collapsible group headers, widened the sidebar, and wired a working resize handle. Full detail in `docs/TODO-DESIGN.md` changelog, entry "2026-07-13 (c)".
11
+ >
12
+ > **What did not ship:** the automated visual-regression gate (Phase 5 in spirit, never formally scoped here) that the accompanying UX audit called for. Verification for this pass was `pnpm verify` + demo build + CDP computed-style comparison, not a screenshot-diff CI gate.
13
+
14
+ ---
15
+
16
+ ## Context
17
+
18
+ Both surfaces were inspected live via CDP on 2026-07-13.
19
+
20
+ ### Symbol Search is a shared library component
21
+
22
+ `src/widget/symbol-search-modal/index.tsx` is consumed by two entry points:
23
+ - `AstroneumChart.tsx:656` — chart symbol change (`chart.setSymbol()`)
24
+ - `WatchlistPanel.tsx:219` — add to watchlist (`manager.addSymbolFromInfo()`)
25
+
26
+ Rewriting it improves both entry points simultaneously.
27
+
28
+ ### Current state
29
+
30
+ | Surface | File | Lines | Gap |
31
+ |---|---|---|---|
32
+ | Symbol Search (library) | `src/widget/symbol-search-modal/index.tsx` | 100 | Large — 460px, no tabs, no row structure, no debounce |
33
+ | Symbol Search SCSS | `src/widget/symbol-search-modal/index.scss` | 42 | Layout for 460px modal |
34
+ | Watchlist (demo) | `demo/src/app/components/panels/WatchlistPanel.tsx` | 514 | Small — missing section group headers |
35
+ | Watchlist CSS | `demo/src/app/components/panels/panels.css` L1-286 | 286 | Mature |
36
+ | WatchlistManager (library) | `src/chart/WatchlistManager.ts` | 244 | Stable — add `group` field |
37
+
38
+ ### Live TV DOM (observed via CDP 2026-07-13)
39
+
40
+ **Symbol Search dialog** — `[data-name="symbol-search-items-dialog"]` role=dialog, 840x680px:
41
+ ```
42
+ Title "Symbol search" + Close [data-qa-id="close"]
43
+ Input [data-qa-id="symbol-search-input"] role=searchbox, placeholder="Symbol, ISIN, or CUSIP"
44
+ Tabs [role=tablist]: All(active) / Stocks / Funds / Futures / Forex / Crypto / Indices / Bonds / Economy / Options / More
45
+ Results: [data-name="symbol-search-dialog-content-item"] rows, 4 cells:
46
+ 1. ticker (bold, e.g. "BTCUSD")
47
+ 2. description (muted, e.g. "Bitcoin / U.S. dollar")
48
+ 3. exchange (smaller, e.g. "spot crypto defiBitstamp")
49
+ 4. actions (empty, hover-revealed)
50
+ 51 rows in listContainer, active row has indigo tint
51
+ Footer: "Search using ISIN and CUSIP codes"
52
+ ```
53
+
54
+ **Watchlist panel** — rail `[data-name="base"]` 44x44px aria-pressed=true:
55
+ ```
56
+ Header:
57
+ [data-name="watchlists-button"] "Red list" 102x34px
58
+ [data-name="add-symbol-button"] aria-label="Add symbol" 38x38px
59
+ [data-name="advanced-view-button"] aria-label="Advanced view" 38x38px
60
+ [data-name="settings-button"] aria-label="Settings" 38x38px
61
+ Columns: Symbol / Last / Chg / Chg% (sortable buttons, class="sortable-PYr1BHmD", NO aria-sort on TV)
62
+ Rows: [data-name="symbol-list-wrap"] 319x775px
63
+ Each row: ticker + "Market open" status + last + chg + chg% + volume
64
+ Section dividers: plain text group headers (e.g. "US STOCK")
65
+ Details: details-add-note / details-settings / details-more-technicals
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Phase 1: CDP Capture
71
+
72
+ ### 1A. Create two targeted capture scripts
73
+
74
+ | Script | Output | Target |
75
+ |---|---|---|
76
+ | `cdp-symbol-search.js` | `ref-symbol-search.html` + `symbol-search.png` | `[data-name="symbol-search-items-dialog"]` outerHTML + screenshot |
77
+ | `cdp-watchlist.js` | `ref-watchlist.html` + `watchlist.png` | Watchlist panel (header + columns + rows) outerHTML + screenshot |
78
+
79
+ Both reuse the existing CDP pattern (port 9223, WebSocket, `Runtime.evaluate` + `Page.captureScreenshot`). Output only to `tv-mirror-reference/`.
80
+
81
+ ### 1B. Update reference docs
82
+
83
+ | File | Section | Change |
84
+ |---|---|---|
85
+ | `tv-mirror-reference/watchlist.md` | 2.4, 3, 8 | Fill `not captured` items: row DOM (4 cells per row), section group headers ("US STOCK"), "Market open" status, details buttons |
86
+ | `tv-mirror-reference/structure.md` | 4.1 | Update Symbol Search: 840px width, 11 tabs, 4-cell rows, footer text, selectors |
87
+ | `tv-mirror-reference/README.md` | Evidence files table | Add `ref-symbol-search.html`, `ref-watchlist.html` rows |
88
+
89
+ ---
90
+
91
+ ## Phase 2: Symbol Search Rewrite (library-level)
92
+
93
+ ### 2A. Component rewrite — `src/widget/symbol-search-modal/index.tsx`
94
+
95
+ | # | Change | Why |
96
+ |---|---|---|
97
+ | S1 | Width 460px to 840px | Fit 4-cell rows |
98
+ | S2 | Asset-class tabs: All / Stocks / Funds / Futures / Forex / Crypto / Indices / Bonds / Economy / Options / More | Core TV function — filter results by `symbol.type` |
99
+ | S3 | 4-cell result rows: ticker / description / exchange / actions | TV row structure |
100
+ | S4 | Keyboard up/down navigation through results | Functional — can't use dialog without it |
101
+ | S5 | 200ms debounce on input | Prevents API hammering (Polygon) |
102
+ | S6 | Footer "Search using ISIN and CUSIP codes" | Part of the dialog |
103
+
104
+ `SymbolInfo.type` is `string` (not union) — tab filter maps common values client-side: `stock/equity` to Stocks, `crypto` to Crypto, `forex/fx` to Forex, etc. Undefined `type` shows in "All" only.
105
+
106
+ ### 2B. Props interface — no breaking change
107
+
108
+ ```ts
109
+ export interface SymbolSearchModalProps {
110
+ locale: string
111
+ searchSymbols: (query?: string) => Promise<SymbolInfo[]>
112
+ onSymbolSelected: (symbol: SymbolInfo) => void
113
+ onClose: () => void
114
+ }
115
+ ```
116
+
117
+ No change to `Datafeed.searchSymbols` signature — filtering by `type` happens client-side on returned results.
118
+
119
+ ### 2C. SCSS rewrite — `src/widget/symbol-search-modal/index.scss`
120
+
121
+ - 840px width, max-height with internal scroll on results
122
+ - Tab bar: horizontal scroll, 28px buttons, active underline
123
+ - Result rows: 4-cell grid layout (ticker | description | exchange | actions)
124
+ - Active row: `--astroneum-accent-tint` background
125
+ - Footer: muted, 12px, centered
126
+
127
+ ### 2D. i18n — `src/i18n/en-US.json`
128
+
129
+ New keys (en-US first, 17 other locales fall back):
130
+ ```json
131
+ "symbol_search_all": "All",
132
+ "symbol_search_stocks": "Stocks",
133
+ "symbol_search_funds": "Funds",
134
+ "symbol_search_futures": "Futures",
135
+ "symbol_search_forex": "Forex",
136
+ "symbol_search_crypto": "Crypto",
137
+ "symbol_search_indices": "Indices",
138
+ "symbol_search_bonds": "Bonds",
139
+ "symbol_search_economy": "Economy",
140
+ "symbol_search_options": "Options",
141
+ "symbol_search_more": "More",
142
+ "symbol_search_footer": "Search using ISIN and CUSIP codes"
143
+ ```
144
+
145
+ ### 2E. No subpath export needed
146
+
147
+ `SymbolSearchModal` rides the root barrel (`src/index.ts:82`). No `src/entries/` or `package.json` change.
148
+
149
+ ### 2F. Demo consumer fix — `WatchlistPanel.tsx:219`
150
+
151
+ Pass `locale` prop instead of hardcoded `"en-US"`.
152
+
153
+ ---
154
+
155
+ ## Phase 3: Watchlist — section group headers
156
+
157
+ ### 3A. `WatchlistManager.ts` — add `group` field
158
+
159
+ ```ts
160
+ export interface WatchSymbol {
161
+ ticker: string
162
+ // ... existing fields ...
163
+ group?: string // section header label (e.g. "US STOCK")
164
+ }
165
+ ```
166
+
167
+ `group` persists (not stripped by `_storedSymbol` — it's user-organized, not injected like quotes). Add to the whitelist in `_storedSymbol` (L196-206).
168
+
169
+ ### 3B. Render section dividers — `WatchlistPanel.tsx`
170
+
171
+ When iterating sorted symbols, detect `group` changes and insert a non-interactive divider row with the group label text.
172
+
173
+ ### 3C. Divider row style — `panels.css`
174
+
175
+ ```css
176
+ .term-wl-group-header {
177
+ padding: 8px 12px;
178
+ font-size: 11px;
179
+ font-weight: 600;
180
+ color: var(--term-text-2);
181
+ text-transform: uppercase;
182
+ letter-spacing: 0.04em;
183
+ background: var(--term-panel-bg);
184
+ position: sticky;
185
+ top: 0;
186
+ z-index: 1;
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Phase 4: Docs Sync
193
+
194
+ | File | Section | Change |
195
+ |---|---|---|
196
+ | `docs/TODO-DESIGN.md` | 7.6 | Symbol Search: update spec (840px, tabs, 4-cell rows) |
197
+ | `docs/TODO-DESIGN.md` | 3b | Add section group header row |
198
+ | `docs/TODO-DESIGN.md` | 11.10 | Symbol search result-state parity: mark progress |
199
+ | `docs/design-astroneum.md` | Symbol Search Pill | Update width 640 to 840px, add tab + 4-cell row spec |
200
+ | `docs/design-astroneum.md` | Watchlist Panel | Add section group headers |
201
+ | `tv-mirror-reference/watchlist.md` | 2.4, 3, 8 | Fill `not captured` items from CDP |
202
+ | `tv-mirror-reference/structure.md` | 4.1 | Update Symbol Search DOM |
203
+ | `tv-mirror-reference/README.md` | Evidence table | Add new capture files |
204
+
205
+ ---
206
+
207
+ ## Phase 5: Verification
208
+
209
+ ```
210
+ pnpm verify (= pnpm lint && pnpm typecheck && pnpm build && pnpm test)
211
+ ```
212
+
213
+ Must pass before any deploy. No deploy in this plan — implementation only.
214
+
215
+ ---
216
+
217
+ ## Execution Order
218
+
219
+ 1. Phase 1A — CDP capture scripts (2 new files in `tv-mirror-reference/`)
220
+ 2. Phase 2A-2F — Symbol Search rewrite (library: tsx + scss + i18n; demo: locale fix)
221
+ 3. Phase 3A-3C — Watchlist section group headers (library: `group` field; demo: divider rows + CSS)
222
+ 4. Phase 4 — Docs sync
223
+ 5. Phase 5 — `pnpm verify`
224
+
225
+ ---
226
+
227
+ ## Files Touched
228
+
229
+ | File | Phase | Change |
230
+ |---|---|---|
231
+ | `tv-mirror-reference/cdp-symbol-search.js` | 1A | New — CDP capture script |
232
+ | `tv-mirror-reference/cdp-watchlist.js` | 1A | New — CDP capture script |
233
+ | `tv-mirror-reference/ref-symbol-search.html` | 1A | New — capture output |
234
+ | `tv-mirror-reference/ref-watchlist.html` | 1A | New — capture output |
235
+ | `tv-mirror-reference/structure.md` | 1B | Update 4.1 |
236
+ | `tv-mirror-reference/watchlist.md` | 1B | Update 2.4, 3, 8 |
237
+ | `tv-mirror-reference/README.md` | 1B | Update evidence table |
238
+ | `src/widget/symbol-search-modal/index.tsx` | 2A | Rewrite — tabs, 4-cell rows, debounce, keyboard nav |
239
+ | `src/widget/symbol-search-modal/index.scss` | 2C | Rewrite — 840px layout |
240
+ | `src/i18n/en-US.json` | 2D | 12 new keys |
241
+ | `demo/src/app/components/panels/WatchlistPanel.tsx` | 2F, 3B | Locale fix + section dividers |
242
+ | `demo/src/app/components/panels/panels.css` | 3C | Group header style |
243
+ | `src/chart/WatchlistManager.ts` | 3A | Add `group` field |
244
+ | `docs/TODO-DESIGN.md` | 4 | Update 7.6, 3b, 11.10 |
245
+ | `docs/design-astroneum.md` | 4 | Update Symbol Search + Watchlist sections |
246
+
247
+ ---
248
+
249
+ ## Risks
250
+
251
+ | Risk | Mitigation |
252
+ |---|---|
253
+ | `SymbolInfo.type` is `string` not union — tab filter may miss non-standard values | Client-side filter: if `type` undefined, show in "All" only; map common values |
254
+ | No debounce currently — Polygon API rate limits | 200ms debounce + cancel in-flight on new keystroke |
255
+ | `group` field on `WatchSymbol` needs migration for existing localStorage | `_load()` already validates; add `group` with fallback `undefined` |