ek-ui 0.2.21

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
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
# CHANGELOG

## 0.2.21(2026-10-22;第三条断供判据:登录后资金回报「从未到手」也会超时)

只动本 crate,依赖不变(仍 ek-ctp 0.9.1 / ek-oms 0.3.7 / ek-station 0.2.3)。

### 实连读数把口径的洞照出来了

ek-station 实连套新增 S15f(向每个在场账户 ask `{gw}.status`)第一轮读数:

- 账户 A:`logged_in=true 掉线=0 查询被拒=0 资金时刻=Some(..)` —— 一切正常。
- 账户 B(F2 那个账户):`logged_in=true handle_up=true 掉线=1 查询被拒=0(连续 0) 资金时刻=None 持仓时刻=None 错误=None`,
  且这个形状在 150 秒 + 240 秒两轮等待里**一直没变**。

0.2.20 的判据是「连续被拒达阈 **或** 距最后一次资金回报超时」——第二条要求 `td_last_account_ms`
是 `Some`。**一次都没到手**时它永远是 `None`,于是 B 在界面上一直是「已装配·在线 + 资金回报尚未到手」。
「尚未到手」这句话在场,但徽标是绿的:这正是 F2 在界面上的形状 —— 一个查询彻底停摆的账户看着像活着。

### 改法:同一个 stale 阈值也管「从未到手」

`apply_status` 登录着的分支加第三条判据:距 `td_last_login_ok_ms` 超过 `ACCOUNT_STALE_SECS`(30 秒)
且资金时刻仍为 `None` → `已装配·查询断供` + 细节「资金回报从未到手(登录已 Ns)」。
刚登录那几秒不算(轮转第一拍本就要 2~6 秒,ek-ctp 0.7.0 口径);真到手即回在线,判据不粘。
**判据原料齐了(登录时刻 + 读数时刻),网关侧不需要多给任何东西** —— 这也是为什么这条修在 ek-ui 而不是 ek-ctp。

### 测试与门禁

- kittest 钉 57 → 58(`s60`:刚登录 5 秒 = 在线 + 一句话 / 登录 40 秒从未到手 = 断供带时长 / 到手即回在线)。
- `cargo fmt` 干净;`cargo clippy --all-targets -- -D warnings` = 0,`--features ctp-native` 面同样 0;
  `cargo test --tests` 全绿。

## 0.2.20(2026-10-22;三条开放项收口:F4 勾选口径、Q17 运维窗移除边界、日志窗口径 + 接 ek-ctp 0.9.1 分项)

依赖随 ek-ctp 0.9.1 整链抬版(`ek-ctp` 0.9.0 → 0.9.1、`ek-oms` 0.3.7、`ek-station` 0.2.3)。
这一版没有新功能,做的是**把三条一直挂在开放项里的口径落到界面上**,并把 ek-ctp 0.9.1 的
新读数接进状态细节。

### F4:「谁查谁过滤」写死成一句话(面板底部 + 配置弹窗各一处)

- 实连 S14b 的事实:账户 B 把勾选改成「期货+期权」,公用合约表仍 852 条、期权 0 条 ——
  界面上没有任何地方告诉用户这是「没生效」还是「网关没查到」。
- 现在两处都写明:**勾选只管该网关自己收录并上抛的合约;公用合约表 = 在场各网关的并集
  (合约与行情公用,规划 D3);改勾选须『连接/重试』才下发到网关。** 钉 `s57` 数两处
  (同一口径不得只写在一处)。

### Q17:运维窗不再对账户清册里的实例提供『移除』/『重注入』

- 账户面板的『移除』带全套守卫(活跃委托闸 + 二次确认 + 持仓提示),运维窗那条是同一动作的
  **无守卫副本** —— 同一个实例两条移除路、守卫只长在一条上,等于守卫可以绕过。
- 现在:名字在账户清册里 → 运维窗那一格改写成「起停归『账户』面板(带撤单守卫)」,
  实例**仍列在表里**(运维可见性不砍),给的是去处不是死钮。
- **清册外的 `ctp_*` 实例(手工起的 / 旧流程残留)仍留这条出口** —— 否则没有退路。
  钉 `s56`(工厂插件 dm/oms 的『移除』『重注入』照旧在场)。

### 日志窗:溢出时把「这是窗口不是历史」说出来

- 巡回 G1 的实连证据:重加账户后 90 秒内没抓到「交易服务器登录成功」行,但同一账户的资金行
  下一拍就刷新 —— 登录其实成功,是 512 行环被状态推送流挤满把登录行顶掉了。
- 原来只写「已丢弃 N 条(环形 512)」;现在同一行接上口径与去处:**「日志窗是窗口不是历史 ——
  登录/掉线/查询被拒看账户面板的状态列与状态细节,别回扫这里」**。钉 `s59`。

### 接 ek-ctp 0.9.1 分项:合约查询被拒在状态细节里点名

- `apply_status` 在句尾追加「合约查询被拒 N 次」(`td_instrument_rejects > 0` 时),与断供句
  并存(「查询连续被拒 3 次;合约查询被拒 2 次」),**同一件事不重复点名**。
- 为什么要点名:总数与连续串不分路是对的(消费侧要的是「查询这条路连续被拒几次」),但合约
  一条没到手时下一笔单会被 ContractGate 以 `contract:unknown` 拒掉,而状态列只写「在线」就是假信息。
- 分项为 0 不加句子(健康行的状态细节不无谓变长)。钉 `s58` 三段(未达阈 / 达阈并存不重复 / 零分项不加)。

### 测试与门禁

- kittest 钉 53 → 57(`s56`–`s59`);快照 `s39`/`s40`/`s41` 重基线(面板底部多一行口径句);
  `.gitignore` 补 `**/snapshots/**/*.old.png`(重基线副产物)。
- `cargo fmt` 干净;`cargo clippy --all-targets -- -D warnings` = 0,`--features ctp-native` 面同样 0;
  `cargo test --tests` 全绿(98 例,其中 kittest 57)。

## 0.2.19(2026-10-22;接上网关状态口:状态列两段口径 + 『断开』钮;规划 P3 的界面半边)

依赖随 ek-ctp 0.9.0 整链抬版(`ek-ctp` 0.8.0 → 0.9.0、`ek-oms` 0.3.5 → 0.3.6、
`ek-station` 0.2.1 → 0.2.2)。0.2.18 那条「界面只有在场位,登录成败不可见」的缺口,
这一版由 ek-ctp 的 `{gw}.status` 补上 —— 规划里的 P3 至此闭合(修 U5 全条、给 F2 界面出口)。

### 状态列改两段口径:装配位 + 登录态(`state::LoginState`)

- **`AccountRow` 新增 `login: LoginState` + `status_note: Option<String>`**,状态列文案由
  `status_label()` 拼:`已装配·在线` / `已装配·掉线` / `已装配·登录失败` / `已装配·查询断供` /
  `已装配·已断开` / `已装配·登录态未知`。着色:在线绿、掉线与登录失败红、查询断供橙、未知与已断开灰。
- **六档的判据(`AccountRow::apply_status`)按顺序写明**:登录失败 → 登录着(才谈断供)→
  主动『断开』→ 柜台掉线 → 未知。
- **`td_handle_up` 真不等于在线**:网关的"句柄已建/registerFront 已发"在掉线时仍是真
  (ek-ctp DESIGN D-STATUS ①)。界面只认 `td_logged_in`,没有它就只有「未知」——
  这一条有钉(s48 读数二:handle_up 真 + logged_in 假 → 必须显示「掉线」)。
- **`None` 不当 0 用**:网关没回报过资金时刻 ≠ 「0 秒前刚回报过」;登录成功但资金一次
  都没到手 = 在线 + 细节「资金回报尚未到手」,不冤枉成断供(轮转第一拍本就要几秒)。
- **纯行情账户看 MD 半边**:该行 td 前置留档为空时,TD 读数永远全假,照抄只会得到「未知」
  —— 那是假信息,改看 `md_logged_in`/`md_disconnects`(s53)。

### 轮询(`UiPlugin::refresh` 尾,`STATUS_POLL_TICKS = 2`)

- eTimer 每 1 秒一拍,每 2 拍向每个**已装配且在场**的账户 ask 一次 `{gw}.status`。
- **只问在场的**:实例被摘除后清册行还在,照问不误就是每次等满 2 秒超时、按账户把刷新循环拖住
  (钉 c5:未装配的行 `status_note` 必须仍是 `None`)。
- 状态口在 ek-ctp 侧只读、不写日志,所以这条轮询不会冲 eLog 环(512 行环的保护不变)。
- **问不到就退回「未知」并写明原因**(`note_status_unreachable`):上一轮的「在线」不得留档
  —— 问不到就是没有证据(s50)。

### 「查询断供」两判据(规划 Q10;阈值住在界面,不住在网关)

- `QUERY_REJECT_STREAK_LIMIT = 3`(连续被拒次数)、`ACCOUNT_STALE_SECS = 30`(资金回报超时秒)。
- 两判据任一命中即「查询断供」;细节行写明是哪一条(`查询连续被拒 3 次` / `资金回报已 60s 未更新`)。
- `set_status_thresholds(limit, secs)` / `status_thresholds()`:覆盖位(测试夹具用它把判据收紧;
  后续要进配置文件就接这一位)。

### 『断开』钮(`UiCmd::Disconnect` → `{gw}.disconnect`;规划裁定 1)

- 只对**已装配且会话还活着**的行画这颗钮(条件不成立就不画,不是画出来再禁用;规划 Q21)。
- 回执后徽标「已断开」,**装配态不变**(实例还在,『移除』仍是另一颗钮、另一条口)。
- **恢复入口仍是『连接』**:会话停了但实例在 → 该行重新给『连接』钮 → `UiCmd::Connect` →
  svctl 回「已在场」→ 插件侧改走**重投凭据**(不误判成装配失败)。
- 按了钮立刻入列并落一条日志,不等下一轮轮询(`mark_account_stopped`)。
- 未装配的行没有『断开』钮(s52)。

### 状态细节行

有话可说的行(带次数 / 带秒数 / 带原因)在面板底部逐行给一句 `状态细节 <派生名>:<原因>`。
为什么不塞进状态列:Grid 列宽实测会把长文案挤成竖排(s39 实锤)。为什么不只给告警行:
「状态口问不到」与「纯行情账户」也是事实,灰色徽标不给原因用户只能猜。健康行没有细节,
所以这一段不随账户数无谓变长。

### 执行自查(写完这一版自己审出来 3 处,当场修 + 补钉)

1. **『断开』漏了活跃委托守卫**:会话一停就撤不了单、也不再回报 —— 与『移除』要挡的是同一个
   观测黑洞(规划 §5.B)。现在两颗钮共用同一道闸:有活跃委托就不画『断开』,出口仍是『先撤 N 笔』
   (钉 s54:撤完即恢复,不是一次性禁用)。
2. **实例被摘除后登录态读数留着**:`sync_accounts_connected` 把行打回「未装配」时不清
   `login`/`status_note`,而状态细节行只看"有没有细节"不看装配态 → 结果是在一个「未装配」
   的账户下面印「掉线 2 次」,替一个已经不存在的会话说话。两处都修(状态侧作废 + 渲染侧只给
   已装配的行)(钉 s55)。
3. **状态列文案与 `AccountStage::label()` 重抄了一遍**(`未装配`/`装配中…`/`装配失败` 两处字面量):
   `status_label()` 改为非装配段直接走 `stage.label()`,单一事实源。顺带把配置弹窗的
   「装配状态(只读)」升级成「装配/登录状态(只读)」——保存前用户就该看见这一行现在是什么状态。

### 测试

- **s37 口径翻转**(原名 `..._exposes_only_presence` → `..._shows_stage_and_login_state`):
  0.2.18 钉的是「掉线/断供 各出现 1 次(只在口径句里)」,0.2.19 钉的是「真读到才上屏,
  没读到一处都不该有」+「已装配·登录态未知 必须逐行在场」+『断开』钮只给已装配行。
- **新钉 s48–s55**:读数三档各自显形且旧读数不留档 / 断供两判据与阈值覆盖 / 问不到退回未知 /
  『断开』入列(且不得顺手发移除令)与徽标翻转 / 未装配无『断开』钮 / 纯行情账户看 MD 半边 /
  『断开』共用活跃委托守卫 / 摘除即作废读数。
- **新钉 c4/c5(总线级,`tests/ui_ctp.rs`)**:`UiCmd::Connect` 起真实例 → 服务目录含
  `.status`/`.disconnect` → 轮询真拿到 `GatewayStatus`(读数里没有「问不到」= downcast 成功 =
  两份 ek-ctp 没有静默断)→ 纯逻辑传输登不上时必须仍是「未知」→『断开』后徽标「已断开」、
  实例仍在场、后续轮询不得把它翻回「未知」;c5 钉"未装配的行不被轮询"。
- kittest 钉数 45 → 53;s39/s40/s41 快照基线重生成(`UPDATE_SNAPSHOTS=1`)。
- 门禁:fmt / clippy(默认面 + `ctp-native` 面)0,`cargo test --tests` 全绿(89 例)。

### 未做(写明边界)

- 断供阈值仍是常量 + 覆盖位,**没有进配置文件**(Q10 只说"进配置",界面没有配置文件;
  要接的是运维面板一个输入格,或 ek-ui 的可选 `--config`,都还没定)。
- 状态读数**不持久化**(裁定 2:凭据与状态都不落盘);重启后第一拍之前一律「未知」。
- 掉线没有被动事件推送(ek-ctp 侧裁定:只给 ask 口,不发总线事件)——界面靠轮询显形,
  最坏 2 秒延迟。
- 运维窗对 `ctp_*` 实例的『移除』与账户面板的『移除』仍是两条路(Q17 边界未收口)。

## 0.2.18(2026-10-21;账户配置重构:四态分离 + 列表与配置弹窗;执行核查 5 处修正)

按 `docs/ek-ui-account-tabs-plan.md`(裁定 0:不做 tab-per-account,做「列表 + 配置弹窗」)
分两个 commit 落地,再做一轮执行核查(自查抓到 5 处问题,当场修 + 补钉 s46/s47)。

### Commit A(数据模型:添加 ≠ 连接 ≠ 断开 ≠ 移除,规划 §3.3)

- **`UiCmd::Connect { gateway, form }` 新增**:一条令做完 `svctl.start ctp <派生名>` +
  `{gw}.configure`(插件侧顺序执行)。旧 `UiCmd::Start` / `UiCmd::CtpConfigure` 保留为低层口
  (运维窗重注入仍走 `Start`),但**账户面板不再下发两令**。
- **『添加账户』只写清册,不下发任何指令**(旧口径:点添加 = 立刻 start + configure)。
  行内『连接 <显示名>』/『重试 <显示名>』才是上线入口。
- **`AccountStage` 四态**:未装配 / 装配中… / 已装配·登录态未知 / 装配失败:<原因>。
  状态列只说**实例装配**(svctl 在场位),登录成败、掉线、查询断供界面不可见 ——
  面板底部一句口径说明点名这一缺口(开放项 `ctp.status`,规划裁定 3:等终态口,不自建过渡)。
- **装配失败不回滚清册行**:留红字原因 + 给『重试』(修 U3)。
- **每账户独立 form**(修 U4):『添加』草稿位凭据清空(账号/密码/前置),broker/AppID/AuthCode
  沿用;纯 TD 账户的 md 位在**留档与下发同一份**清空(旧口径只清下发,编辑回填带着一根用不上的前置)。
- **取消编辑清空凭据**(修 U6);**`CtpForm::validate` 前置形制校验**(修 U7):
  `tcp://host:port` + 端口 1..=65535;td 前置必填,**md 前置允许为空**(纯 TD 账户,单源纪律)。
- **`default_gateway()` 与下单下拉只认已装配账户**(规划 Q4):只添加未连接的账户
  不进下拉、不点亮委托钮。
- **软上限 `MAX_ACCOUNTS = 8`** + **别名唯一**(规划 Q3);`ConfigureReply.product_classes`
  回带留档(规划 U9 前置)。
- 钉口径重排:s13 / s31 / s32 / s33(原 s33 改名 `…_leaves_row_and_allows_retry`)/ s34 / s35 /
  s36 / s37 全部按新口径重写;s39 快照重生成。

### Commit B(界面形态:7 列一览 + 非 modal 配置弹窗)

- **账户面板 = 一览**:7 列 `egui::Grid`(网关(派生名) / 别名 / 账号 / 状态 / 行情源 /
  收录(勾→实) / 操作),列表上方一行「当前行情源 = …」「账户 n/8」「添加账户」入口;
  达软上限后入口撤场并给提示句(不给宿主侧注定被拒的请求)。
- **配置表单从窗内内嵌改为独立非 modal 弹窗**(`account_edit_win`,680×340):
  添加 / 编辑两模式;钮 = 『保存』/『保存并连接』/『取消』。
  关闭或『取消』= **放弃未保存**(规划 Q24);校验被拦 = **不关弹窗** + 红字『拦下:原因』+ 日志(规划 Q26,可就地改)。
- **编辑模式 broker/账号只读** + 橙字口径句「派生注册名 = …(由 broker + 账号决定):要改这两格请『移除后重加』」(规划 Q25);
  **非行情源且已锁定 → md 三位置灰** + 口径句(规划 Q12)。
- **收录列并排显示勾选意图与网关回带事实**(`勾:期货` / `实:期货+期权`),弹窗内另有只读回带行
  「网关实际收录(只读回带)…装配状态(只读)…」(规划 U9)。
- **移除守卫给出口**:活跃委托 >0 时不再只堵门,给『先撤 N 笔』按钮逐笔入列 `CancelOrder`
  (网关 = 该账户自己的派生名,与「全撤」同一条 oms 通道)(规划 U10)。
- **移除 / 确认移除 / 待二次确认句统一取显示名**(别名优先,空则账号)——
  顺带修掉一个 Commit B 自造坑:`pending_confirm` 记槽按派生名、按钮文案按显示名,
  两者对不上导致首点后按钮永远翻不到『确认移除』(s31/s38 实锤)。
- **账本残留行加『(已摘除)』标注**(修 U8):委托/成交/持仓表的「接口」列,清册非空且该网关不在清册
  = 已摘除账户的残留行(摘除不清账本 = F5 实连复测口径)。口径边界:清册为空不标注;资金格不标注。
- **`push_connect` 入列即置「装配中」**(规划 Q7 界面侧一半):否则入列到出队之间行上钮还亮 = 可重复入列。
- `examples/opening_multi_tour.rs` 改走 `UiCmd::Connect`(单源裁决移到插件侧 `prepare_connect_form`)。
- 新增钉 s40–s45(添加弹窗快照 / 编辑弹窗快照含只读置灰与回带 / 别名上列表但不参与寻址 /
  软上限撤钮 / 『保存并连接』一条令 / 『先撤 N 笔』逐笔入列)+ s39 重生成 + s40/s41 新快照。

### 执行核查(5 处,当场修 + 补钉 s46/s47)

1. **摘除 × 非 modal 弹窗的连带口径**(真坑,可致错配账户):弹窗开着时可以点同一行『移除』——
   编辑目标消失后弹窗会**静默退化成「添加模式」**(只读句消失、字段全可编辑),点『保存』会拿旧凭据
   建出一个新账户。修:摘除连带 `end_account_edit_if_editing`;『保存』前检查编辑目标仍在清册;
   `begin_account_edit(Some(gw))` 对不在册的 gw 直接拒。钉 s46。
2. **下单下拉补堵已摘除网关**:清册非空时,不在清册的历史网关名(= 已摘除账户)不再进下拉;
   表单里「选过它」的陈旧选择就地回落默认阶梯并落盘。否则下一笔单注定撞 `unknown:gateway`。钉 s47。
3. **同一行四个钮口径统一**:『连接/重试』也取显示名(此前只有『编辑/移除』取显示名)。
4. **撤除多余代码**:`account_grid` 行选记槽(`select_row`)是内嵌表单时代遗留,已无读者。
5. **文案口径漂移**:state.rs 文档写「实例已装配(登录态未知)」而实现是「已装配·登录态未知」
   (短形是被 `egui::Grid` 列宽夹出来的 —— s39 快照实锤过长文案会竖排折字)。

### 破坏性变更 / 已知边界

- 账户面板布局与钮文案变化:`移除 <派生名>` → `移除 <显示名>`;『编辑』不再就地切换共享表单,而是开弹窗。
- `UiCmd::Connect` 是账户上线的唯一界面入口;`UiCmd::CtpConfigure` 仍可被外部宿主直接调用(低层口)。
- 仍不可见(等 `ctp.status`,规划 P3):登录成败、掉线、查询断供的逐行状态。
- 仍不做:账户持久化(规划裁定 2)、多 broker 行情源、勾选面(product_classes)多账户语义(F4)。
- kittest 查询面只有 label 口径 → **置灰/只读无法按控件断言**,只能钉口径句 + 快照(s35/s41 注释已记)。

## 0.2.17(2026-10-21;账户面板布局两处硬伤修复 + 账户配置界面核查钉 s33–s39)

- **修① 账户表与账户表单互相压字(整窗不可读)。** 账户清单原先走 `pinned_table`
  = `ScrollArea::both().auto_shrink([false,false])`(吃掉整块可用高)+ 覆盖式表头带
  (`render.rs:pinned_table`),而账户面板是**自动尺寸浮动窗** —— 两者凑不出正确高度:
  账户行与其后的「行情源已锁定…」口径句、整张 9 输入位表单叠在同一像素上,文字互相覆盖
  (前后对照见视觉快照钉 `s39_account_panel_layout`)。账户行最多个位数,不需要固定表头滚动面
  → 改普通 `egui::Grid`。⚠️ `account_grid` 不再是 ScrollArea(行选择/编辑钮口径不变)。
- **修② 前置输入位看不见自己填的内容。** 表单原先是 6 列 `Grid`:列宽在测量期被
  「窗宽 ÷ 列数」夹死(实测每格 ≈120px),`desired_width` 永不生效 → 一根真实前置
  `tcp://182.254.242.81:30001`(26 字符)只显示出 `tcp:/`,端口看不见也没法核对。
  改为三行 `ui.horizontal` + `desired_width(210)` → 前置与端口完整可见。
- **新增现状钉 s33–s38(账户配置界面六条问题,每条都写明待修口径):**
  s33 装配失败(svctl 快照无此名)→ 行留清册、界面零显形、再点添加被『已存在』拒(须先移除);
  s34 共享表单串味:添加后不清空,第二账户继承上一家密码/前置,且**纯 TD 装配只清下发的那份
  `md_addr`,清册留档没清**(编辑页回填带行情前置);
  s35 取消编辑只解除选中不清空表单(此时改一格即建出凭据错配账户);
  s36 前置为空照样建账户并下发 Start+Configure(界面不拦,只提示 md 空);
  s37 面板状态列只有『已装配/未装配』(= svctl 在场位),登录成败与掉线**无任何可见位**
  (实连复测 F2 那种「在线但查询整段断供」在面板上与正常账户一模一样;开放项 `ctp.status`);
  s38 摘除账户后该账户的委托/持仓行仍在账本,界面无『该账户已摘除』标注。
- **新增视觉快照钉 s39**(`tests/snapshots/s39_account_panel_layout.png`):两账户 +
  真形制前置一屏,布局回归闸(与实窗同字体面,BD-UI-26)。
- 门禁:fmt 0 / clippy `--all-targets --features ctp-native` 0 / 全测试绿(kittest 37 钉)。

## 0.2.16(2026-10-21;随 ek-oms 0.3.5 / ek-station 0.2.1 整链抬版:撤单归属守卫)

- **依赖抬位:`ek-oms` 0.3.4 → 0.3.5、`ek-station` 0.2.0 → 0.2.1。** 界面代码零改动。
- **为什么界面不用改:** 账户面板的「全撤」与单行撤都用**每笔单自己的网关**
  (`UiCmd::CancelOrder { orderid, gateway }` → `en_orderid = {gateway}.{orderid}`),
  与 `OrderData::en_orderid()` 同式 → 天然满足 ek-oms 0.3.5 的归属守卫。
- **抬版带来的实际变化:** 若界面与 oms 的 `en_orderid` 组式哪天不同式(或账户切换后残留旧网关),
  现在会在 oms 显形成 `mismatch:gateway` 被拒并落日志,而不是交付到错误网关后在柜台沉默。
- 门禁:fmt 0 / clippy `--all-targets --features ctp-native` 0 / kittest 全绿(含 s30「全撤随单走」)。

## 0.2.15(2026-10-21;多账号面:账户面板;DESIGN 新条 BD-UI-39)

- **菜单「网关」→「账户」**:旧单账号 CTP 配置窗退役,收纳为账户面板的添加/编辑表单
  (9 输入位 + 密码掩码 + 收录勾选原样沿用)。旧窗死面一并删:`UiShared.ctp_window`
  状态位与 `ctp_window_open/set_ctp_window_open`、`render::MENU_CTP`、
  `render::BTN_CTP_CONNECT`(不留"供旧测试引用"的死常量)。⚠️ 公共 API 减项。
- **`UiCmd` 三处形制变更**:⚠️ 破坏性(公共枚举):`Start { factory, name }`(新增 `name`,
  空 = 旧行为)、`CtpConfigure { gateway, form }`(原单元变体)、
  `CtpSubscribe { gateway, symbols }`(新增 `gateway`)。服务名随网关走
  `{gw}.configure` / `{gw}.subscribe`,不再硬编码 `ctp`。
- **`parse_gateway(raw, fallback)`**:空位不再兜底成字面量 `"ctp"`,改落显式阶梯
  `default_gateway()`(上次选择 → 首个已装配 → 首个);无账户 = `Err` 且「委托」钮禁用。
- **新账户清册面**:`AccountRow`(派生名 + broker + 账号 + 在场位 + 元账号标记 + 表单留档)
  与 `UiShared` 的 `accounts/account/add_account/update_account_form/remove_account/
  sync_accounts_connected/md_source/accounts_locked/set_md_source/subscriptions/
  note_subscriptions/default_gateway`。派生名唯一入口 = `ek_ctp::derived_gateway_name`。
- **单源纪律(§5.C)**:添加时定源 —— 无源且无在场账户则新账户自动成为行情元账号
  (md 前置保留),否则按纯 TD 装配(md 前置清空);订阅两入口一律经 `subscribe_target`
  路由到元账号,未指定明确报错。元账号新装配后自动重放订阅集。
- **元账号连接后锁定(用户裁定)**:任何账户已装配即拒切换(`set_md_source` 回 Err)。
- **移除守卫**:有活跃委托不给移除钮(提示"先撤单才能移除");有持仓可移除但警告;
  两步确认沿用 `confirm_step`。
- **撤单随单走**:行内钮 / 全撤 / 行双击三处都跟该单的 `gateway_name`,空串落阶梯;
  持仓行双击把 `gateway_name` 带入下单面板(旧实现不带,平哪条腿由上一单残留决定)。
- **行情带列名**「接口」→「行情源」(公共数据只来自元账号,身份列说清)。
- 测试:s13 重写、s17/s18/s20 改、新增 s28(锁定 + 移除守卫);五张快照重基线
  (s3/s10/s16/s21/s27);`examples/demo_tour.rs` 改走账户面板流程。

## 0.2.13(2026-10-20;时间列/横轴北京钟面;可独立发布)

- **`fmt_dt` 由 UTC 直显改为北京时间 +08**(表内时间列统一口径):数据面
  时间戳统一存 UTC 纳秒 —— CTP 回报的自然日本就是北京钟面,展示面换回
  +08,交易员看到的不再差 8 小时(旧实现会把 09:30:01 的成交显示成 01:30:01,
  夜盘跨日更乱)。换算用固定偏移,不依赖本机时区设置。
- **K线窗横轴同口径**:egui_charts 0.2 的 `Bar` 只吃 `DateTime<Utc>` 且按
  UTC 钟面画刻度 —— 用「换到 +08 再标回 UTC」的重标定喂柱,刻度显示北京
  钟面(只挪刻度不挪序)。
- 新增单测钉 `render::tests::fmt_dt_shows_beijing_wall_clock`(含跨日钉:
  北京 00:00:30 = UTC 前日 16:00:30,旧实现会把这根显示成 16:00:30)。
- 依赖零改动(ek-ctp 0.6.0 照常工作);随 ek-ctp 0.7.0 的整链抬版另起
  0.2.14(见下条)。

## 0.2.14(2026-10-20;随 ek-ctp 0.7.0 整链抬版;装配代码零改动)

- **依赖:`ek-ctp` 0.6.0 → 0.7.0、`ek-oms` 0.3.2 → 0.3.3、`ek-station`
  0.1.13 → 0.1.14。** 本 crate 直接 ask 网关(0.2.12 口径),downcast 纪律与
  0.2.12 条目同 —— 三处必须同一份 ek-ctp。抬版动作在本次发布波次一并提交。
- **demo_tour 叙事对 0.7.0 校正**:步4.5a/4.5b 的「重传压在驱动缓存里、等合约
  齐才回放」改为「重传直接上账,收尾后账本只增不减」—— 缓存回放面已退役。

## 0.2.12(2026-10-20;网关「收录勾选」上界面;整链抬版随 ek-ctp 0.6.0)

- **网关窗新增「收录勾选」:期货 `'1'` / 期权 `'2'` 两个 checkbox**(ek-ctp 0.6.0
  D-PC-FILTER 的界面面)。缺省只勾期货 —— 与 0.2.11 的既有口径一致(网关只上抛期货)。
- **`CtpForm` 新增 `accept_futures` / `accept_options` + 手写 `Default`**:不能 derive,
  derive 会给 `accept_futures = false`,而两个都 false 在语义上是「什么都不收录」。
  `product_classes()` 把勾选译成 CTP 码表喂 `ConfigureReq.product_classes`;两个都不勾
  交空表,插件侧归一化一律回落默认期货(**空 ≠ 全丢**)。
- **勾选变更即回写 `shared`**(与 `ctp_edit` 同一口径):网关窗每帧从 `shared.ctp_form()`
  重读表单,不回写则这次点击下一帧就蒸发。
- **`ConfigureReply` 模式补 `..`**(0.6.0 起带 `product_classes` = 实际生效值)。刻意
  **不**用它回填表单勾选:被拒时回带的是「网关当前仍在跑」的那一份,拿它覆盖用户刚点的
  勾选会把他的意图吃掉。
- **依赖整链抬版:`ek-ctp` 0.6.0 / `ek-oms` 0.3.2 / `ek-station` 0.1.13。** 本 crate 直接
  ask 网关(载荷类型来自 ek-ctp),网关却由 station 工厂装配 —— 三处必须链到同一份 ek-ctp。
  总线载荷是进程内 `Arc<dyn Any>` downcast,不是序列化:两份 ek-ctp 的同名类型
  `type_name` 字符串相同(`ask_service` 守卫放行)而 TypeId 不同(网关 `on_request` 的 arm
  downcast 失败、不回复、超时)。**编译期完全不报错,坏在运行时**;完整实测记录见
  ek-oms 0.3.2 / ek-station 0.1.13 条目。
- **新钉 `c3_ctp_form_product_classes_reach_gateway`**:缺省勾选回显 `收录产品类:1`,
  勾上期权后回显 `收录产品类:1 2`。这条同时是「进程里两份 ek-ctp」的哨兵 —— 不同源时
  日志出现的是 `ctp.configure 失败` 而不是「受理」。
- **破坏性:`CtpForm` 加两个公开字段** —— 用完整字面量构造 `CtpForm` 的调用方需补字段或
  改 `..Default::default()`。仓内三处已改(`examples/demo_tour.rs` 两处、
  `tests/ui_kittest.rs` 一处);`tests/ui_ctp.rs` 原本就写 `..CtpForm::default()`,未受影响。

## 0.2.11(2026-10-19;纯文档修正;crates.io + cnb tag v0.2.11)

- render.rs 两处模块注释回正(执行后自审抓出):运维窗描述仍写「流控查询」
  钮(该钮已随 0.2.10 sim 退场撤除)→ 改历史注。代码行为零变动。

## 0.2.10(2026-10-19;BD-UI-38 纸面网关退场;crates.io + cnb tag v0.2.10)

- **sim 纸面网关整体退场(用户裁定)**:纸面账户功能全撤,ctp/SimNow 两套前置
  (12 小时/7×24)满足需求;选前置探测用 ek-ctp `scripts/simnow-fronts.ps1 -Probe`。
  依赖 ek-paper 撤除;ek-station 依赖 bump 0.1.8→0.1.12(其工厂表已无 sim)。
- **界面清 sim**:接口下拉 ∪ 集改 {ctp};`parse_gateway` 空兜底 sim→ctp;
  运维窗「流控查询」钮撤(`UiCmd::QueryRate` 公共变体同步撤——API 减项);
  demo 窗改 dm/oms/ui(网关经运维窗 svctl 激活);demo_tour 纸面段整段删,
  实连段重编为步 5/6/7。
- **测试面**:ui_sim.rs 整删(9 钉);ui_converter_chain 网关位改测试本地
  "ctp" 受理夹具(受理形制忠实 ek-ctp,成交按 CTP 回报行直发注入;oms 管线
  钉数值零变);kittest/market 夹具标签 sim→ctp(委托号 SIM_*→CTP_*,
  行键 `ctp.*`);s17 断言「流控查询」改运维表头证。
- **快照重基线五张**(s3/s10/s16/s21/s27):钮撤除 + 夹具标签改名的像素变,
  `UPDATE_SNAPSHOTS=true` 重出;`.old.png` 备份即删。
- **kittest 环境稳健钉**:本机后装的 "GameViewer Virtual Display Adapter" 劫持
  wgpu 缺省择优选中(Dx12/DX11 纹理回读 BufferAsyncError,单测串行也复现;
  Vulkan/GL 实测好)——snap() 头部 Windows 缺省钉 `WGPU_BACKEND=VULKAN`
  (OnceLock 单次写,先于 LazyRenderer 建实例;用户已设则尊重)。
- 62 测试全绿(71−9);clippy -D warnings + doc 净。
- 0.2.9 及更早条目为史实保留(BD-UI-36 迁出记录不动)。

## 0.2.9(2026-10-19;BD-UI-36 纸面网关迁出;crates.io + cnb tag v0.2.9)

- **sim 纸面网关迁出到独立网关包 ek-paper 0.1.0(用户裁定)**:网关的【身份是
  网关】,对应 Python app/paperaccount(PAPER 网关,与 CTP 同级独立 app)——
  界面包不再藏网关。src/sim.rs 删除,语义零迁移(449↔449 行逐行比对,唯一
  差异 = Arc 写法面);注册名保持 "sim"(UI 下拉/demo/测试钉零涟漪)。
- 引用面改写:runtime 工厂/pick 表/QueryRate ask 类型/main demo/tests
  (ui_sim、ui_converter_chain)全部改引 ek_paper::;ui_kinds 与 pick_kinds
  撤自建 sim 项(防与 station 工厂双份)。
- 依赖:新增 ek-paper "0.1.0";ek-station 0.1.7→0.1.8(station 工厂表登记
  "sim" ——无头宿主可起纸面流,SimNow 登不上时切换的本地模拟环境)。
- 71 测试全绿零减钉(ui_sim 9 钉改 import 不改断言)。

## 0.2.8(2026-10-19;BD-UI-35 用户实看三报;crates.io + cnb tag v0.2.8)

- **行情带状态行撤除(用户裁定「tick N 格 / 最后更新…早已去掉,你又加回」):**
  考证:该状态行 0.2.0~0.2.7 从未删过(0.2.0 撤的是「行情」题字)。本轮按裁定
  删除,s8 改反向钉(此文字不得回归)。
- **表头上方缝隙硬根因(egui 源码实锤):** `Visuals::clip_rect_margin` 默认
  3.0(style.rs:1435),ScrollArea 内容裁剪 = inner_rect 向外扩该余量
  (scroll_area.rs:771-772)——覆盖带旧版从 inner_rect 顶边起画,滚动行从带
  上方 3px 余量露出。修 = 带向上/左右扩 clip_rect_margin,向下盖表头格高 +
  行间距;clip 同步为带矩形。新钉 s27:滚轮下滚委托表后出快照
  s27_scrolled_orders(像素扫描口径:表头文字上方带 x450..790 内
  faint_bg(16,21,30) 条纹行数=0、无数据文字像素;rb0/rb1 已滚走、rb2 起)。
- **空态提示移入表格 body(用户裁定「应在表头下面,或干脆不显示」):**
  委托/未成交/成交/持仓/查询合约的提示 label 移进 grid 首格 = 表头下第一行;
  行情带空态(无表格)保留原样(有意例外)。
- **本轮自审抓出(未出闸即修):** 成交/持仓空态分支表头又凭记忆写错
  (「成交时间/来源」vs 真实「成交额/时间」)——委托/成交/持仓/合约四表列头
  全部提为函数内 const 单源(ORDER_HEADERS/TRADE_HEADERS/POS_HEADERS/
  CONTRACT_HEADERS),空/非空分支同源,杜绝手写第二套。
- **测试闸加固:** ①快照回读全局串行(SNAP_LOCK,std Mutex 零新依赖):本机多
  wgpu 设备并发 map_async 回读 BufferAsyncError(5 快照并发随机成组挂,
  串行后连续三轮 25/25 绿)。②s27 补 apply_cjk_fonts 闸(初版漏装字体,
  快照豆腐块)。
- 全组 71 绿;s3/s10/s16/s21/s27 快照按修复后重录。

## 0.2.7(2026-10-19;0.2.6 自审纠错 + 仓库卫生;crates.io + cnb tag v0.2.7)

- **纠错(0.2.6 自审抓出):** 查询合约窗空态分支表头误写另一套名
  (「代码/名称/交易所/乘数/最后价」),与该表真实列头(「选(代码)/交易所/
  名称/乘数/最小变动」)不一致——空表时表头与有数据时不同、且「最后价」
  根本不是本表列。修 = 表头提为函数内 const 单源,空/非空两分支同源。
- **仓库卫生:** 0.2.6 误提交 7 个二分实验裁切图(.audit_*.png)已删,
  gitignore 补 `.audit_*.png` 防再犯。
- README 补记 0.2.5/0.2.6 特性段(表头固定/四主题/K线最大化+ESC)——
  0.2.5 时漏记。
- 自审其余结论:0.2.6 的 8 个代码 hunk 全在预定区域;测试只增不删;
  行情带空态(非格表,外层 ScrollArea 保高)为有意例外,不改。

## 0.2.6(2026-10-19;表头固定三处返修,BD-UI-34 用户实看回报;crates.io + cnb tag v0.2.6)

- **竖线根因(用户报「资金/日志右边多一根竖线」):** 表头覆盖带旧版用
  `ui.put` 重画全部表头格——视口外格(持仓 16 列总宽撑到 x=1222)把覆盖带
  子 UI 的布局 min_rect 撑宽 → Frame 描边矩形跟着撑 → 格右边界画在行中段。
  二分实验实锤:关覆盖带线即消失;换空内容线跟内容宽走(1007)。修 =
  覆盖带改纯 `painter.text/rect_filled`(零布局副作用,越界文字由 clip 裁)。
  资金格同时恢复带填充 ScrollArea 的 group_frame(plain Frame 会收缩到内容
  宽,右边框又落行中段)。
- **表头缝露内容(用户报「滚动时表里的东西在表头缝里露一点点」):** 覆盖带
  旧高 = 表头格高,未盖 grid 行间距带(item_spacing 3px)→ 滚动行从缝里露。
  修 = 带高 = 表头格高 + 行间距。
- **空表缩格(用户报「未成交没东西,表格缩得很小」):** 委托/成交/持仓/查询
  合约空态旧版提前 return,pinned_table 未调用。修 = 空态照常走 pinned_table
  (表头 + 吃满滚动区在场,只缺数据行)。新钉 s26。
- 快照按修复后重录;s21 全量表快照即「竖线回归」的像素闸。全组 70 绿。

## 0.2.5(2026-10-19;表头固定 + 四套主题 + K线最大化/ESC,BD-UI-34;crates.io + cnb tag v0.2.5)

- **表头固定(`pinned_table`,五张表全上:行情带/委托/成交/持仓/查询合约):**
  官方 DIY 模式(scroll_area.rs:412「painting a sticky header on top of
  it」)——单 grid 实例保证列对齐,ScrollArea 之后用本拍捕获的表头格矩形在
  滚动区顶部重画表头带(背景盖行 + Label 重画;格矩形已含滚动平移 → 横向
  自动跟随、纵向永远盖顶、零拍滞后;覆盖带只画无交互 Label → 点击穿透)。
  两轮机制实锤排除歧义:vertical 嵌 horizontal 高度塌缩(s3 底行钉实测
  462<500)、同 id 双 grid State 互写 repaint 环(kittest max_steps 爆)。
- **四套主题(用户问「PySide6 式可换的金融向主题」):** 金融深色(默认,
  深海军蓝交易终端观感)/egui深色/egui浅色/Qt灰白(PySide6 Fusion 迁移
  用)。菜单钮一键循环(单层无下拉);运行期态(AtomicU8),无配置文件。
  语义色(多红/空绿/隔离红/选中蓝)跨主题恒定——金融语义不随主题漂移。
  现状说明:此前 = egui 默认深色骨架 + 自配语义色,并非全默认。
- **K线窗:** 打开即最大化(ViewportBuilder::with_maximized)+ ESC 键快捷
  关闭(body 请求关闭 → 同一 close_kline 链;键按子窗焦点路由,主窗不误关)。
- **新钉:** r9 主题循环/默认(ui_state);s25 ESC 关 K线(Embedded 面注入
  Event::Key)+ builder.maximized=Some(true)(Immediate 面 recording
  renderer)。快照按新默认主题重录。全组 69 绿。
- **内部:** show_independent_window 几何参数收敛 WinGeom(clippy
  too_many_arguments);行情带外层 ScrollArea 保留(resizable 面板吃满前提,
  s3 实锤撤掉即塌)。

## 0.2.4(2026-10-19;四窗一型独立化 + 合约表吃满窗口,BD-UI-33;crates.io + cnb tag v0.2.4)

- **运维窗 + CTP 网关窗转独立 OS 窗(用户裁定「四窗皆可脱离主窗」):** 与
  查询合约/K线同形态——原生最小化/最大化/关闭、可拖出主窗放屏幕任意处、
  独立任务栏;Embedded 回退(kittest/WEB)维持主窗内弹窗同本体,测试面零改动。
- **查询合约表吃满窗口(用户实看指出):** 撤 `.max_height(260)`(内嵌弹窗
  时代残留——OS 窗里表格定高 260px,放大窗口表格不跟着长);动作行(已选
  计数 + 订阅钮)移到表格前,表格 ScrollArea 吃满剩余窗高:放大窗口 = 表格
  变高,行溢出才出滚动条。
- **四窗一型(`show_independent_window` 助手):** 守卫(0.2.3 教训)内建于
  调用方模式——位关不 show = GC 销毁;Embedded 回退返回关闭态;Immediate
  面 close_requested 返回关闭态。运维/CTP/查询合约/K线四个调用点同构,
  消除四份重复的 viewport 样板。
- 全组 67 绿 + clippy/doc/fmt 净;快照零变化(回退面几何保持)。

## 0.2.3(2026-10-19;查询合约窗「关不掉」根因修复,BD-UI-32 勘误;crates.io + cnb tag v0.2.3)

- **症状(用户实看):** 独立查询合约窗点 X 无反应,永远关不掉(K 线窗正常)。
- **根因(定位经源码实证,非猜测):** egui 的子 viewport 销毁判据 = 本拍未
  used(context.rs end_pass「never used this pass」)。0.2.2 的调用点没按
  `contracts_window_open()` 守卫——`show_viewport_immediate` 每拍无条件调用
  = 每拍 used = egui/eframe 永不销毁 OS 窗;cb 虽正确翻负状态位,下一拍又被
  show 回来(等效 CancelClose)。附带病灶:窗在主窗一开就自己出现(不管状态位)。
  K 线窗无此病(按 kline_symbols 列表循环,出列表 = 不再 show = 销毁)。
- **修复:** 查询合约窗整段 show 包进状态位守卫;位关 → 不 show → GC 销毁。
- **测试勘误(为什么 21 绿没拦住):** kittest 走 Embedded 回退,`.open` 旗标
  本就正确——无覆盖的恰是 Immediate 真窗面。新钉 s24:注入 recording
  renderer(set_immediate_viewport_renderer,模拟多窗后端)+ embed=false,
  三拍增量断言(位关零 show / 位开有 show / 翻负零 show)——该钉打在 0.2.2
  代码上必红。
- **审计勘误:** 0.2.2 复审核穿了 eframe/egui 机制链但漏核自家调用点的守卫
  ——机制正确 ≠ 用法正确;复审清单补一条「新机制的每个调用点按状态位辞行」。

## 0.2.2(2026-10-19;字号与独立窗轮,BD-UI-32;crates.io + cnb tag v0.2.2)

- **全局字号上调(`apply_ui_style` 新口,用户实看裁定):** body/button 16px、
  small 14px、heading 22px、monospace 15px(egui 缺省 13/12/18/12),
  render_body 顶部统一施加,真实 App 与 kittest 同源。
- **查询合约窗 + K线窗 = 独立 OS 窗(用户裁定,Python 独立窗形态):**
  `show_viewport_immediate`(eframe immediate viewport)——原生最小化/最大化/
  关闭钮自带、可拖出主窗放屏幕任意处、独立任务栏;OS 关闭钮 = 出表
  (close_requested → 状态位翻负,父窗 ≤1s 辞行销毁)。后端不支持多窗
  (kittest/WEB)时 egui 自动回退 Embedded = 主窗内弹窗,同一本体——
  kittest 断言面零改动。运维窗/CTP 窗维持主窗内弹窗(用户未点)。
- 快照重基线(字号);全组 66 绿 + clippy/doc 净。

## 0.2.1(2026-10-19;行交互归行扩展,BD-UI-31 续;crates.io + cnb tag v0.2.1)

- **动作归行不归列(用户裁定):** 委托/未成交/成交/持仓/查询合约窗/行情带 ——
  行内**任何一格**单击都触发该行动作(选中/带入/勾选联动),双击同理
  (撤单/带入/K线);标签外观不变(`click_cell` 在标签矩形上叠点击感应)。
- **行情带盘口四列带方向(`import_tick` 新口):** 买1价/买1量 → 方向卖,
  卖1价/卖1量 → 方向买(吃对手挂价口径);两个价格列顺带把该价填进价格格;
  其余列只带入合约+交易所,方向/价格保持表单默认(用户裁定)。
- 新钉 r8(import_tick 逐列口径五段断言);全组 66 绿 + clippy 净;快照不变
  (外观未动)。

## 0.2.0(2026-10-19;界面体验轮,BD-UI-31;crates.io + cnb tag v0.2.0)

- **菜单一键直达(R1):** 「运维/网关/查询合约」从 menu_button→下拉→入口钮
  两步,改为直接按钮单击弹窗(去下拉层);「帮助」保留菜单(多行内容)。
  **破坏性:** pub 常量 `BTN_OPEN_OPS/BTN_OPEN_CTP/BTN_OPEN_CONTRACTS` 删除
  (入口钮标签 = 菜单名本身)。
- **成交表方向上色(R2):** 方向列空绿/多红,与委托/持仓表同口径。
- **表格行选中 + 悬停跳色(R3):** 委托/未成交/成交/持仓/查询合约窗行首格
  单击 = 选中行(蓝调跳底,每表一行,换行即换选,`UiShared::row_selected/
  select_row` 新口);行情带选中行 = 下单面板当前合约(带入即跳色,不另存态);
  全表悬停 = 淡灰底(`paint_row_bg` 行格并集半透明叠底)。行首格既有双击
  动作(撤单/带入/K线)保留——双击先经 clicked,选中顺带发生,无冲突。
- **标题文字撤除(R4):** 「行情带(唯一源 = eTick. 订阅流)」「下单面板」
  「盘口(合约)」三处题字整行删(带位/侧栏即身份,Python 无题同口径;
  盘口括号合约代码一并删——选中合约由下单面板代码格与行情带跳色行指认)。
- 新增 kittest s23(行选中钉:委托换选 + 成交独立槽);s17 改单击直达形态;
  快照重基线(菜单/去题)。全组 65 绿 + clippy 净。

## 0.1.1(2026-10-19;OMS 中枢链薄下单者,BD-UI-30;crates.io + cnb tag v0.1.1)

- **依赖 bump(强制):** `ek-oms 0.2.1 → 0.3.1`、`ek-station 0.1.5 → 0.1.7`
  (station 工厂已装配 ek-risk 三闸——面板与 headless 同一装配口径)。
- **薄下单者(BD-UI-30,核心):** 下单/撤单直发 `{gw}.send_order` /
  `{gw}.cancel_order` 旁路口**删除**——`UiCmd::SendOrder` 走
  `oms.submit_order` 五段管线(拆单/风控/逐腿发送/记账,parent 聚合逐腿显形
  进日志),`UiCmd::CancelOrder` 走 `oms.cancel_order`(守卫 4000 不可绕过;
  全撤 = 界面侧逐个扇出到同一守卫口)。symbol/exchange 不再随撤单令携带
  (oms 订单缓存解)。
- **「自动开平」勾选(OrderForm.auto_split,默认开):** 开 = `LockMode::Auto`
  走拆单器;关 = `LockMode::None` 原样旁路(逐请求;守卫不可旁)。
  kittest s22 钉勾选双向翻转 → mode 逐位。
- **ui_converter_chain 升格:** fixture 退役(dev-dep ek-converter 删除)——
  真管线链(sim+dm+oms 真装配):Auto 平 3 拆 2 腿今先 → 冻结钉 → tick 撮合
  → 账本清零 + parent reference 钉;None 单腿旁路钉;ek-risk 手数闸 parent 级
  零发送链钉。顺带实锤忠实初始化口径(无成交史首轮 ePositionDone. 只记时刻,
  第二轮 ≥3s 才置位,原稿 1282-1285)。
- **ui_ctp_live 第 13 步:** 管线平仓钉(有仓则 CLOSE 经管线 = CLOSETODAY
  实连证据;7×24 快照市无成交 = OBSERVE 如实记档)。实连 13 步全收场
  (撤单守卫面 110s PASS)。
- 快照四张更新(下单面板多勾选);ui_sim 断言随日志新形(parent 聚合)回正。

## 0.1.0(已发布 crates.io 2026-10-18;布局 v5 面板化重构 + T8 集成钉)

- **converter 全链合体钉(T8,`tests/ui_converter_chain.rs`,默认门禁零网络)**:
  dev-dep ek-converter 0.1.1(装版引用);真宿主 sim+dm+oms+offset 夹具(N2 形态)
  ——账本初始化(空 5=今 2 昨 3)→ Auto 平 3 → SHFE 今昨拆 2 腿今先
  (CLOSETODAY×2 → CLOSEYESTERDAY×1)→ 两腿入 sim+oms → tick 撮合 ALLTRADED
  traded 2/1 → 账本回流 pos=2 yd=2 td=0 冻结清零。运行时语义零改动(converter
  以测试夹具进链,接线时机属设计裁定)。顺带实锤 `en_symbol` 口径 = 交易所
  前缀在前(`SHFE.sn2709`,vt_symbol 真源)。
- **面板化重构(BD-UI-29,render_body 改收 `&egui::Context`)**:实窗 1280
  演示暴露:右列裁切(定宽 250+140+480+480 ≈ 1438 > 1280 且无横向滚动 →
  裁掉不可达)、底部死区 ~300px(定高 250 实渲 ~85px)、无 splitter 语义。
  官方面板组合落地:TopBottomPanel(菜单)→ TopBottomPanel(行情带 240,
  resizable = Python 分带 splitter)→ SidePanel::left(下单面板 + 盘口纵排,
  250, resizable)→ CentralPanel(2×2 监控格,最后)→ 弹窗(面板之后)。
- **机制钉(dbg 实锤)**:horizontal 行成员可用高度初始 = interact_size,高度
  分配不沿横向传播 → 格内定高 max_height 失效;修复 = `allocate_exact_size`
  预留 (可用-间距)/2 矩形 + `new_child` 内开 Frame+ScrollArea(`add_sized`
  收 impl Widget 不适用,已试撤)。
- **BD-UI-28⑦「下单面板帧不动」被取代**:侧栏内容天然纵向,err label 改由
  ScrollArea 吸收 sizing 退让(BD-UI-27 口径不变)。
- **测试面**:s3 加结构钉(右列贴边不裁切/底行沉底无死区/侧栏在左/两列等宽
  随窗分配);kittest 建构 `build_ui` → `build`(ctx 级);快照 s3/s10/s16/s21
  重基线。kittest 19 钉 + 全量 60 例绿(例数不变)。

## 0.1.0(进行中;第五轮审计修正 2026-10-18)

- **回车订阅真缺陷修复(BD-UI-28①)**:原 `has_focus + key_pressed(Enter)`
  口径下回车订阅**实窗从不触发**(egui 单行 TextEdit 遇 Enter 即
  surrender_focus,检查时 has_focus 已 false)→ 改规范口径 `lost_focus() +
  key_pressed`;回车输入带 `EX.` 前缀剥成裸码并回写表单(与查询合约窗同
  口径)。新 s20 钉(focus + type_text + key_press 全链实测;空代码回车不入列
  守卫同钉)。
- **悬空面清除(BD-UI-28②)**:`selected_symbol` 字段 + 两访问器删除(四处写
  零读);行情带行单击 = 带入下单面板(方案「行单击 = 选中合约」落地,s8 钉
  扩展);委托行/持仓行单击无动作(Python 同口径)。
- **委托行双击 = 撤单(BD-UI-28③)**:Python OrderMonitor `itemDoubleClicked→
  cancel_order` 等价(活动行限定,s11 钉扩展;原双击弹 K线为偏离已撤,K线
  入口唯一在行情带双击)。
- **表列补全(BD-UI-28④,Python 蓝图逐值抄录)**:委托表 13→17 列(+名称/
  交易所/类型/来源)、成交表 10→12(+名称/交易所)、持仓表 11→16(+名称/
  交易所/在途/市值/成本);数据字段全在场,零新增依赖。
- **tick_of 交易所感知(BD-UI-28⑤)**:交易所非空 = EX.symbol 精确匹配
  (跨所同码不串);下单面板名称/随价/盘口三消费点同步接线。
- **测试面**:kittest 17→19 钉(s20 新增;s11 双击钉用 `with_step_dt(0.1)`
  建构 —— kittest 每事件一帧 + 缺省 step_dt 0.25s 会把两击拉到 0.5s,超 egui
  双击时限 0.3s,BD-UI-28⑥;s21 满表视觉钉)。全量 60 例绿。
- **Frame 布局泄漏修复(BD-UI-28⑦,s21 显形)**:`Frame::show` 继承
  `ui.horizontal` 横向布局 → 2×2 格内 tab 行/表题/宽网格横流挤同一行(空表
  期隐形)→ `group_frame` 内容强制 `ui.vertical`;委托表表题撤除(tab 即表
  身份,「委托」三面降两面,s12 口径同步);快照基线 s3/s10/s16/s21 重生成。

## 0.1.0(进行中;v4 带框架 + 订阅两入口 2026-10-18,布局重做定稿)

- **v4 带框架(BD-UI-27,render.rs 重写)**:菜单栏(运维|网关|查询合约|帮助)→
  行情带(全宽,tick 唯一源,交易所二级 tab,双击行弹 K线窗)→ 交易带(左下单面板
  9 字段 + 委托/全撤钮 + 盘口独立帧;右 2×2 监控格「委托|未成交」「成交」
  「持仓」「资金|日志」)→ 弹窗面(egui::Window:K线/运维/CTP/查询合约)。
  默认窗 1100×720 → **1280×860**。
- **删除线(界面面退役,带测试迁移)**:TicketForm/parse_ticket/ticket_form
  (票据文本框;OrderForm 成唯一下单面,ui_sim 票据 4 断言迁 ui_state r7,
  ui_ctp_live 下单同步迁移);回灌表单界面面(render_reload_form + reload_form
  状态 + s9 钉;**UiCmd::Reload + 插件处理 + ui_market 闭环钉保留**);
  CTP 窗「订阅标的」输入位 + 「订阅行情」钮上界面撤除(CtpForm.symbols 字段
  保留 = state/ask 载荷面)。
- **订阅两入口(用户裁定「订阅不在网关窗定死」)**:① 下单面板代码框回车 →
  `UiCmd::CtpSubscribe`(bare symbol,Python returnPressed→set_en_symbol 等价);
  ② 菜单「查询合约」弹窗(Python ContractManager 等价:查询框「代码或交易所,
  留空 = 全部」+ 勾选 + 「订阅选中」钮;未勾选 = 不发 + 日志说明)。
- **合约数据流(plugin.rs routes += `eContract.`/`eContractFinish`)**:
  ek-ctp 登录后自动 reqQryInstrument,UI 只收不催(无催查询总线口);
  `UiShared.contract_store`(键 EX.symbol)+ 完成标记落日志「合约流完成」。
- **实码布局钉(实窗截图审出,egui 0.33 帧内 sizing 退让)**:下单表单 = 帧内
  网格(委托/全撤/提示语都是网格行,网格后不挂面板内容 —— 否则后置行叠回网格);
  盘口独立帧(140px 列);交易带整体包 ScrollArea(小窗可达,D7 口径延续)。
- **测试面**:kittest 17 钉(新 s17 菜单开窗 / s18 查询合约勾选订阅 / s19 全撤
  扇出;s9 随回灌界面退役删除;s12 点击帧改 `harness.step()` 单帧钉红字;
  建构面统一 `harness_for()` 1280×860);ui_state 11 钉(新 r9 合约库查询勾选 /
  r10 窗位 + K线多开);ui_sim 9 钉(新 u8 tick_store 上屏 / u9 contract_store
  上屏;u4 票据钉随退役删)。五闸(fmt/clippy/doc/test/release)全绿。

## 0.1.0(进行中;CJK 字体回退 2026-10-18,乱码根因修复)

- **BD-UI-26 CJK 字体回退(`src/fonts.rs`)**:epaint 0.33 缺省字体组(Hack /
  Ubuntu-Light / NotoEmoji / emoji-icon)**无 CJK 字形** → 界面中文渲染成
  豆腐块(用户实窗报障)。启动时按候选表从系统字体目录装载一枚 CJK 字体
  (simhei → simkai → simfang → simsunb → Noto VF → Linux unifont/droid →
  macOS Arial Unicode;**只收纯 .ttf/.otf** —— ab_glyph 不吃 .ttc 集合且
  epaint 解析失败即 panic,复审拆掉的雷),追加到 Proportional/Monospace
  两族末位作回退。两处 run_native 取帧闭包接线(runtime + pick 窗)。
  装载证据行 stderr 可 grep:`[ek-ui] CJK 回退字体已装载: <path>`。
- 验收三钉:`fonts::f0`(字形 API:22 常用汉字 has_glyph)、`s16_cjk_font`
  快照(汉字密集页 baseline,豆腐块再现即 diff 爆红)、s3/s10 快照重生成
  (与实窗同字体面)。

## 0.1.0(进行中;sim 流控拒绝面 2026-10-17,T7 注错)

- **sim 流控超限拒绝(D4)**:`RateWindow::hit` 超限返回 false,订阅/发单/撤单
  三口第 7 笔(1s 窗)被拒显形(`流控被拒:1s 窗内请求已达上限 6`),被拒笔不进
  oms。原面只计数不拒绝,与 CTP 流控语义差一档。新测试 u7(6 收 1 拒)。

## 0.1.0(进行中;票据网关选择子 2026-10-17,集成测试战役 T5 前置)

- **票据网关选择子(BD-UI-25)**:票据新增「网关」位(空 = `sim` 纸面,现行为
  不变;填 `ctp` = ask `ctp.send_order`/`ctp.cancel_order` 真网关)。
  `UiCmd::SendOrder/CancelOrder` 随单携 `gateway`;行级撤单钮跟随委托来路
  `gateway_name`(空兜底 sim)。`parse_gateway`(空 → sim;含空白 → 红字不入列)。
- 新增 `tests/ui_ctp_live.rs`(native 门 + `#[ignore]`):**无头界面控制**的
  CTP 实连 12 步全流(手工 env 注入账号 → configure → 启动链六日志 → 合约 →
  订阅 → 行情上屏 → 发单/挂单撤单/拒单 → 资金/持仓 → 错密码 → 改密重连 →
  收场),与真实窗口同一 UiShared/UiCmd/ask 链路。

## 0.1.0(进行中;CTP 手工配置面 2026-10-17,集成测试战役 T0)

- **CTP 网关段(界面手工配置面)**:渲染新增「CTP 网关」——9 输入位(broker/td
  前置/用户/密码/AppID/AuthCode/md 前置/用户/密码;密码与 AuthCode 掩码不回显)
  + `连接 CTP` 钮 + 订阅标的位 + `订阅行情` 钮。值全部人敲(界面零配置口径的
  正向面:配置归插件自身),经 `UiCmd::CtpConfigure/CtpSubscribe` → ask
  `ctp.configure` / `ctp.subscribe`(ek-ctp 0.3.0 插件自带配置口)。
- **资金小节**:交易监控页加「资金」表(ask `oms.get_all_accounts`;CTP 登录后
  eAccount 入账即上屏;空帧占位「等网关 eAccount.」)。
- 装版升 `ek-ctp 0.3.0` / `ek-station 0.1.3`;新增 feature `ctp-native =
  ["ek-station/ctp-native"]`(默认关,日常构建不碰 C++;测试战役实连时开)。
- 测试 42 → 47:`tests/ui_ctp.rs` c0–c2(configure 全链/守卫日志/服务目录 5 口/
  订阅受理/ctp 不在场失败不炸链)+ kittest s13(表单入列逐位 + 空订阅拦截)
  + s14(资金行读数 + 可用 = balance−frozen)。

## 0.1.0(进行中;配置面撤销 2026-10-17 用户定盘)

- **零配置口径落地**:ek-ui 不读任何配置文件。整块删除 `cfg.rs`(toml-lite/
  UiConfig/locate_setting/redact)、`CfgStatus` 状态条(state/render)、
  `--config` 形态与发现链(main.rs);bin 形态收敛为 `(无参)demo / --pick /
  --from [--ready-file]`。测试 51 → 41(cfg 9 例 + s7 1 例随撤)。
  需配置的插件(唯一 = ek-ctp 虚拟账号)配置面归插件自身,激活时在插件内完成;
  **setting/ 目录整树清理**(样例与凭据档全删,工作区不留共享配置文件;
  Python 侧 enkeep_trade_setting 原档只读未动)。详见 DESIGN「配置面撤销记录」。

## 0.1.0(进行中;审计修正 2026-10-17)

- `sim` 撮合两守卫(DESIGN BD-UI-22 补钉):**tick/bar 双重计价防护**
  (tick 成交挂账,bar 收口预算扣回 —— 同一笔市场量只吃一次)+ **bar 单调
  守卫**(重复/重放帧不重复撮合);ask 臂锁面开块(锁内造单锁外上总线)。
  测试 +2(u5/u6)。
- 装版升 `ek-station = "0.1.2"`(pickfile BOM 剥离;Notepad 系清单不再拒启)。

## 0.1.0(进行中;S5)

- **依赖面**:+`ek-ctp = "0.2.0"`(默认特性,native 关;取四口契约类型)+
  `ek-oms = "0.2.1"`(取 oms 查询 Req 类型)。
- `sim` 新模块:`SimGatewayPlugin`(注册名 "sim")——四口 ask 与 ek-ctp 同构
  (`sim.subscribe/send_order/cancel_order` 直接复用 ek-ctp 的 Req/Reply 型;
  第四口 `sim.query_rate` 自定流控查询:1s 窗 used/limit=6/remaining);
  `SimMatcher` 纸面撮合(纯逻辑零总线):限价买 bar.low≤价/卖 bar.high≥价
  成交于委托价,市价成交于 close/last;**手数排队** = 每 bar 的 volume 是本拍
  成交预算,挂单 FIFO 吃预算,吃满排队到下一拍;成交产出 eOrder./eTrade./
  ePosition. 三件套上总线(锁内撮合锁外 put,与 DmPlugin 同形);拒单口径
  对齐 ek-ctp(开平 NONE/类型不支持/量非正)。
- `state`:`UiCmd::SendOrder/CancelOrder/QueryRate`;`TicketForm`/`parse_ticket`
  (中文向 多/空/开/平/市价 可写;exchange v1 固定 SHFE);`TradeSnap`
  (委托/成交/持仓三表帧)。
- `plugin`:refresh 尾段 ask `oms.get_all_orders/get_all_trades/get_all_positions`
  组 TradeSnap(oms 不在 → 空帧不抛错);run_cmds 增三口(回执 LogRing)。
- `runtime`/`launcher_ui`/`main`:工厂表 + pick 表 + demo 缺省均含 sim
  (demo 窗 = dm/oms/sim/ui,开窗即见交易监控闭环演示)。
- `render`:+交易监控段(委托表带行级撤单钮[仅 ACTIVE_STATUSES 行]/成交表/
  持仓表)+ 下单票据 + 流控查询钮;解析错红字 + 日志双面(同回灌)。
- 测试:`tests/ui_sim.rs` 5 例(u0 撮合金样本 FIFO/预算/双向/市价/撤单/拒单;
  u1 全链闭环 eTick→DM→eBar→Sim→eOrder/eTrade/ePosition→OMS→监控页帧逐位;
  u2 撤单闭环;u3 流控计数;u4 票据解析);`ui_kittest` +s11(监控页读数逐位 +
  撤单钮入列)/s12(票据提交/坏单不入列)。**kittest 点击纪律改 `click_accesskit`**
  (坐标点击对折叠线下节点无效;accesskit 动作可达不可见控件)。
- 快照基线重生成(s3/s10:S5 版面变化)。

## 0.1.0(进行中;S4)

- **依赖面**:`ek-dm = "0.1.0"`、`egui-charts = "0.2.0"`、`chrono = "0.4"`
  (BD-UI-7 四件套:eframe/egui 0.33.3 + egui-charts 0.2.0 + egui_kittest
  0.33.3 全锁;图表层薄封装入口在本 crate)。改 K线渲染面无需直改
  `ek-dm`(转换在手:ek-data BarData → egui_charts Bar)。
- `state`:`MarketStore`(eBar./eBarUpdate. 上屏地面;(symbol,interval) 格
   缓存 4096 丢头保尾;帧/更新/回灌三计数 —— 刷新不丢帧统计可见)、
  `UiCmd::Reload`(dm.reload 回灌指令)、`ReloadForm`/`parse_reload_csv`
  (行 `dt,o,h,l,c,v`;dt epoch 秒;错行报行号)、`CellView`。
- `plugin`:routes + `eBar`./`eBarUpdate.`;`run_cmds` 增 reload 分支
  (ask `dm.reload`,push=true,回执 LogRing,DM-US)。
- `runtime`:`ui_kinds` 收 `dm_rt` — **UI 宿主 dm 工厂 real_time=true**
  (替换 ek-station 缺省入口;headless 仍旧 rt-off 两宿主互不波及);
  `RunOpts.dm_rt`。
- `render`:+行情板(金样本数值逐位)/K线页 v1(egui-charts collapsible
  首格定点,tracking 开)/回灌窗(表单 + 提交 + 错 CSV 红字即 Logan 环)。
- `main`:`[<mode>.dm] real_time` 覆写入 RunOpts。
- 测试累计 42:`ui_market` +2(金样本回放逐位对 + 回灌端到端)、
  `ui_kittest` +4(s8 金样本行、s9 提交/坏 CSV 红字双面,s10 K线帧快照,
  快照并发互斥钉)。

## 0.1.0(S3)

- **依赖面**:`ek-station = "0.1.1"`(pickfile 抽世落地,`pick.rs` 变薄转发
  —— BD-UI-15 统一兑现)。
- `cfg`:**toml-lite** 自写解析(零依赖;Str/Int/Bool/List;段头/重名/坏行号;
  引号内 `#` 与行尾注释并存),`UiConfig`(mode + `[<mode>.station] plugins`
  + per-plugin 子树)、`locate_setting`(env EK_STATION_SETTING → cwd →
  cwd 祖先链找 `setting/global.toml`)、`redact/redact_line`(秘密键替 ***)。
- bin:`--config <global.toml>` 形态;优先级 `--from` > `--config` > 发现 > demo;
  预启动校验(未知插件/空清单/mode 值外/解析/IO)= fatal → exit 1 拒启动
  (BD「错配置拒绝启动」);缺 station 段 = Warn 状态条即 UI 红字(不进总线)。
- `state`:`CfgStatus{Ok/Warn}` + `cfg_status`(UI 面 only);`render` 顶部
  配置状态条(绿/红字)。
- 测试累计 37:`cfg` +9(round-trip 真形样例含引号内 #/坏行号/subtree/脱敏/
  校验三 fatal/缺段 warn/发现链/IO fatal)、`ui_kittest` +1(s7 状态条绿/红/无)。

## 0.1.0(S2)

- `state`:`UiCmd{Start/Remove}`(窗口面→总线面 指令队列)、`pending_confirm` +
  `confirm_step/confirm_cancel/confirm_cancel`(二步确认 Armed/Fired/Switched)、
  `is_quarantined`(状态原文快照扫描,BD-UI-17)。
- `plugin`:eTimer 先辞行 UiCmd 再拉快照;svctl.start/remove 回执进日志
  (`[ops] ...` 可断言行)。
- `render`:插件表加操作列(重注入单步 / 移除二步确认)、待确认横幅 + 取消钮、
  依赖图文本段、隔离行红标(RED 状态/名)。
- 测试累计 27:`ui_ops` +5(指令队列/确认状态机/隔离判定 + 真链 摘除→重注入→
  ghost 被拒)、`ui_kittest` +3(s4 二步确认流程/s5 重注入直发/s6 取消+红标+依赖图);
  静态快照基线随布局更新(操作列入格)。

## 0.1.0(S1b)

- `runtime`:`run_station_ui` 增加 `ready: Option<&Path>`(start 成功即写
  `"ready\n"`,launcher 轮询面);bin 形态扩成 无参 / `--pick` / `--from [+--ready-file]`
  四型(未知参数 usage exit 2)。
- `pick`:行式清单读写(`#` 注释/空行/空白行拒;Empty/BadLine/Io 业务错)。
- `launch`:分离式 spawn(CREATE_NO_WINDOW + stderr 管道)→ wait_ready(50ms
  轮询,30s 缺省;超时 kill)/ Failure 分类 `Exited{code,stderr_tail}` /
  `NoReadySignal{timeout}`;成功路径 drop Handle 分离脱管(BD-UI-14)。
- `launcher_ui`:`render_pick`(纯渲染,回调注入)+ `PickApp`(UI 线程只画与
  翻位;worker 见 BD-UI-16)。
- `launcher`:`run_pick_launcher`(window join + 槽读取)/ `launch_worker`
  (写清单→spawn→wait_ready);pick 表 = 缺省工厂 + ui(BD-UI-15)。
- 测试累计 19:`ui_kittest` +4(S1b)/ `launch` +5(powershell 替身:成功标记/
  拒退+stderr 尾/超时 kill/清单读写规则)/ `pick_ui` +3(勾选翻位+摘要联动/
  启钮禁用-触发/取消钮)。

## 0.1.0(S1b)

- `state`:`UiShared` / `LogRing`(cap 512 + spilled)/ `BusSnap`。
- `plugin`:`UiPlugin`("ui")—— eLog 环形、ePlugUp/Down 记录、eTimer 1s 拉
  svctl.list + 服务目录组快照;纯观测零服务(BD-UI-13)。
- `render`:`render_body` 单渲染入口(插件表/服务目录/日志 + 清空日志就地按钮)。
- `runtime`:`ui_kinds`(缺省表 + ui 工厂,shared 同享)/ `spawn_host_watcher`
  (关窗协议)/ `run_station_ui`(boot→start→eframe 主循环→join 观察线程)。
- bin `ek-ui`:dm+oms+ui 直起窗口。
- 测试:L1 `ui_state`(6);L3 `ui_kittest`(节点断言/就地交互/静态快照);
  装配链 `ui_runtime`(eTimer 回路 + 关窗 close 链)。
- 依赖: ek-core 0.3.0 / ek-event 0.1.0 / ek-data 0.2.2 / ek-station 0.1.0(装版);
  eframe = egui = 0.33.3;egui_kittest 0.33.3(dev,snapshot+wgpu)。