ek-ui 0.2.24

ek 生态常驻主界面(egui/eframe):插件即界面——观测页 + 日志页,车头与 UI 同插件
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# DESIGN —— ek-ui(S1b 常驻观测界面)

Frozen ref: docs/ek-ui-station-plan.md(DV 表 + S1b 段验收)。本档只记**结构决定**与
执行期钉。形式沿用 ek-dm/ek-station 的 BD-U1.. 编号加序。

## 结构

- `state.rs`:`UiShared`(总线面写 / 窗口面读)+ `LogRing`(cap 512,挤旧 +
  spilled 单调计数)+ `BusSnap`(svctl.list 行 + 逐插件服务 id 目录)。
  锁粒度:每次操作 lock/clone,不跨帧持锁;`snapshot()` 返回 `Arc<BusSnap>` 零拷贝。
- `plugin.rs`:`UiPlugin`("ui"):routes = `eLog`/`ePlugUp`/`ePlugDown`/`eTimer` +
  `eTick.`(行情带唯一源)+ `eContract.`/`eContractFinish`(查询合约窗数据流,
  ek-ctp 登录后自动查询,UI 只收不催)。
  - eLog:payload `LogData`,`is_warning_or_above` 打 WARN 标签(+UCH 转储环形);
  - ePlugUp/Down:payload `PlugNotice` → 单行 host 日志 + rev;
  - eTick./eContract.:payload 直落 `tick_store`/`contract_store`(键 EX.symbol);
  - eTimer:`ask(svctl.list, 2s)` + 逐插件 `ctx.services_of` → `set_snapshot`;
    ask 失败进日志面,不堆 panic。
- `render.rs`:`render_body(ctx, &UiShared)` 唯一渲染入口(L2;v5 面板化,
  BD-UI-27/29):TopBottomPanel(菜单)→ TopBottomPanel(行情带,resizable)→
  SidePanel(下单面板 + 盘口,resizable)→ CentralPanel(2×2 监控格,响应式)→
  弹窗面(K线/运维/CTP/查询合约,egui::Window,面板之后)。
- `runtime.rs`:
  - `ui_kinds(&Arc<UiShared>)` = station 缺省表 + ui 工厂;工厂闭包绑定**同一个**
    shared → ui 插件被 svctl 摘除/再注,窗口观测态不丢(状态在 shared 不在插件体);
  - `spawn_host_watcher`:`sleep 120ms` 轮询 stop 位 → `engine.close_all(3s)`;
    close 顺序(逆 start 序)仍由宿主引擎决定,观察线程只送信号;
  - `run_station_ui`:boot(不 start 由 boot 内部? 否—— boot 只装配)→ `engine.start()`
    → 观察线程 → eframe `run_native`;关窗 `on_close_event → request_stop` →
    join 观察线程 → 返回。启动失败(start Err)以 `Err(String)` 上抛(供 S1c
    launcher 的失败信号,不在窗口内吞错)。

## 执行期钉

- **BD-UI-9(UI 即插件 / 双线程形貌)**:总线 worker 写、eframe 主线程读,以
  `Arc<UiShared>` 相连;不加自制通道、不退回向量共享消息队列。
- **BD-UI-10(L2 单渲染入口)**:`render_body(ctx, &UiShared)` 被真实 App 与
  kittest 同调;面板组合在 render_body 内(BD-UI-29),测试走
  `Harness::builder().build(|ctx| …)`(ctx 级,不再包 CentralPanel)。
- **BD-UI-7(四件套版本锁)**:eframe = egui = egui_kittest = `0.33.3`;
  egui-charts 0.2.0 进入点定为 S4(K 线页),进入时与四件套重验一版。
- **BD-UI-11(关窗协议)**:`App::on_close_event` 只置 stop 位;close_all 由
  观察线程完成 —— 窗口永不直接持有 `MainEngine::close_all` 调用链。
- **BD-UI-12(kittest 用法纪律,源自官方 README)**:优先节点/文本断言,少用
  图像快照;快照只对静态首帧;更新基线仅经 `UPDATE_SNAPSHOTS='true'`;冲突
  先 diff 图后压缩容差。
- **BD-UI-13(纯观测零服务)**:ui 插件 `services()` 返回空;管制面唯一在 svctl
  —— 界面无特权路径。动态 add 的 ask 窗口期(`on_init` 后)与 headless 同一契约。
- **BD-UI-14(启动器协议,与 ek-station bin `--from/--ready-file` 同口径)**:
  清单 = 行式文本(`#` 注释;空行跳过;行内空白拒);就绪标记 = `ready\n`;
  spawn 分离式(stdin/stdout 静默 + Windows `CREATE_NO_WINDOW` + stderr 管道,
  **stderr 生存期 ≤ 64KB** —— 子进程不朝 stderr 泼流,违约即协议故障);
  等待 = 50ms 轮询标记 + 存活检查,30s 上限(超时 kill);失败分类 `Exited{code,
  stderr_tail}` / `NoReadySignal{timeout}`;成功路径 **drop Handle = 分离脱管**。
- **BD-UI-15(清单解析地面)**:pick.rs 与 ek-station main.rs 的 `--from` 解析
  S1c 各自落地(等语义);S3 toml-lite 重构时统一抽到共享模块(单点 PR),避免
  现在跨 crate 发版拖 III 制订的节奏。
- **BD-UI-16(pick 窗两线程形貌)**:UI 线程只画与翻位;spawn+wait 在工作线程
  (30s 不冻 UI);结果回槽 `RunShared`;App 见果即关窗,
  收场归 `run_pick_launcher`(join + 读槽)。ui 插件自动补入 pick 后配置,
  其余勾选即装配面(与 boot 同表)。
- **BD-UI-17(隔离红标的闭环面)**:`ePlugQDown.<名>` 是定向广播(未知名无法
  预订阅 —— 严格 exact-type 路由),UI 侧红标走快照行状态扫描
  (`is_quarantined`,1s 节拍内落实);隔离行仍可重注入(svctl.start 对隔离行
  被 Rejected(重名)拒;既定语义链 = 先 remove 再 start)。红标卡件 trace:
  `ek-core::lifecycle::EVENT_PLUG_DOWN_PREFIX("ePlugQDown.")` 常量名与后缀型
  一致,但仅作 reference,不预订阅。
- **BD-UI-18(操作指令的唯一通道)**:窗口面动作只 `push_cmd(UiCmd)`,
  NOT 直 ask —— `PluginContext` 只在 ui 插件 worker 可用;eTimer 拍先辞行
  指令再拉快照;移除走 `pending_confirm` 单槽二步(Armed→Fired;Switched =
  目标迁移),取消钮可擦;重注入为单步(加性动作);拒绝类回执
  (`Rejected/NoSuchName`)进日志面板,不在 UI 抛错。
- **BD-UI-19(配置面 → 2026-10-17 用户定盘撤销,零配置口径)**:原 S3 落地过
  toml-lite + `setting/global.toml` 融入读法 + 状态条 + 脱敏(cfg.rs/cfg 测试
  9 例/s7 kittest 1 例)。**现行口径:ek-ui 零配置、宿主零配置** —— 不读任何
  配置文件,界面本身无设置;插件结构只由 `--from` 清单或 demo 缺省决定。
  需要配置的插件(唯一 = ek-ctp 虚拟账号),配置面长在插件自身,激活时在插件
  内完成(见「配置面撤销记录」);宿主/界面不代读不转发。cfg.rs 整块删除;
  脱敏纪律(redact)随配置面一同撤销(无配置输入即无秘密暴露面;插件自带
  配置面若需展示账号,脱敏规则届时由该插件自持)。
- **BD-UI-20(S4 行情面口径)**:主题面(数据)与身体面(交互)分离——
  `MarketStore`持直播线上屏(eBar./eBarUpdate.),(symbol,interval) 格缓存
  4096 丢头保尾,帧/更新/回灌三计数 = 「刷新不丢帧统计可见」的三处钉面;
  K线 chart 实例**每帧重建**(v1 不保 pan/缩放;首帧快照入库即 review 约定
  —— BD-UI-8);图表层引用在本 crate 集中(`render::render_kline_cell`
  一点换器,egui_charts→egui_plot 退守路线只动此处,BD-UI-4)。
- **BD-UI-21(S4 直播面工厂纪律)**:UI 宿主的 dm 工厂在 `ui_kinds` 内换建
  (real_time=true),ek-station headless 的 rt-off 工厂**不动** ——
  宿主差异收在各自工厂表,不回写 ek-station、不新增 svctl 控制面;
  dm 工厂 real_time 门为装配参数(缺省 true;UI 观测面要实时门 —— 非配置文件
  输入,零配置口径,BD-UI-19 现行版)。
- **BD-UI-15 兑现记录(S3)**:清单地面统一抽世到 `ek-station 0.1.1::pickfile`
  (已发布;`ek-ui::pick` 变薄转发,launcher/bin/测试共一处,双实现消灭)。
- **BD-UI-22(S5 纸面网关契约纪律)**:sim 前三口直接复用 ek-ctp 的
  Req/Reply 类型(SubscribeReq/SendOrderReq/CancelOrderReq 及回包)——
  同构 = 同一类型面,接线换 `sim.` 即插真网关;第四口 `query_rate` 为 sim
  自定(ek-ctp 0.2.0 无此口,CTP 流控语义镜像)。撮合规则钉:限价成交于
  委托价、市价成交于 close/last、bar.volume = 本拍成交预算、挂单 FIFO ——
  手数排队由此可断言。撮合器 `SimMatcher` 纯逻辑零总线(金样本直断),
  插件壳只做路由/锁/上总线(锁内撮合锁外 put,与 DmPlugin 同形)。
  sim 不引 ek-oms:成交经 eOrder./eTrade./ePosition. 总线入账由 OmsPlugin
  被动完成(插件不直调铁律不破)。
- **BD-UI-23(S5 监控页读数纪律)**:监控页读数 = ui 插件 eTimer 拍 ask
  `oms.get_all_*` 全量查询帧(与 headless 同源契约;oms 不在 → 空帧不抛错);
  下单/撤单/流控三口走 UiCmd → ask `sim.*`(窗口面零直调)。撤单钮只挂
  ACTIVE_STATUSES 行(与 ek-data 同口径)。kittest 点击纪律:**`click_accesskit`
  取代坐标 `click`** —— 坐标点击对折叠线下(clip 外)节点无效,accesskit
  动作可达不可见控件;`get_all_by_label` 精确匹配、`query_by_label_contains`
  子串匹配且多命中即炸,撞名列按语义选面。
- **BD-UI-24(S6 CTP 手工配置面,2026-10-17 集成测试战役)**:「插件需要配置 →
  配置面在插件自身」的正向界面落地 —— CTP 段 9 输入位全部**人敲**(界面与宿主
  不读文件;密码/AuthCode `password(true)` 掩码不回显),`连接 CTP` →
  `UiCmd::CtpConfigure(CtpForm)` → ask `ctp.configure`(ek-ctp 0.3.0
  D-CONFIGURE:校验/守卫复位/发起归插件,md 账号回落也在插件侧裁决 —— 表单
  `to_configure_req` 不预处理);`订阅行情` → ask `ctp.subscribe`(订阅位
  逗号/空白解析,空位红字不入列)。`UiCmd` 携 `CtpForm`(全 String 可 PartialEq;
  ek-ctp `ConfigureReq` 无 PartialEq,抬线在 ui 插件出队点一次转换,装版不动)。
  资金小节 = `oms.get_all_accounts` 帧(可用 = balance−frozen 由 AccountData
  自算)。native 经 feature `ctp-native` 透传(默认关;实连战役开,工厂
  `with_native` 在 ek-station 侧)。装配序纪律:要观察插件 on_start 的日志,
  ui 须在清单里先于该插件(观察序 = 装配序,c0 钉)。
- **BD-UI-25(票据网关选择子,2026-10-17 战役)**:票据新增「网关」位(空 =
  `sim` 纸面;填 `ctp` = ask `ctp.send_order`/`ctp.cancel_order`);`UiCmd`
  随单携 `gateway`;行级撤单钮跟随委托来路 `gateway_name`(空兜底 sim)。
- **BD-UI-26(CJK 字体回退,2026-10-18 乱码根因)**:epaint 0.33 缺省字体组
  (Hack/Ubuntu-Light/NotoEmoji/emoji-icon)**无 CJK 字形** → 中文豆腐块。
  `fonts::apply_cjk_fonts` 启动时从系统字体候选表装载一枚 CJK 字体追加
  Proportional/Monospace 两族末位(**只收纯 .ttf/.otf** —— epaint 走 ab_glyph,
  不吃 .ttc 集合且解析失败即 panic;VF 按缺省实例解析,排位靠后);两处
  run_native 取帧闭包接线(runtime + pick 窗)。验收三钉:
  f0 字形 API(22 常用汉字 has_glyph)、s16 汉字密集页快照 baseline、实窗
  stderr 证据行 `[ek-ui] CJK 回退字体已装载: <path>`。界面文案保持中文单语
  (用户定盘:要的是不乱码,不是双语切换)。
- **BD-UI-27(v4 带框架 + 订阅两入口,2026-10-18 布局重做定稿)**:
  ① **带框架(Python 主界面等价)**:菜单栏(运维|网关|查询合约|帮助)→ 行情带
  (全宽,tick 唯一源,交易所二级 tab,双击行弹 K线窗)→ 交易带(左下单面板
  9 字段 + 委托/全撤 + 右 2×2 监控格「委托|未成交」「成交」「持仓」「资金|日志」);
  回灌/票据界面面退役(总线面 UiCmd::Reload 保留);默认窗 1280×860。
  ② **订阅两入口(用户裁定「订阅不在网关窗定死」)**:下单面板代码框回车 =
  CtpSubscribe(bare symbol,Python returnPressed 等价);菜单「查询合约」弹窗
  (Python ContractManager 等价:查询框留空 = 全部 + 勾选 + 订阅选中)。CTP 网关窗
  无订阅字段。UI 对合约流只收不催(无催查询总线口)。
  ③ **egui 0.33 帧内 sizing 退让钉(实窗截图审出)**:帧内 Grid 的 sizing 预估遇
  ComboBox 高行会退让,网格后挂的面板内容会叠回网格 → 下单表单 = 网格(按钮/
  提示语都是网格行,网格后不挂内容),盘口独立帧,交易带整体包 ScrollArea 保小窗
  可达(D7 口径)。④ **kittest 钉**:建构面统一 `harness_for()` 1280×860(实窗
  同源);点击帧断言用 `harness.step()` 单帧(`run()` 空闲帧会撤走单帧红字);
  撞名 label(委托钮/tab/表题)用 `get_all_by_label` 按渲染序取。
- **BD-UI-28(第五轮审计,2026-10-18 复审修正)**:
  ① **回车订阅真缺陷(实码修复)**:egui 单行 TextEdit 遇 Enter 即
  `surrender_focus`,`has_focus()` 在检查时已 false —— 原 `has_focus +
  key_pressed` 口径下回车订阅**实窗从不触发**;改规范口径 `lost_focus() +
  key_pressed(Enter)`(s20 钉实测)。回车输入带 `EX.` 前缀则剥成裸码并回写
  表单(与查询合约窗同口径,ctp.subscribe 收裸码)。
  ② **悬空面清除**:`selected_symbol` 四处写零读(注释声称「联动下单面板」
  未兑现)→ 字段/访问器删除;行情带行**单击 = 带入下单面板**(方案「行单击
  = 选中合约」落地,s8 钉);委托行/持仓行单击无动作(Python 同口径)。
  ③ **委托行双击 = 撤单**(Python OrderMonitor `itemDoubleClicked→
  cancel_order` 等价,活动行限定,s11 钉;原实现双击弹 K线为偏离,已撤 ——
  K线入口唯一在行情带双击)。④ **表列补全(Python 蓝图逐值抄录)**:委托表
  13→17 列(+名称/交易所/类型/来源=reference)、成交表 10→12(+名称/交易所)、
  持仓表 11→16(+名称/交易所/在途/市值=market_value/成本=price);数据面
  OrderData/TradeData/PositionData 字段全在场,零新增依赖。⑤ **tick_of
  交易所感知**:交易所非空 = EX.symbol 精确匹配(跨所同码不串),空 = 仅按
  代码。⑥ **kittest 双击口径**:`step()` 每事件一帧且缺省 step_dt=0.25s →
  两击释放间隔 0.5s > egui `max_double_click_delay` 0.3s → 双击钉用
  `with_step_dt(0.1)` 建构(s11);同帧四连击不可用(每事件一帧)。Node 借用
  harness 树,取 rect/id 后再 step(借用检查);`Role::TextInput` 非 TextBox。
  ⑦ **Frame 布局泄漏钉(s21 满表快照显形)**:`Frame::show` 继承父级
  `ui.horizontal` 的横向布局 → 2×2 格内 tab 行/表题/宽网格横流挤同一行
  (空表期隐形,满表现形)→ `group_frame` 内容强制 `ui.vertical`(行情带同款
  口径:短行成行,宽网格末位溢出走横向滚动);下单面板帧不动(其内容 = 网格 +
  罕见 err label,横流反而护住 BD-UI-27 叠行钉)。委托表表题撤除(tab
  「委托|未成交」即 Python 表身份,三重撞名降为两面,s12 口径同步)。
  满表视觉钉 s21(委托 17/成交 12/持仓 16 列入 baseline)。
- **BD-UI-29(面板化重构,布局 v5;实窗演示暴露 + 官方文档核对)**:实窗
  1280 演示暴露三症:① 交易带定宽 250+140+480+480+间距 ≈ 1438 > 1280 →
  右列裁切且带级 ScrollArea 只开纵向 → 裁掉内容不可达(违 BD-UI-27 可达
  口径);② 2×2 格定高 250 实际只渲染 ~85px → 底部死区 ~300px;③ 菜单/两带
  全挤单 CentralPanel 手工嵌套,无 Python splitter 语义。
  - **机制实锤(dbg 探针)**:`[row] avail=[9984, 18]` —— egui horizontal 行
    成员的可用高度初始只有一行高(interact_size),**高度分配不沿横向向下
    传播**,行按内容收缩 → 格内 `ScrollArea.max_height` 被钳到最小滚动尺寸,
    定高失效(②的根因;①为定宽算术,1438>1280 可复现)。
  - **官方口径核对**(egui 0.33.3 源码模块文档 = docs.rs 同源;crates.io 包
    不含 examples/,egui.rs 书为 JS 渲染页):panel.rs「面板是放控件的正规
    区域;顶层面板不得互相嵌套;CentralPanel 必须最后加;Window 挂面板之后」;
    `TopBottomPanel::resizable/default_height`、`SidePanel::resizable/
    default_width` = QSplitter 官方等价;resizable 前提 = 内容吃满可用空间
    (ScrollArea auto_shrink(false) 满足)。
  - **落地**:render_body 改收 `&egui::Context` 自开四面板(菜单 → 行情带
    240 可拖 → 下单侧栏 250 可拖[下单面板 + 盘口纵排,Python TradingWidget
    内序] → CentralPanel 最后 → 弹窗);2×2 格响应式 `allocate_exact_size`
    预留 (可用宽-间距)/2 × (可用高-间距)/2 矩形 + `new_child` 在预留矩形内
    开 Frame+ScrollArea(高度才吃满;`add_sized` 收 impl Widget 不适用闭包
    内容,已试撤);`group_frame` 保留 `ui.vertical` 泄漏钉(BD-UI-28⑦ 延续)。
  - **BD-UI-28⑦「下单面板帧不动」被本钉取代**:面板化后下单侧栏内容天然
    纵向,err label 后置改由 ScrollArea 吸收 sizing 退让(BD-UI-27 口径不变,
    护垫从「横流侥幸」换成「官方吸收面」)。
  - **钉**:s3 加结构钉(右列存在贴边不裁切/底行沉底无死区/侧栏在左/两列
    等宽随窗分配,先于字体让位执行);快照 s3/s10/s16/s21 重基线;行为钉
    s0–s20 全数不动全绿;kittest 建构面 `build_ui` → `build`(ctx 级)。

## 与 ek-station 的边界

ek-ui 依赖 `ek-station = "0.1.7"` / `ek-ctp = "0.5.0"`(装版;禁 path —— 同纪律)。ek-station
本 bin 保持 headless 不引 ek-ui;UI 总装配在 ek-ui(`ui_kinds` 复用 `default_kinds`
+ ui 工厂),launcher(S1c)也在 ek-ui 侧。

## 审计修正记录(2026-10-17 全面复审)

- **BD-UI-22 补钉(撮合两守卫)**:① **tick/bar 双重计价防护** —— tick 成交
  挂账 `tick_filled`,bar 收口预算 = bar.volume − 本段 tick 已成(同一笔市场
  量只吃一次;u5 钉);② **bar 单调守卫** —— 同 symbol `bar.datetime` 非严格
  递增的帧(重复/重放)直接忽略,不重复撮合(u6 钉);③ ask 臂锁面开块
  (match 暂存守卫会跨臂存活 —— 锁内造单锁外上总线,与 on_mail 同形)。
- **ek-station 0.1.2(pickfile BOM 剥离)**:Notepad 系 UTF-8 BOM 使首行注释
  识别失效 → 整表 BadLine 拒启(活跑实测撞到);read 剥 BOM,p3 钉。
- 复审确认未动面:ek-station headless 工厂表(rt-off)未波及;已发布六仓仅
  rustfmt 1.8.0 纯格式对齐(无语义变);setting/global.toml 只读;
  ek-ctp/ek-oms 契约类型只引用未改。

## 配置面撤销记录(2026-10-17 用户定盘)

- 用户口径:**界面零配置 + 宿主零配置** —— ek-ui 不读任何配置文件;station
  侧也不预读配置转发。插件结构只由 `--from` 清单或 demo 缺省决定。
- **配置归插件自身(定盘)**:激活列表(pick 窗勾选 / svctl start 重注入)时,
  哪个插件需要配置,配置面就在该插件内部完成(插件自带配置面/配置服务);
  宿主与界面都不代读、不转发、不集中。暂时不需要任何多配置机制。
- 全生态唯一需要配置的插件 = ek-ctp(虚拟账号)。其配置面归 ek-ctp 插件自身
  (S6 接线时按此定盘落地:激活 ctp 时配置在插件内完成,不落共享配置树)。
- 落地:cfg.rs(toml-lite/UiConfig/locate_setting/redact)+ CfgStatus 状态条 +
  `--config` 形态 + 发现链整块删除;cfg 测试 9 例 + s7 kittest 1 例随撤;
  测试 51 → 41。**setting/ 目录已整树清理(2026-10-17 定盘)**:global.toml +
  backtest/common/live/test 分层样例(含凭据档)全部删除——插件配置自持,
  工作区不留共享配置文件;Python 侧 c:\dev\enkeep_trade_setting 原档不在
  清理范围(只读纪律,原样未动)。

## BD-UI-30(0.1.1 薄下单者,OMS 中枢链,2026-10-19)

- **裁定**:下单/撤单直发 `{gw}.send_order`/`{gw}.cancel_order` 的旁路口
  **删除**——UI 是薄下单者,管制面收敛到 oms 两服务:
  `UiCmd::SendOrder{req, gateway, mode}` → `oms.submit_order`(五段管线:
  入口校验/拆单槽位/风控槽位/发送段/记账段;parent 聚合 SENT|PARTIAL|REJECTED
  逐腿显形进日志);`UiCmd::CancelOrder{orderid, gateway}` → `oms.cancel_order`
  (守卫 4000 不可绕过;symbol/exchange 不再随令携带,oms 订单缓存解;
  全撤 = 界面侧逐个扇出到同一守卫口)。BD-UI-23/25 的「ask sim.*/ctp.*」
  口径自此被取代(历史记录保留)。
- **「自动开平」勾选(OrderForm.auto_split,默认开)**:拆单四态的 UI 映射 =
  Auto(勾选)/None(取消,原样旁路);风控与守卫不可旁,拆单可逐请求旁
  (DESIGN ek-oms §6.2 口径的 UI 面)。s22 钉勾选双向 → mode 逐位。
- **装配口径统一**:ui_kinds 复用 station 0.1.7 `default_kinds`——oms 已装配
  ek-risk 三闸(standard_gates 缺省限额),面板与 headless 同一台 oms。
- **fixture 退役**:ui_converter_chain 从「offset 夹具进链」升格真管线链
  (拆单器入驻 oms 管线,夹具失去存在意义;dev-dep ek-converter 删除)。
  顺带实锤忠实初始化口径:无成交史的首轮 `ePositionDone.` 只记时刻等下一轮
  (原稿 1268-1288/1282-1285),第二轮距首轮 ≥3s 才置位——链测补第二轮。
- **实连钉**:ui_ctp_live 第 13 步(有仓则 CLOSE 经管线 = CLOSETODAY 实连
  证据;7×24 快照市无成交 = OBSERVE 如实记档)。13 步 110s 全收场。
- **依赖**:ek-oms 0.2.1→0.3.1、ek-station 0.1.5→0.1.7(强制 bump,防同构建
  两份 oms)。

## BD-UI-31(0.2.0 界面体验轮,2026-10-19;用户四点裁定)

- **R1 菜单一键直达**:「运维/网关/查询合约」menu_button→下拉→入口钮的
  两层结构废除,菜单钮即入口,单击直接弹窗(「帮助」保留菜单——多行内容)。
  破坏性:三个入口钮 pub 常量删除(标签 = 菜单名)。
- **R2 成交表方向上色**:方向列空绿/多红,补齐与委托/持仓表的一致口径
  (此前成交表方向为裸文本 = 遗漏,用户实看指出)。
- **R3 表格行选中 + 悬停跳色**(`UiShared::row_selected/select_row` 新口 +
  `paint_row_bg` 渲染工具):委托/未成交/成交/持仓/查询合约窗行首格单击 =
  选中行(蓝调半透明跳底,每表一行,换行即换选);行情带选中行 = 下单面板
  当前合约(带入即跳色,不另存态);全表悬停 = 淡灰底。行首格既有双击动作
  (撤单/带入/K线)全保留——egui 双击先经 clicked,选中顺带发生,无冲突。
  叠底为半透明(行文字先画,叠后仍可读)。
- **R4 标题文字撤除**:「行情带(唯一源…)」「下单面板」「盘口(合约)」三处
  题字整行删(带位/侧栏即身份;Python TickMonitor/TradingWidget 无题同口径;
  盘口括号合约代码一并删——选中合约由下单面板代码格与行情带跳色行指认)。
- **不动面(用户裁定)**:K 线窗标题保留合约;运维窗维持浮动弹窗形态
  (O1 内嵌页方案否决);「帮助」菜单保留。
- **钉**:s23 行选中(委托换选 + 成交独立槽);s17 改单击直达;快照重基线。
- **0.2.1 续(动作归行,用户裁定)**:行内任何一格单击/双击都触发该行动作
  (`click_cell` 标签矩形叠感应,外观不变);行情带盘口四列带方向
  (`import_tick`:买1 价/量 → 卖,卖1 价/量 → 买,价格列顺带填价,其余列
  方向/价格保持默认)。r8 钉逐列口径。

## BD-UI-32(0.2.2 字号与独立窗轮,2026-10-19;用户三点裁定)

- **R1 全局字号上调**(`apply_ui_style` 新口):body/button 16、small 14、
  heading 22、monospace 15(egui 缺省 13/12/18/12)——用户实看「整体字偏小」,
  对照 Python 版;render_body 顶部施加,真实 App 与 kittest 同源(快照重基线)。
- **R2/R3 查询合约窗 + K线窗独立 OS 窗**(`show_viewport_immediate`):
  原生最小化/最大化/关闭钮(此前 egui::Window 只有关闭钮,且被物理关在
  主窗矩形内不能拖出——用户对照 Python 独立窗形态指出);可拖到屏幕任意
  位置、独立任务栏条目。OS 关闭钮 = close_requested → 状态位翻负 → 父窗
  下一拍不再 show → eframe 销毁窗。Embedded 回退(后端不支持多窗:
  kittest/WEB)= 主窗内弹窗同一本体,测试断言面零改动。
- **不动面**:运维窗/CTP 窗维持主窗内弹窗(用户只点了这两个窗)。
- **选型**:immediate 而非 deferred——回调可捕获 `&UiShared`(UiShared 非
  Clone,deferred 需 Arc 化会动 render_body 签名 = 无谓破坏性);StationApp
  1s 节流重绘节奏下 immediate 的父子联动重绘代价可忽略。
- **0.2.3 勘误(关不掉根因,用户实看指出):** egui 销毁判据 = 子 viewport
  本拍未 used——0.2.2 调用点未按状态位守卫,每拍 show = 每拍 used = OS 窗
  永不销毁(X 翻位后又被 show 回来,等效 CancelClose),且窗在主窗一开就
  自己出现。修复 = show 整段包进 `contracts_window_open()` 守卫。测试面
  教训:Embedded 回退掩盖 Immediate 真窗面(21 绿 ≠ 真窗面正确),s24 注入
  recording renderer 补钉;复审纪律补条:新机制的每个调用点必须按状态位辞行。

## BD-UI-33(0.2.4 四窗一型独立化,2026-10-19;用户裁定)

- **运维窗 + CTP 网关窗也转独立 OS 窗**(「四窗皆可脱离主窗」):与查询合约/
  K线同形态同守卫;Embedded 回退维持主窗内弹窗(kittest 断言面与快照零改动)。
- **合约表吃满窗口:** 撤 max_height(260) 内嵌时代残留;动作行(计数 +
  订阅钮)前置,ScrollArea auto_shrink(false) 吃满剩余窗高——放大窗口 =
  表格变高(用户「放大窗口意义在哪里」的直接回应)。
- **`show_independent_window` 助手(四窗一型):** 守卫模式内建(0.2.3 教训
  固化成调用方纪律:位关不 show = GC 销毁);Embedded/Immediate 两面的
  "被关"统一以返回值出表,消除四份 viewport 样板重复。

## BD-UI-34(0.2.5 表头固定 + 主题 + K线最大化/ESC,2026-10-19;用户裁定)

- **表头固定(`pinned_table` 五表全上):** 官方 DIY 模式(scroll_area.rs:412
  注释「painting a sticky header on top of it」)——单 grid 实例(头行+体行)
  保列对齐,ScrollArea 之后以本拍捕获的表头格矩形重画表头带(背景盖行 +
  Label 重画;矩形已含滚动平移 → 横向跟随、纵向盖顶、零拍滞后;Label 无
  交互 sense → 点击穿透)。两轮机制实锤:①vertical 嵌 horizontal 高度塌缩
  (s3 底行钉 462<500 抓到);②同 id 双 grid 各从空白重建 State,save 交替
  request_repaint = 重绘环(kittest max_steps 爆)——两案皆弃,留 overlay 案。
- **主题四套(金融深色默认/egui深/egui浅/Qt灰白):** 菜单钮一键循环;
  Theme 枚举 + AtomicU8 运行期态(零配置口径);apply_ui_style_for 统一施加
  (主窗/独立子窗/kittest 三处同源)。语义色跨主题恒定。回答用户「颜色是
  默认还是自配」:此前 = egui 默认深色骨架 + 自配语义色(非全默认)。
- **K线窗:** maximized 打开即最大化(s25 钉 builder.maximized=Some(true));
  ESC 快捷关闭(body 返回 true → close_kline 同链;s25 钉 Embedded 面注入
  Event::Key;Immediate 面键按子窗焦点路由)。
- **行情带外层 ScrollArea 不可撤:** resizable 面板吃满前提(BD-UI-27),
  本轮实撤一次即 s3 底行钉实测塌到 462——已恢复并注释钉死。

## BD-UI-34 返修(0.2.6,2026-10-19;用户实看三报,全有机制实锤)

- **竖线(资金右):** 覆盖带 ui.put 视口外表头格 → 布局 min_rect 撑宽 →
  Frame 描边矩形撑到 x=1222(= 346.9 + 16 列总宽)。二分实锤:关覆盖带线
  消失;空内容时线跟内容宽走(1007)。修 = painter.text/rect_filled 纯绘制
  (零布局副作用)+ clip 裁越界。教训:覆盖/装饰一律走 painter,不得用布局
  调用(put/label)画视口外内容。
- **表头缝:** 带高须 = 表头格高 + item_spacing.y(3px),否则滚动行从间距带
  露出。
- **空表缩格:** 空态不得提前 return——pinned_table 照调(空 body),表头与
  吃满滚动区在场。s26 钉三表空态表头同在场。
- **group_frame_plain 使用纪律(0.2.6 钉死):** Frame::show 描边矩形按内容
  min_rect 收缩(实锤:label 内容时 gw 宽缩到 112)——只配 auto_shrink(false)
  填充物(pinned_table)使用;非表格格(资金/日志)必须走带填充 ScrollArea 的
  group_frame,否则 Frame 收缩、右边框落行中段。

## BD-UI-35(0.2.8,2026-10-19;用户实看三报,全机制实锤)

- 行情带状态行「tick N 格 / 最后更新」= 用户裁定撤除(考证:从未删过,
  0.2.0 撤的是题字)。s8 反向钉防回归。
- 表头缝硬根因 = clip_rect_margin(默认 3.0)外扩裁剪;覆盖带必须按
  inner_rect ± margin 起画。s27 滚动快照 + 像素扫描口径入 CHANGELOG。
- 空态提示一律在表头之下(grid body 首格),不得再置于 pinned_table 之前。
- 列头单源纪律扩散到全部四张格表(const HEADERS):本轮自审又抓到两处
  空态分支凭记忆写错——手写第二套表头 = 必错模式,单源是唯一防线。
- 快照回读串行化(SNAP_LOCK):本机 wgpu 并发回读会随机成组失败,测试闸
  对此环境敏感;新增快照测试必须走 snap() 不得裸调 harness.snapshot。

## BD-UI-36(0.2.9,2026-10-19;纸面网关迁出界面包)

- 用户裁定:纸面网关 = 网关,应与 ek-ctp 同级独立成包(Python 蓝图 PaperAccount
  即与 CTP/Xt/Qmt 同级独立 app;SimNow 登不上时切换的本地模拟流)。
  曾议落位:留 ek-ui(否决:界面包藏网关,无头测不到)/并入 ek-ctp(否决:污染
  纯 CTP 协议面)→ 裁定 = 独立 crate ek-paper。
- 语义零迁移实锤:旧 sim.rs 与新 matcher+plugin 非注释代码行 449↔449,
  Compare-Object 仅 4 处 Arc 写法面差异。
- 工厂表归一:sim 登记项上移 ek-station 0.1.8 default_kinds(无头/有头同一张表);
  ek-ui ui_kinds/pick_kinds 撤自建 sim 项防双份(注册名冲突 = add_plugin Duplicate)。
- 教训钉(注册表耦合顺序):改工厂表必须【先发布被依赖方】——station 0.1.8
  未发布前 ek-ui 测试 UnknownKind("sim")(本地改动对 registry 依赖不可见,
  这正是禁 path 耦合的代价与回报)。

## BD-UI-38(0.2.10,2026-10-19;纸面网关整体退场)

- 用户裁定(2026-10-19):纸面账户功能整体撤除,界面不再有 sim——
  **ctp/SimNow 两套前置(12 小时/7×24,选前置用 ek-ctp scripts/simnow-fronts.ps1
  -Probe)已满足当前需求**。BD-UI-37(sim 对外面对齐 CTP 的改造方案)作废,
  未执行半成品整堆丢弃(ek-paper 工作区回滚到 0.1.1 已发布态)。
- 退场涟漪(三包):ek-station 0.1.12 撤 ek-paper 依赖 + 工厂表 sim 项
  (pipeline_headless 改 dm/oms/ctp 装配 + CTP 回报行直发注入,账本/行情/因子
  钉数值零变);ek-ui 0.2.10 撤 ek-paper 依赖 + 下拉/兜底/钮/测试全清;
  ek-paper 0.1.x 档案原样留存(不删仓库不发版不 yank——yank 反咬已发布老版)。
- 界面口径:接口下拉 ∪ 集改 {ctp};parse_gateway 空兜底 sim→ctp;运维窗
  「流控查询」钮撤(UiCmd::QueryRate 变体同步撤——公共 API 减项);
  demo 窗 = dm/oms/ui(网关经运维窗激活);demo_tour 纸面段整段删(8 步重编)。
- 测试口径:ui_sim.rs 整删(9 钉);ui_converter_chain 网关位改测试本地
  "ctp" 受理夹具(受理形制忠实 ek-ctp:SUBMITTING 单上总线 + ok 回执 +
  `{gw}.` 全号;成交按 OnOrderTrade/OnRtnTrade 同构行直发)——本钉主体
  一直是 oms 五段管线,与网关撮合无涉,换顶替零减钉;kittest 夹具标签
  sim→ctp(五张快照重基线:s3/s10/s16/s21/s27,钮撤除 + 标签改名像素变);
  71→62 钉(减的 9 = ui_sim)。
- 纪律钉:历史条目(BD-UI-36、CHANGELOG 0.1.8/0.1.9 sim 入表等)是史实,
  不改写;退场只记新条目。

## BD-UI-39(多账号面:账户面板取代网关窗,规划 docs/ek-multi-account-plan.md)

> 本条取代的旧口径(史实保留不改写):BD-UI-24 的 `连接 CTP` → 硬编码
> `ctp.configure` / `ctp.subscribe`;BD-UI-27/33 的「CTP 网关配置窗」单账号窗体;
> BD-UI-38 的接口下拉 ∪ {ctp} 与 `parse_gateway` 空兜底 `"ctp"`。

- **菜单「网关」→「账户」**:旧单账号网关窗退役,收纳为账户面板的添加/编辑表单
  (9 输入位 + 密码掩码 + 收录勾选原样沿用,`ctp_grid` 未改字段)。旧窗的死面
  (`ctp_window` 状态位、`MENU_CTP`、`BTN_CTP_CONNECT`)一并删除,不留"供旧测试引用"。
- **账户清册在界面侧**(`UiShared.accounts`):行 = 派生注册名 + broker + 账号 +
  在场位 + 元账号标记 + 表单留档。派生名唯一入口 `ek_ctp::derived_gateway_name`
  (界面不自己拼字符串);密码只在内存,本轮不持久化(规划 §10.2)。
- **添加时序钉(规划 §5.A)**:一个钮两条 cmd —— `Start{factory:"ctp", name:<派生名>}`
  先装配,`CtpConfigure{gateway:<派生名>, form}` 后注入凭据(凭据不进 svctl.start 通道)。
- **单源纪律在"添加"这一刻定源(规划 §5.C)**:无元账号且无在场账户 → 新账户自动成为
  **行情元账号**(md 前置保留;清掉它等于这个账户自己也永远收不到 tick);已有元账号或
  已有在场账户 → 按纯 TD 装配(md 前置清空)。订阅两个入口(下单面板代码回车 /
  查询合约窗勾选)一律经 `subscribe_target` 路由到元账号,未指定就明确报错不静默发。
- **元账号连接后锁定(用户裁定「账户连接后不允许切换」)**:任何账户已装配即拒切换;
  换源流程 = 撤挂单 → 移除 → 改配置 → 重连 → 重放订阅(订阅集 `note_subscriptions`
  登记,元账号新装配后自动重放)。
- **移除守卫**:该账户有活跃委托 = 不给移除钮(只给"先撤单才能移除"提示);有持仓 =
  可移除但警告"柜台侧不受影响,重加后由轮转查询重建视图";两步确认沿用 `confirm_step`。
- **网关名回落只剩一个阶梯**:`default_gateway()` = 上次手动选择 → 首个已装配 → 首个;
  一个账户都没有 = 禁用「委托」钮并给提示(旧"空 → 字面量 ctp"兜底退役,B4)。
  撤单三处(行内钮 / 全撤 / 行双击)一律随该单自己的 `gateway_name`。
- **诚实标注钉**:账户表状态列写「已装配 / 未装配」不写「已连接」——界面只能从
  `svctl.list` 看见实例在场,登录成败 ek-ctp 无状态口可查(开放项)。
  **(0.2.19 已接上:`{gw}.status` 落地,状态列改两段口径「已装配·<登录态>」;
  「不写已连接」这条口径不变,见文末 0.2.19 节。)**
- **测试口径**:s13 重写(添加账户 → 派生名 + 两条 cmd + 首个账户自动定源 +
  第二账户 md 清空不被抢源)、s17 菜单改「账户」、s18/s20 订阅路由到元账号、
  新增 s28(元账号锁定 + 挂单移除守卫 + 守卫解除);五张快照重基线
  (s3/s10/s16/s21/s27,菜单标签与 tick 列名「接口」→「行情源」像素变)。

## 0.2.19(接上网关状态口 + 『断开』钮;规划 docs/ek-ui-account-tabs-plan.md P3,2026-10-22)

事实源换成了 ek-ctp 0.9.0 的两个服务口(`{gw}.status` / `{gw}.disconnect`)。界面侧的六条口径:

- **D-UI-STATUS-1 状态列两段,不合并**:装配位(`svctl.list` 在场)与登录态(`{gw}.status`
  读数)是两件事,拼在一栏显示(`已装配·在线` / `·掉线` / `·登录失败` / `·查询断供` /
  `·已断开` / `·登录态未知`)但**不合成一个布尔**。0.2.18 的「已装配·登录态未知」保留为
  读数缺失时的显式档位 —— 缺口从「不可见」变成「本轮还没问到」,两者必须长得不一样。
- **D-UI-STATUS-2 只认 `logged_in`,不认 `handle_up`**:网关的「句柄已建 / registerFront 已发」
  在掉线时仍是真(ek-ctp DESIGN D-STATUS ①)。界面照抄它 = 替掉线的账户谎称在线(s48 读数二钉)。
  同理 **"从未回报过"不当"0 秒前回报过"用**:`td_last_account_ms = None` 是"没有这件事",
  不是"很久没做"。
- **D-UI-STATUS-3 阈值住在界面**:「查询断供」= 连续被拒达 `QUERY_REJECT_STREAK_LIMIT`(3)
  **或** 资金回报超 `ACCOUNT_STALE_SECS`(30 秒)。网关只给原料(计数与时刻),"几次/多久算断供"
  是界面口径 —— 它才知道自己多久刷一次、多久问一次。覆盖位 `set_status_thresholds` 已开,
  配置文件面还没定(规划 Q10 只说"进配置",ek-ui 现在零配置)。
- **D-UI-STATUS-4 轮询只问"已装配且在场"的行**:每 `STATUS_POLL_TICKS = 2` 拍问一次。
  实例被摘除后清册行还在,照问不误就是每次等满 2 秒超时、按账户把 eTimer 刷新循环拖住(c5 钉)。
  状态口在 ek-ctp 侧只读、不写日志,所以这条轮询不吃 eLog 环(512 行环保护不变)。
  **问不到就退回「未知」并写明原因**,上一轮的「在线」不得留档(s50)。
- **D-UI-DISCONNECT-1 断开 = 停会话,移除 = 停实例,两条口不共用**(规划裁定 1):
  『断开』发 `UiCmd::Disconnect` → `{gw}.disconnect`;『移除』发 `UiCmd::Remove` → `svctl.remove`。
  断开后装配态不变、徽标「已断开」、恢复入口仍是『连接』(插件侧走「已在场 → 重投凭据」。
  **同一道活跃委托守卫两者共用**:会话一停就撤不了单也不再回报,与『移除』要挡的是同一个
  观测黑洞(规划 §5.B),出口也同一个(『先撤 N 笔』)—— 这是执行自查补上的一条(s54)。
- **D-UI-STATUS-5 实例不在场即作废读数**:`sync_accounts_connected` 把行从「已装配」打回
  「未装配」时,登录态与细节一并清零;渲染侧只给已装配的行印状态细节(s55)。
  留着就是替一个已经不存在的会话说话。
- **纯行情账户看 MD 半边**:判据是"该行 td 前置留档是否为空"。TD 半边对它是全假的,
  照抄只会得到「未知」——那是假信息(s53)。
- **测试口径**:kittest 45 → 53 钉(s48 三档读数与旧读数不留档、s49 断供两判据与阈值覆盖、
  s50 问不到退回、s51 『断开』入列与徽标翻转、s52 未装配无『断开』钮、s53 纯行情看 MD、
  s54 断开共用活跃委托守卫、s55 摘除即作废读数);s37 口径翻转;总线级新增 c4/c5
  (真起一个 ek-ctp 实例:服务目录含 `.status`/`.disconnect`、ask 真拿到 `GatewayStatus`
  —— 读数里没有「问不到」就是两份 ek-ctp 没静默断的证据);s39/s40/s41 快照重基线。

## 0.2.20(三条开放口径落进界面 + 接 ek-ctp 0.9.1 分项;2026-10-22)

- **D-UI-F4-SCOPE 勾选的作用范围写死成一句话,两处同源**:勾选只管**这个网关自己**收录并上抛的
  合约;公用合约表 = 在场各网关的并集(规划 D3「合约与行情公用」的直接推论);改勾选须
  『连接/重试』才下发。为什么必须写在界面上:实连 S14b 里账户 B 改勾选「期货+期权」后公用表
  仍 852 条、期权 0 条,而界面没有任何地方区分「没生效」与「网关没查到」—— 让用户猜就是
  把口径缺陷转嫁给用户。**同一口径不得只写在一处**(面板底部 + 配置弹窗,s57 数到 2 处)。
- **D-UI-Q17 运维窗对账户清册实例只列不操作**:名字在清册里 → 那一格改写成「起停归『账户』面板
  (带撤单守卫)」,不给『移除』也不给『重注入』(后者对命名实例本来就是 unknown factory)。
  理由不是"整洁",是**守卫可绕过**:账户面板的『移除』带活跃委托闸 + 二次确认 + 持仓提示,
  运维窗那条是同一动作的无守卫副本,两条路并存等于守卫形同虚设。**清册外的 `ctp_*` 实例仍留
  这条出口**(手工起的 / 旧流程残留没有别的退路)—— 边界收口不等于把门焊死。实例仍列在表里:
  砍的是操作,不是运维可见性(s56)。
- **D-UI-LOG-WINDOW 日志窗自报是窗口不是历史**:溢出计数原来只有数字;现在同一行接上去处
  (「登录/掉线/查询被拒看状态列与状态细节,别回扫这里」)。依据是巡回 G1 的实连证据 ——
  90 秒没抓到「交易服务器登录成功」行而资金行下一拍就刷新,**登录其实成功,是 512 行环被
  状态推送流挤满**。取证的事实源自此是网关状态口(0.2.19 起),不是日志回扫(s59)。
- **D-UI-STATUS-6 分项点名但不另开一行**:ek-ctp 0.9.1 的 `td_instrument_rejects` 接进状态细节
  句尾(「查询连续被拒 3 次;合约查询被拒 2 次」),同一件事不重复点名,分项为 0 不加句子。
  总数与连续串**不分路**是对的(消费侧要的是「查询这条路连续被拒几次」),但合约一条没到手时
  下一笔单会被 ContractGate 以 `contract:unknown` 拒掉 —— 状态列只写「在线」就是假信息(s58)。
- **测试口径**:kittest 53 → 57 钉(s56 运维窗收口且工厂插件照旧、s57 口径两处同源、
  s58 分项三段、s59 日志窗口径);s39/s40/s41 快照重基线(面板底部多一行口径句);
  `.gitignore` 补 `**/snapshots/**/*.old.png`。

## 0.2.21(第三条断供判据:「从未到手」也要超时;2026-10-22,依赖不变)

- **D-UI-STATUS-7 「从未发生」不能只是"没有这句话",它自己也要有超时位**:0.2.19 立「`None` 不当 0 用」
  时,只把它用在"距最后一次回报多久"这一判据的**前置条件**上(`Some` 才谈超时)。实连 S15f 第一轮
  读数照出洞:F2 账户 `logged_in=true`、`td_query_rejects=0`、`td_last_account_ms=None` 持续 390 秒
  不变 —— 于是界面永远是「已装配·在线 + 资金回报尚未到手」。**徽标是绿的,查询却彻底停摆**,
  这就是 F2 在界面上的形状。
- **修法与边界**:登录着的分支第三条判据 = 距 `td_last_login_ok_ms` 超过同一个 `ACCOUNT_STALE_SECS`
  且资金时刻仍 `None` → `已装配·查询断供` + 「资金回报从未到手(登录已 Ns)」。刚登录几秒不算
  (轮转第一拍本就要 2~6 秒);真到手即回在线。**不新建第二个阈值**:"多久算等太久"在两条判据里
  必须是同一个数,否则同一件事有两种松紧。
- **为什么修在 ek-ui 而不是 ek-ctp**:判据原料(登录时刻 + 读数时刻)网关早就给了,缺的是消费侧
  怎么用 `None`。网关侧加"资金超时"位就是把界面口径往上游搬(D-UI-STATUS-3 的反面)。
- **测试口径**:kittest 57 → 58 钉(`s60` 三段:5 秒 = 在线带话 / 40 秒从未到手 = 断供带时长 / 到手即回在线)。