按「现状架构记录」方法论产出:只记已实现的,不做目标设计、不打分。 每条事实标
OBSERVED(源码直接可证)/DOCUMENTED(有文档但未复核)/INFERRED(由证据推断)/UNKNOWN。 本文档是活的架构地图,架构改动后同步更新;DESIGN.md 是设计决策与阶段日志,两者分工不同。
- 快照身份:branch master;本文快照写于 2026-09-06(HEAD
2e90fe3前后),其后追加:目录迁移 + 进度条、runtime 守卫 + 单实例互斥、「停止并继续」、NSIS 重打(详见 git log)。行数/Message 数等精确数字以该日快照为准。 - 快照日期:2026-09-06(更新 2026-09-07)
- 规模:
src/共 8628 行 Rust;core/1255 行(复用自上一代);单 crate、单 exe。
| 项 | 事实 | 证据 |
|---|---|---|
| 语言/框架 | Rust 1.92 (edition 2024),iced 0.14(实际 0.14.2)+ wgpu,Elm 架构(State → view → Message → update) | Cargo.toml;main.rs:119-168 OBSERVED |
| 目标平台 | Windows 10/11 x64 only;无边框窗口(decorations: false)、DWM 圆角、DX12 后端 |
main.rs:130-157 OBSERVED |
| 分发 | 单 exe(+crt-static 零运行时依赖)+ NSIS 安装包(packaging/) | DESIGN.md §9 DOCUMENTED |
| 后端进程 | 由 core/procman.rs spawn 的 dsh 子进程,UI 本体 1 进程 |
src/core/procman.rs OBSERVED |
分层与依赖方向(grep 验证 2026-09-06):
main.rs ──► app.rs(State/Message) ◄── update.rs(update/subscription)
│ │ ▲ │
│ ▼ │ ├──► bridge.rs ──► core/(后端,零上层依赖)
└──► pages/(view/外壳)─► ui/(自造组件,不依赖 core)──► iced/iced_wgpu
theme.rs(令牌,叶子)
src/core/对ui/pages/app/bridge的依赖 = 0(grep 证实);唯一 "tauri" 命中是core/mod.rs:1的文档注释。src/ui/、src/theme.rs不依赖 core,是纯叶子层。- OBSERVED,
grep -rn "crate::ui|crate::pages|crate::app" src/core/与grep -rln "crate::core" src/ui/ src/theme.rs均为空。
| 文件 | 行数 | 职责 | 关键事实 |
|---|---|---|---|
main.rs |
209 | 入口、CLI 解析、窗口设置、DX12 限定、字体注入、PNG 写盘 | CLI:--theme/--shot/--after/--page/--switch-to/--switch-at/--tall/--drawlog/--autotest/--e2e/--all-backends OBSERVED |
app.rs |
337 | Dshnext 状态 + Message 枚举(约 60 个变体)+ 通用 helper |
状态分组:窗口/导航浮层/后端数据/环境页/插件页/控制台页/设置页 OBSERVED |
update.rs |
781 | 全部状态迁移(update + subscription),阶段 3 从 app.rs 拆出 |
每条 Message 打 debug 日志(Tick/ToastTick 除外)OBSERVED |
bridge.rs |
66 | core 异步世界 ↔ iced 的桥:OnceLock<EventSink> + Mutex<Option<EventStream>> + Arc<ProcMap> 全局持有 |
存在原因:Subscription::run builder 是裸 fn() 捕获不了状态;Task::perform future 必须 'static OBSERVED |
tests.rs |
325 | iced_test 14 例(view↔update 契约,不跑 Task) |
DOCUMENTED(DESIGN.md §9,cargo test 全绿) |
| 模块 | 行数 | 职责 |
|---|---|---|
event.rs |
83 | CoreEvent{EnvProgress/Log/Url/Exit} + EventSink(unbounded,日志洪峰不反压)+ channel() |
store.rs |
65 | config.json 读写、数据目录 %LOCALAPPDATA%\DshDesk\ |
profiles.rs |
186 | profile 增删改查、目录模板 |
envres.rs |
249 | 环境探测(--version ×3)、版本查询、PATH 拼装 |
installs.rs |
220 | Node 下载解压、dsh/pnpm 安装 |
procman.rs |
192 | spawn dsh(CREATE_NO_WINDOW)、日志转发、taskkill /T /F 杀树、ProcMap |
plugins.rs |
240 | 插件装卸、npm keywords:dsh-plugin 市场检索 |
platform.rs |
~110 | reg.exe 封装(自启/主题探测)、端口探测(Free/Http/Other)、系统名 |
diag.rs |
~150 | 脱敏诊断报告(Key 只出长度);导出到「文档」目录 |
selfupdate.rs |
~180 | 自更新:Releases 查版 → 下载 → SHA256(PowerShell)→ 换身 .old |
| 文件 | 行数 | 职责 | 关键事实 |
|---|---|---|---|
mod.rs |
61 | 文本纪律入口:txt/txt_bold/mono 全项目唯一文本通道(钉死内嵌字体 + Advanced shaping);字体字节 include |
OBSERVED |
theme.rs(在 src/) |
332 | 两套 Palette 令牌 + 字号/间距/圆角常量(FS_/GAP_/R_*) |
OBSERVED |
widgets.rs |
670 | 通用控件大杂烩:tag/dot/icon_badge/list_row/divider/empty/busy/field/kv/input/secret_input/check/dropdown/segmented/page_head/page_stack/slim_scrollbar/strip/Toast/toast_host/hoverable/ellipsize | 第一代散文件(sidebar/list_row/toast/tag/segmented)收编于此 OBSERVED |
card.rs |
181 | 卡片容器(card()/card_hero() 两档高度) |
OBSERVED |
button.rs |
255 | 六变体(primary/secondary/danger/teal/quiet-danger/ghost/accent)× 三尺寸 × 禁用态 | OBSERVED |
anim.rs |
118 | Tween/AnimState 补间驱动;动画期才订阅 frames(),空闲零出帧的基石 |
OBSERVED |
icon.rs |
— | 描边图标(assets/icons/*.svg,include_bytes!) |
OBSERVED |
titlebar.rs |
231 | 无边框窗口三件套 | TITLEBAR_H=40(拖动+三键细条,横跨主区)、BRAND_H=64(品牌头部单元,归侧边栏)、SIDEBAR_W=232、resize_grips(八向 6px 热区)OBSERVED |
modal.rs |
231 | 自造模态(stack + opaque 遮罩 + ESC 全局键盘订阅 + Dialog/Draft) | OBSERVED |
reveal.rs |
165 | 切页入场:with_translation 位移(1=刚切 0=落定),t=0 直通零开销 |
OBSERVED |
frosted.rs |
387 | 假高斯模糊玻璃卡:布局跟随内容的自定义 widget;glass 底 quad → 窗口锚定的光球+颗粒共享场(卡片=裁剪窗)→ 内容;滚动平移从 viewport.pos − MAIN_ORIGIN 反推;四处嵌套 with_layer 都传 bounds.intersection(viewport)(iced 嵌套层不复交父裁剪) |
MAIN_ORIGIN=(233, TITLEBAR_H) OBSERVED |
glass_pipeline.rs |
568 | 自定义 wgpu primitive 管线(iced_wgpu 0.14 官方通路):GlassQuad{Kind::{Background,Card,Veil}} → glass.wgsl 三模式;background_field() 环境光、fade_veil() 滚动渐隐帏幕 |
OBSERVED |
glass.wgsl |
— | 单 fragment 三分支:0=背景球场、1=卡片(sheen→渐变→球场→sRGB 灰修正→颗粒→SDF 圆角描边)、2=帏幕(不透明背景场 + 锚定边 smoothstep alpha 坡道) | 帏幕约定:sheen=bg 色、grain_tile.x=带高物理 px、grain_tile.y=方向旗 OBSERVED |
glow_mesh.rs |
183 | ambient_specs(pal) 两光球参数唯一来源(窗口绝对坐标,时钟冻结 40.0);tiny-skia 回退用 Mesh::Solid 扇形 |
OBSERVED |
tray.rs(src/) |
~140 | 系统托盘(tray-icon 0.24):thread_local 持有(TrayIcon !Send),事件流轮询全局 channel | OBSERVED |
win32.rs(src/) |
~70 | 窗口显隐(iced 无 hide/show):FindWindowW 按标题 + ShowWindow + AttachThreadInput 前置 | OBSERVED |
stack[ shell, toast_host, resize_grips ]
shell = container( stack[ ambient(背景球场), inner ] )
inner = column[ titlebar(40px 细条), row[ sidebar(brand_cell 64px + nav + foot), side_divider, main_area ] ]
main_area = reveal( stack[ scrollable(page_body), fade_veil(14,top), fade_veil(44,bottom) ] )
模态有 dialog 时整块套 modal::overlay
OBSERVED(pages/mod.rs:99-209)。Console/Home 不走 scrollable;DrawTicker 放侧边栏(放 scrollable 里会被视口剔除永远不 draw)。
main() → bridge::init()(建 channel,先于 application)→ iced::application(Dshnext::new, update, pages::view) → Message::Opened(id) 批量触发 RefreshEnv/RefreshProfiles/PollProcs + 可选 shot/switch/autotest 定时任务。OBSERVED(update.rs:21-57)。
core 函数(tx: &EventSink) → unbounded channel → bridge::events() (Subscription<CoreEvent>)
→ update.rs Message::Core(e) → push_log / urls / sys_log → view 重绘
订阅只建一次(recipe hash 去重,STREAM.take() 防重入)。OBSERVED(bridge.rs:45-66)。
- 进程轮询
time::every(2s)仅当procs非空才挂。 - 动画帧
frames()仅当anim.is_animating()。 - 两者皆无 →
Subscription::none(),空闲零出帧(--drawlog可验)。OBSERVED + DOCUMENTED(DESIGN.md §8)。
ambient:GlassQuad::Background全窗球场(wgpu)或 Mesh 扇形(tiny-skia)。- 卡片:
frostedwidget 自绘 glass quad + 共享颗粒场(窗口锚定,卡片只是裁剪窗)。 - 内容:普通 iced widget(同 layer 内 quad 先于 text 的次序由
with_layer保证颗粒不污染文字)。 - 帏幕:
fade_veil覆盖滚动区 Fill×Fill,带高 14px(顶)/44px(底),事件直通(不实现 update,Stack 逆序派发返回 Ignored)。
OBSERVED(frosted.rs 头注释、glass_pipeline.rs:465、pages/mod.rs:121-129)。
| DESIGN.md 说法 | 实际(OBSERVED) |
|---|---|
§4 目录树:ui/sidebar.rs / list_row.rs / toast.rs / tag.rs / segmented.rs |
不存在,全部收编进 widgets.rs |
| §4 目录树未提 | 新增:ui/{frosted, glass_pipeline, glow_mesh, reveal, titlebar}.rs + glass.wgsl;bridge.rs;update.rs;tests.rs |
| §4「app.rs = State/Message/update/view/subscription」 | update 在 update.rs、view 在 pages/(app.rs 头注释明说) |
| §2 依赖清单缺 | 实际多出:iced_wgpu+iced_renderer+bytemuck(玻璃管线)、png(--shot)、env_logger/log、image feature |
| §7 标题栏一节(BRAND 在 TITLEBAR 内) | fbdc40f 起拆开:40px 细条横跨主区,品牌块是侧边栏独立 64px 头部单元 |
| §1「软回退 15.3 MB 探针」等数字 | 仍是 DOCUMENTED 历史数字,玻璃管线加入后未重测 → 重跑 measure-idle.ps1 的活还在待办 |
- 依赖方向:
grep -rn "crate::ui|crate::pages|crate::app|crate::update|crate::bridge" src/core/(空);grep -rln "crate::core" src/ui/ src/theme.rs(空) - 行数:
wc -l src/**/*.rs(2026-09-06) - 布局:
src/pages/mod.rsview()/sidebar();常量:src/ui/titlebar.rs:20-24 - CLI 全集:
src/main.rs:3-9;启动任务批:src/update.rsMessage::Opened分支 - 帏幕/玻璃约定另见:AGENTS.md 坑 31/32/33、记忆
dshnext-frosted-grain-glass.md