feat(core): 指标定义编程式注册(registerIndicatorDefinition)+ 外部指标 ui 通道 - #273
EliteOtaku wants to merge 118 commits into
Conversation
Add './dist/data/provider/sources/*.js' subpath (types + import) to package exports so hosts can deep-import builtin market-data source registrations. Without it, hosts bootstrapping the provider registry outside the aggregation UI get an empty registry and no routable providers.
The bare web-component entry gets its side-effect source registrations tree-shaken away (package sideEffects:false), leaving hosts with an empty provider registry. New entry force-imports the five builtin market-data sources (baostock/finshare/gotdx/mock/tradingview) and re-exports the element so the side-effect chain survives bundling. It also declares static capabilities for host-wired sources: the shell's SourceRouter filters providers by source.capabilities, which the stock flow only populates via probe() (aggregation-UI-only path), so headless bootstrap was deadlocked with no routable provider even after registration. Static declarations unblock routing on the first bars request (idempotent: only fills undefined capabilities).
Add '4h' to KLinePeriod union, KLINE_PERIODS set, market data policy initial days (90), agent tool whitelist, semantic DataConfig period union + validator max range (180 days), and the level dropdown entry. Hosts mapping broker resolutions (e.g. H4=240) currently have no in-library period to land on.
applyToolSession resets the active tool via setDrawingToolId('cursor'),
which also clears the current selection. Emitting onDrawingCreated
before that reset meant a host selecting the new drawing in the
callback (TV-style floating style toolbar) had its selection wiped
immediately. Swap the order: reset the tool first, then notify.
Selected-drawing floating toolbar gains a template dropdown (filtered by the current drawing kind) plus a 'save as template' dialog: - KLineChart accepts an optional drawingTemplateStore prop; a localStorage-backed default store is used when not injected, so the mechanism stays host-agnostic (hosts can route to their own backend) - apply merges the template style into the selected drawings (may add keys updateBatch would reject via its field-intersection guard) - save captures the primary selected drawing's style under a name Depends on the selection-timing fix so the toolbar actually appears right after finishing a drawing.
Add packages/nexus-shell: a generic, business-decoupled TradingView-style host shell (React + Vite, private workspace package, never published): - top bar (period presets incl. 4h, theme toggle), left drawing toolbar, indicator panel, template panel skeleton, chart stage mounting the engine via the react adapter - theme tokens as CSS variables (light/dark), all colors centralized in tokens.css per upstream convention - UI strings centralized in labels.ts; template storage behind a DrawingTemplateStore port so hosts can inject their own backend Engine wiring (datafeed, tool selection, indicator registry, template store default impl) is intentionally left as documented next milestones: this scaffold ships chrome + ports only, no product coupling.
Add a fork section (via a docs fragment so generated READMEs stay in sync with pnpm docs:generate) describing the fork's purpose: a generic TV-style host shell plus engine enhancements offered back upstream in small batches. Add NOTICE with upstream attribution; LICENSE is retained unchanged from upstream.
- ambient module stub for the web component entry (same approach as packages/react) instead of following into .vue resolution - drop vite.config.ts from tsconfig include (repo convention excludes config files from type-check) - type DEFAULT_INDICATORS as ReadonlyArray to avoid literal-type trap
The exports wildcard for data provider sources has no single source file to map, so createCoreSourceAliases aborted every vitest/vite config that consumes it (ai-runtime tests failed at startup). Wildcard entries are runtime deep-import patterns for built output; source-alias contexts do not need them.
The vue package source (pulled in via the web component alias) imports ~icons/tabler/* virtual modules; without the Icons plugin the chart mount fails to resolve them and the dynamic import of the web component entry throws.
- ChartStage hosts core createChartController directly: the react WC adapter exposes no controller channel and KLineChart.vue embeds its own toolbar chrome, so the shell now owns all chrome itself (G-10) - left toolbar: grouped flyouts with last-used memory, favorites, magnet tri-state (shell-side OHLC snapping), stay-in-drawing-mode, measure and eraser pseudo-tools, zoom buttons - selection style flybar: color/width/linetype/fillOpacity with mixed-state handling, lock toggle, template dropdown, delete - drawing templates on nexus.* localStorage: save/apply/rename/delete plus auto-apply to newly created drawings (toggleable) - pointer bridge: Shift 45-degree angle lock, Shift-click multi-select normalization (engine only supports Ctrl), Ctrl drag-copy via restore-in-place strategy, Esc/Delete keyboard flow - deterministic seeded mock data source covering symbols and periods; dev server pinned to port 5273 - E2E acceptance probe scripts/probe-drawing.mjs (39 assertions, all green) and docs/action-checklist.md tracking TV-parity actions
…batch 2) - SymbolPicker: dropdown with keyword filter over the mock catalog, Enter-to-select and recents persisted on nexus.shell.recent-symbols - TopBar rebuilt on shell context: grouped period select (minutes / hours / day-week-month) re-feeds mock data on change; theme toggle - probe-topbar.mjs acceptance probe (12 assertions) including canvas legend rendering verification; drawing probe regression 39/39
Add magnetSnapper pure module (weak: 8px to high/low, strong: 15px to OHLC, X snaps to bar center) mirroring the verified nexus-shell ChartPointerBridge.applyMagnet semantics. resolveDrawingPointer gains an optional magnet config applied before screenToAnchor so cursor hit, marquee and label paths stay untouched. DrawingInteractionController owns the session-level magnet mode (off/weak/strong, Ctrl/Meta temporarily upgrades to strong including off) and passes it only along the drawing anchor and preview paths.
A locked drawing was still clickable, marquee-selectable and dragged along with the selection group. Exclude locked drawings from the hit candidates fed to HitTester, from marquee commit candidates, and from the connected drag group so a locked drawing can no longer be selected, boxed or moved by pointer interaction.
Shift+click in cursor mode now toggles drawing selection and preserves the selection when clicking blank space, matching Ctrl semantics. The shell previously normalized Shift into a synthetic Ctrl event; this makes the engine accept both modifiers natively.
Host UIs previously had to duplicate the anchor-count table to render step hints. Export getAnchorCountForTool plus the SINGLE/DOUBLE/TRIPLE anchor tool lists from the drawing module and re-export them through the controllers facade.
Hosts implementing eraser or object-tree hover needed the same filtered hit candidates as cursor clicks but had no access to HitTester. Extract the candidate filtering shared with findDrawingHit and expose hitTestAt(x, y) returning the drawing under container-local coordinates, honoring pane offset and the locked exclusion.
Channel kinds render an area primitive whose fill falls back to stroke when unset, so fill is a kind-inherent capability rather than an explicitly-stored style key. getBatchStyleKeys now includes 'fill' when every target is a channel kind, letting hosts batch-edit the fill color without changing the default style (unset fill keeps following stroke, so no visual regression). Mixed channel+line selections keep the intersection guard rejecting fill.
vue-tsc resolves @363045841yyt/klinechart-agent-runtime (and its /contracts/ui subpath) through package exports, which point at a dist that is never built in dev clones; the vite dev/build path already aliases the same specifier to agent-runtime source. Mirror that mapping in tsconfig.app.json paths so the whole features/agent type chain resolves (337 type errors drop to 56 pre-existing test-file debts outside the agent chain; use-agent-workspace implicit-any errors were downstream of the broken imports and disappear with them).
Document magnet tier semantics (radii, candidate order, Ctrl override, why X snaps to bar center), the resolveDrawingPointer opt-in contract, why magnet mode lives on the controller instead of StateKernel, and the locked-drawing interaction semantics plus the channel fill key decision.
…xports, hitTestAt, channel fill, vue type chain)
Shift 角度锁(宿主壳层实现)会先改写指针坐标,引擎磁吸若再吸附 会造成双重改写;此前壳侧以分支互斥规避,迁移到引擎 setMagnetMode 前必须先在 resolveMagnetOptions 恢复该互斥语义。Shift 优先级高于 Ctrl/Meta 的 strong 升级,单锚点工具按 Shift 亦不吸附。 补 interaction.magnet.test.ts 用例(Shift 不吸附、Shift+Ctrl 抑制 升级),设计文档修饰键条目同步更新。
引擎自 fb5392d 起原生提供 OHLC 磁吸(档位/半径/X 吸附/Ctrl 升级 与壳侧基准逐点一致),壳侧删除重复实现:applyMagnet、其按下/移动 两处调用分支与 MAGNET_RADIUS_* 常量。偏好读取保留为 BridgeStateAccessors.getMagnet,新增桥方法 syncMagnet 由壳在桥挂载 与偏好变化时调用 dic.setMagnetMode 同步档位;持久化键不变。
…i-selects natively 引擎光标模式自 fb5392d 起原生支持 Shift 多选(ctrlKey || shiftKey 同语义:toggle 且空白不清空),壳侧删除 cursor 模式的 shiftKey→ctrlKey 事件归一化分支;clonePointerEvent 的 shiftKey 覆写字段随之失去调用方,一并移除。
橡皮擦从"点选→读选中→删"三步组合改为 hitTestAt 公开命中查询 (与点选同口径:locked 排除、pane/工作区过滤)直接删除;不再借用 光标点选语义,消除误开拖拽会话与空白点击清空选中的副作用。
磁吸/Shift 多选/橡皮擦已切换引擎原生实现(G-01/G-06/G-03), locked 强制、锚点数表导出、通道 fill 键、vue 类型链均随 fb5392d 关闭;缺口表同步标注,仅 G-02(测量)/G-05(FVG)/G-10(WC 通道) 保持登记。
…tMagnetMode wiring, native shift-select, hitTestAt eraser)
TV 官方 Magnet Mode 文档定义 Ctrl/Command 为磁吸临时取反:off 时 临时开启、开启时临时关闭。此前引擎沿壳侧旧基准"一律强制 strong", 后半段与官方相反。临时开启强度取 strong(TV 对磁吸开的描述即吸附 OHLC 四值)。测试改写为取反三态用例,设计文档修饰键条目同步。
可见区间不含任何真实 bar 时(range.start >= data.length),Pane.updateRange 早退保留最近一次有效 priceRange 与基准价,消除拖入未来区时价格轴跳变到 {100,0} 兜底;空数据冷启动仍走原兜底。
…ading calendar collectFutureTimeBoundaries 纯函数按外推时间 key 变化检测未来槽位边界(经 RenderContext.getTimestampAtLogicalIndex 复用 Task 5 外推 SSOT,渲染器不二次推导 session/周期);时间轴未来刻度以 text.tertiary 降级渲染,纵向网格同帧合并。
…olation MT5 连接器 symbol-catalog 规格约定 sessionId=MT5(对应前端注册的会话),但内建注册表只有 CN/HK/KR/US,resolveSymbolMarketSession 对 MT5 品种 throw 使 futureSession 恒 null,未来区时间外推/十字签/网格在 MT5 链路整体失效。注册 MT5 -> FOREX_MARKET_SESSION(24/5 主场景),crypto 等全周品种的周末预测由索引制轴自愈兜底。
# Conflicts: # packages/core/src/engine/controller/__tests__/interaction.future.test.ts # packages/core/src/engine/data/__tests__/contentGeometry.parity.test.ts # packages/core/src/engine/data/chartDataManager.ts # packages/core/src/engine/market/cryptoMarketSession.ts # packages/core/src/engine/market/forexMarketSession.ts # packages/core/src/engine/market/marketSessionRegistry.ts # packages/core/src/engine/render/chartRenderer.ts # packages/core/src/engine/renderers/__tests__/gridLines.mode.test.ts # packages/core/src/engine/renderers/__tests__/helpers/futureAxisTestKit.ts # packages/core/src/engine/renderers/__tests__/timeAxis.crosshairFuture.test.ts # packages/core/src/engine/renderers/__tests__/timeAxis.future.test.ts # packages/core/src/engine/renderers/timeAxis.ts # packages/core/src/engine/utils/__tests__/chartZoomController.future.test.ts # packages/core/src/engine/viewport/__tests__/visibleRange.clamp.test.ts # packages/core/src/foundation/plugin/types.ts # packages/core/src/foundation/utils/sessionTimeLabels.ts # packages/vue/src/components/KLineChart.vue
…/getRenderer) 宿主业务 overlay(指标扩展/业务标记层)此前只能经 dev-only 的 window.__chart 自挂实例触达 useRenderer;vue/agent 层 ChartController 无注册入口。 - ChartController 新增 useRenderer/removeRenderer/getRenderer,透传核心 Chart 实例(幂等语义一致:按 plugin.name 保留首注册实例) - 根导出补充 RendererPlugin/RendererPluginWithHost 类型 - 新增 createChartController.renderers.test.ts 3 用例(注册/幂等/移除)
预览工作台启动(controller-ready)时加载宿主声明的渲染器插件模块: - 声明通道:localStorage['kcq_external_renderers'](JSON URL 数组)或 ?externalRenderers=url1,url2 - 模块契约:default/renderers/renderer 导出 RendererPlugin 或其数组 - 相对路径归一为绝对 URL 后动态 import(dev 管线只放行外部协议的动态 import,相对路径会被重写 ?import 进模块图致 public 资产 404——实测坑) - 单模块失败仅 console 告警,插件间与工作台互相隔离 - demo:preview/public/external-demo-renderer.js(主图中部虚线参考线)
useRenderer/removeRenderer/getRenderer 之外补 scheduleDraw(level?):外部渲染器 插件经轮询/WS 取到新数据后需要显式触发重绘(引擎仅在交互/数据变更时自绘)。 委托核心 Chart.scheduleDraw,缺省 UpdateLevel.All;测试补 1 用例(注册前后 调用均不抛)。
插件可经 host.controller 读取品种(controller.symbols 信号)与触发重绘 (controller.scheduleDraw),与 core API 面配套。
…20260929 # Conflicts: # packages/vue/src/components/DrawingStyleToolbar.vue # packages/vue/src/components/KLineChart.vue # packages/vue/src/composables/chart/useDrawingTemplates.ts # packages/vue/src/web-component-with-sources.ts # scripts/core-source-aliases.mjs
数据根数随周期变化,切换后保持 scrollLeft 会让视口漂进未来区 (上游 363045841#261 右缘空白放开后 scroll 不再钳制到数据区),极端时整个 可视区都是未来网格、图元创建全部落空。rAF 双帧等待数据注入后的 视口布局,再 scrollToRight 对齐最新——TV 同款切周期看最新行为。
preview dev server 增加三个本地代理: - /api/option_levels、/api/options_gamma → 127.0.0.1:8888(cloudtrade 后端期权插件试点数据源,同机 FastAPI) - /api/v1 → 127.0.0.1:8090(KCQ market-data 聚合服务,MT5 XAUUSD 等 品种 K 线),preview 直连连接器取真实行情
…gger scope note)
…ain docs) - AGENTS.md 追加 Agent skills 段(issue tracker / triage labels / domain docs) - docs/agents/issue-tracker.md:GitHub Issues 为唯一 triage 通道(gh CLI 约定, PRs 不作为请求面,wayfinder 地图/子票/阻塞依赖操作) - docs/agents/triage-labels.md:五角色同名标签词表 - docs/agents/domain.md:单上下文消费规则(根 CONTEXT.md + docs/adr/, 惰性创建,术语表词汇约束,ADR 冲突显式标记)
…egisterToolHost) Tool 装饰器协议允许任意宿主把领域方法暴露为 Agent 工具,但 toolHosts 硬编码(仅内置对比品种宿主),外部宿主的 @tool 方法无法被归属解析。 - ChartController 新增 registerToolHost/unregisterToolHost(幂等去重); ChartAgentControllerDependencies 加可选 extraToolHosts() - toolHosts getter 合并内置+动态宿主;browser tool registry 的 owns() 解析消费方零改动 - 根导出补通用工具协议:Tool 装饰器 + ChartToolConfig/ ChartToolExecutionContext/ChartToolSafety/RegisteredChartTool 类型 - 新增 toolHosts 测试 3 用例;core 251 文件 2676 用例全绿
@tool 装饰器依赖跨 bundle 不可达(外部插件与 core 不共享模块实例), bridge 工具目录又只在构造时静态快照——外部宿主的 Agent 工具无法进入目录。 - core:chartToolRegistry 增 registerChartTool/unregisterChartTool(编程式 写入唯一真源,冲突语义与装饰器一致);根导出同步 - vue:BrowserAgentBridgeOptions 增 extraChartTools?: () => RegisteredChartTool[] (惰性求值一次),BrowserToolRegistry.registerTools 合并内置+外部源 - 与 363045841#272(registerToolHost 方法归属解析)组成外部 Agent 工具完整链: 宿主持有 @tool 类实例 → registerToolHost 注册归属 → bridge extraChartTools 声明工具目录
插件模块可带 chartTools: RegisteredChartTool[] 命名导出;loader 收集进 externalChartTools 引用(bridge extraChartTools getter 先构造后填充)。
与 363045841#272(工具宿主注册)同族的注册表编程入口:指标目录的注册只有类装饰器 (依赖模块加载),外部宿主 bundle 与 Core 不共享模块实例时不可达。 - indicatorDefinitionRegistry:抽共享组装 defineIndicator,新增 registerIndicatorDefinition(config, rendererFactory)(同名重复注册抛错, 与装饰器语义一致);name 放宽为 string(契约 union 仅约束内置编译期拼写) - IndicatorDefinitionConfig/IndicatorMetadata 增可选 ui 字段(name/description/ params):内置指标 UI 元数据来自静态 uiMeta 表,外部指标随定义携带—— indicatorCatalog rebuild 映射 uiMeta ?? def.ui - 测试 +4(编程式注册进目录/重复注册拒绝/params 通道/装饰器与编程式同表), 全量 252 文件 2680 用例全绿
- toolHosts 测试:hostInputSchema 改 Type.Object 构造(Static<TSchema> 宽泛基类 取不出具体类型致 input: unknown);Type import 修正 - 指标注册测试:删装饰器样例(@Indicator 的 name 受契约 union 约束是上游有意 的类型语义,外部指标走编程式,测试不以 as never 硬绕);kind/category 去 as never 改结构推断字面量;const config 补 as const 防字面量拓宽
… indicator-registration,对应 PR 363045841#266/363045841#274/363045841#273)
真实用例验证 + 两个缺口反馈(一目均衡表套件指标化 spike)我们在闭源侧做了一目均衡表套件的外部渲染层插件(多周期切片/延迟线/ghost 带),但外部渲染层存在宿主竞态窗口内的错帧问题(周期切换/数据更新时 RenderContext 新旧混杂,视觉表现为抖动;原生指标管线无此问题),因此评估走 #273 的编程式注册路线做指标化迁移。本周在 PR 分支 worktree 上做了端到端 spike,验证通过:
缺口 ①:worker 路径无法使用外部内联 compute(阻塞闭源侧接入)外部定义的 建议二选一:
缺口 ②:visibleState.compose 的 entry 取用路径缺文档compute 结果在 compose 收到的 交付形态确认请求外部插件 bundle 需拿到 spike 细节:XAUUSD 1d/1h,worker compute(包内临时 calculator 打通渲染链路验证)+ 自定义 rendererFactory 画三线/云/延迟虚线;错帧对照样本与取证见我们侧记录(外部渲染层同场景存在贯通色墙伪影,原生管线同场景干净)。 |
动机
指标目录的注册只有
@Indicator类装饰器(依赖模块加载时机)。外部宿主/插件 bundle 与 Core 不共享模块实例时,类装饰器不可达——外部指标定义(自定义指标库等)无法进入指标目录,指标选择器/实例管理/参数系统等既有基础设施对外部指标全部不可用。方案(与 #272 的
registerChartTool同族)1. 编程式注册入口
defineIndicator共享组装(装饰器与编程式同一条写入路径,语义一致);2. 外部指标 UI 元数据通道
内置指标的 params/description 来自静态
uiMeta表;外部定义无法进入该表。IndicatorDefinitionConfig/IndicatorMetadata增加可选ui?: { name?, description?, params? },catalog rebuild 映射改为uiMeta[key] ?? def.ui——内置路径零改动,外部指标获得同等的参数面板能力。验证
registerIndicatorDefinition.test.ts4 用例:编程式注册进目录(catalog id=displayName)/ 同名重复注册拒绝 / ui.params 通道 / 装饰器与编程式同表;兼容性
纯增量:未注册外部定义时行为与之前完全一致;
ui为可选字段,内置路径uiMeta[key]优先。