# 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——已恢复并注释钉死。