docs(readme): 状态段改成当前事实,模块地图补齐三个 crate

状态段原来停在「iced 版每个页面都是占位」——早就不成立了。改成:
十四页功能已齐、文案跟随系统语言、外观三件套、整合包导入导出与安装选项,
以及明确列出「还没做」的四项(安装器与自动更新、macOS 定型复核、
逐文件冲突策略、原生文件选择框)。

模块地图补上 crates/presenter、crates/i18n、crates/palette-extract,
并写明分层:app-iced → presenter → core/agent,design 与 i18n 不得反向依赖渲染框架。
This commit is contained in:
AskaEth committed 2026-11-05 11:36:39 +08:00
1 parent 4a212760b6
commit 1315b37f62
2 files changed
+76 -111

No files matched your search

+58 -72
View File
@@ -15,86 +15,66 @@ attached to other agent tooling as an MCP server.
- **Single-file native binary**: the launcher itself needs no JVM, no WebView and no sidecar - **Single-file native binary**: the launcher itself needs no JVM, no WebView and no sidecar
runtime. The JDK a game needs is downloaded and managed per instance, not as a launcher runtime. The JDK a game needs is downloaded and managed per instance, not as a launcher
dependency. dependency.
- **One UI stack in the build**: the UI is iced 0.14 + Material 3 Expressive. The earlier Slint - **One binary, two shapes**: the GUI binary also answers every CLI subcommand before any UI code
implementation (`crates/app`) is kept as source-only history: it is excluded from the cargo runs — no window, no renderer probe, no font scan. A second, lighter `ull` artifact serves the
workspace, so the workspace never builds, tests or ships it. v0.1.0 — the only tagged release so same command set from a binary that links no graphics stack at all.
far — still carries a Slint build (see Status). - **One UI stack**: iced 0.14 + Material 3 Expressive, with self-drawn title bar, animated
- **CPU software-rendering fallback**: `--software-renderer` keeps the interface rendering navigation rail, light / dark / follow-system, and a background-image colour pipeline. The earlier
normally on machines with no usable GPU or with broken drivers. Slint implementation (`crates/app`) is kept as source-only history: excluded from the cargo
- **AI agent + MCP**: chat, setting edits and crash diagnosis inside the launcher; `ull agent workspace, never built, tested or shipped.
serve` exposes the same core as an MCP server (stdio or Streamable HTTP) to external agent tools. - **CPU software-rendering fallback**: `--software-renderer` keeps the interface rendering normally
- **Mod loaders**: Fabric, Quilt, NeoForge, Forge, with modpack and CurseForge / Modrinth clients in on machines with no usable GPU or with broken drivers; the backend probe falls back on its own when
the core. no adapter is usable.
- **AI agent + MCP**: chat, setting edits and crash diagnosis inside the launcher; `ull agent serve`
exposes the same core as an MCP server (stdio or Streamable HTTP) to external agent tools. The
agent rail runs on real presenter state, and says so plainly when no model endpoint is wired.
- **Mod loaders and modpacks**: Fabric, Quilt, NeoForge, Forge; CurseForge manifest, Modrinth
`.mrpack` and MultiMC/Prism packs import, plus mrpack / CurseForge export.
- **Java management**: a Temurin → Oracle → Microsoft → Zulu fallback chain, region-aware. - **Java management**: a Temurin → Oracle → Microsoft → Zulu fallback chain, region-aware.
- **Accounts**: sign in with a Microsoft account over OAuth 2.0 device code flow → Xbox Live → - **Accounts**: Microsoft sign-in over OAuth 2.0 device code flow → Xbox Live → XSTS →
XSTS → `api.minecraftservices.com`; account tokens are kept on this machine only. `api.minecraftservices.com`; third-party community auth servers; local offline profiles for
isolated testing. Account tokens are kept on this machine only.
- **Engine**: multi-source download (health scoring, mirrors, chunked transfers, hash while - **Engine**: multi-source download (health scoring, mirrors, chunked transfers, hash while
downloading) and a YAML rule-based crash diagnosis engine. downloading) and a YAML rule-based crash diagnosis engine.
- **License**: Apache-2.0 © AskaEth (YeiJ). - **License**: Apache-2.0 © AskaEth (YeiJ).
## Status ## Status
Two tracks. They are not the same product yet, so read the headings before downloading anything. `v0.1.0` (2026-11-01) is the only tagged release so far and it predates the UI rewrite: its assets
are Slint builds. Everything described below lives on the development line and is what the release
pipeline now builds.
### Track A — historical: v0.1.0, Slint UI (the only tagged release so far) - **The iced UI is feature-complete**: fourteen pages — instances, version management (three-column
rail layout with search), content library (modpacks / mods / resource packs / shaders with source
> **Historical implementation — no longer in the default build or the release matrix.** The Slint and version filters), logs, settings (general / appearance / Java / downloads), accounts
> tree (`crates/app`) is kept in the repository as source-only reference: it is excluded from the (Microsoft device code, third-party community servers, local profiles), the AI rail and the
> cargo workspace, so `cargo build` / `cargo test` / `cargo clippy` never touch it and no artifact is installer flows — on top of one component library and one token set.
> built from it. Read the source if you want to see how that UI was put together; do not expect to - **Localisation follows the system language**: detection, an explicit override in settings, and
> build it. The list below is kept because it records what the v0.1.0 assets actually contain — persistence across restarts. UI copy goes through a single message catalogue; there is no
> since v0.1.0 the pipeline builds the iced binary and ships it under the same public asset names. hard-coded string in the view layer.
- **Appearance**: window background image, per-panel transparency and whole-window transparency
- **GitHub Releases `v0.1.0`** (2026-11-01, flagged pre-release): single-file assets for Windows (Windows), light / dark / follow-system, and system window corners where the platform grants them.
x64 (a bare `.exe`), Linux x64 and macOS (arm64 and x64) `.tar.gz`. Every release also carries On CPU rendering the translucent layers are switched off and the UI says so.
`SHA256SUMS.txt` and the `LICENSE` / `NOTICE` / `README.md` texts as separate assets; on Windows - **Packaging and the installer paths** (curseforge / modrinth / multimc import, mrpack and
those texts sit next to the `.exe` because a bare exe cannot hold them, while the unix archives CurseForge export, install options with loader selection and real cancellation) are in the build.
already contain them. - **What is not done yet**: installers / auto-update channel; macOS re-verification on a real
- **UI**: Slint + Metro. This is the shipped GUI — the instance, download, account, Java, log and machine after the Windows pass; per-file conflict policy on pack import (the import pipeline has one
settings pages all run on the core engine (the in-app AI chat panel is a scripted placeholder). global policy today, and the UI states that rather than pretending otherwise); a native file
- **CLI in the same binary**: `unlucky-launcher instances list`, `unlucky-launcher launch <instance>`, chooser (import accepts drag-and-drop and a pasted path).
`unlucky-launcher logs <sid>` and the rest of the command set run before any UI is built — no
window, no renderer probe, no font scan. Commands: `instances`, `install`, `launch`, `accounts`,
`sessions`, `logs`, `diag`, `config`, `java`, `agent`, plus `--help` / `--version`.
- **A second, lighter artifact `ull`** (`cargo build --release --bin ull`, zero UI dependencies): the
same command set from a binary that links no graphics stack at all — not even fontconfig. Servers,
CI containers and Termux get `ull`; desktops get `unlucky-launcher` and one binary covers both GUI
and CLI. Both are release assets from the next tagged release (the `v0.1.0` assets predate this).
- Windows ARM64 and Linux ARM64 slots are wired into the release pipeline; the next tagged release
builds them. The `v0.1.0` assets predate those two slots.
### Track B — in progress on `dev`: next-generation UI (iced + Material 3 Expressive)
**This track is not released yet — you can see it only by building from source.**
- **Locked direction**: the look is Material 3 Expressive and the framework is iced 0.14; the UI
layer is written from scratch. Neither the style nor the framework will change again.
- **Done**: the token crate `crates/design` (no UI framework, no third-party crates — real HCT
color, type scale, shape scale, spacing, elevation, motion, icon geometry, per-component
metrics) and the skeleton crate `crates/app-iced` (opens a window, self-drawn title bar and
window buttons, navigation rail, colors and type taken from tokens only, GPU→CPU backend probe
with automatic fallback, and the screenshot/diff channel the visual reviews use).
- **Not done**: the component library (buttons, dialogs, lists, menus, search, and the rest of the
M3 set), page migration, and the i18n pass. Every page in the iced binary is a placeholder.
- **Slint is retired from the build, kept as reference**: `crates/app` is no longer a workspace member
(M118) — the source stays for historical reference only: not compiled, not tested, not shipped, and
no longer buildable as a workspace member. What is left of the flip is the *rename*: until the iced
build passes equivalence acceptance its binary is still called `unlucky-launcher-iced`, while the
release pipeline already ships it under the public `unlucky-launcher` asset name.
Binaries you may run into, so you can tell them apart: Binaries you may run into, so you can tell them apart:
| Binary | Crate | State | | Binary | Crate | State |
|--------|-------|-------| |--------|-------|-------|
| `unlucky-launcher-iced` | `crates/app-iced` (iced 0.14) | the only GUI binary the workspace builds; the release pipeline stages it under the public name `unlucky-launcher`; same subcommand dispatch | | `unlucky-launcher-iced` | `crates/app-iced` (iced 0.14) | the GUI binary the workspace builds; the release pipeline stages it under the public name `unlucky-launcher`; the same binary answers every CLI subcommand |
| `unlucky-launcher` | `crates/app` (Slint) | **historical**: that crate is excluded from the workspace, so this binary is no longer built or shipped; the v0.1.0 assets are the last Slint builds | | `unlucky-launcher` | `crates/app` (Slint) | **historical**: that crate is excluded from the workspace, so this binary is no longer built or shipped; the v0.1.0 assets are the last Slint builds |
| `ull` | `crates/cli` | the zero-UI build of the same command set, shipped as its own asset | | `ull` | `crates/cli` | the zero-UI build of the same command set, shipped as its own asset |
`unlucky-launcher` and `ull` are not duplicates. The GUI binary needs the graphics stack when it `unlucky-launcher` (the GUI) and `ull` are not duplicates. The GUI binary needs the graphics stack
opens a window (it `dlopen`s X11 / Wayland / GL, and links `libfontconfig` at load); `ull` needs when it opens a window (it `dlopen`s X11 / Wayland / GL, and links `libfontconfig` at load); `ull`
nothing beyond glibc, so it runs on a server, a CI container or Termux where no window can exist. needs nothing beyond glibc, so it runs on a server, a CI container or Termux where no window can
Pick `ull` when there is no desktop, `unlucky-launcher <command>` when there is. Both print the same exist. Pick `ull` when there is no desktop, `unlucky-launcher <command>` when there is. Both print the
CLI text and name themselves after the binary you invoked: `unlucky-launcher --help` starts with same CLI text and name themselves after the binary you invoked: `unlucky-launcher --help` starts with
`unlucky-launcher ·`, `ull --help` starts with `ull ·`. `unlucky-launcher ·`, `ull --help` starts with `ull ·`.
### Subcommand dispatch ### Subcommand dispatch
@@ -123,7 +103,7 @@ The rule is deliberately narrow, so nothing that used to work changed meaning:
**Unknown subcommands are an error, not a GUI launch**: `unlucky-launcher lst` prints **Unknown subcommands are an error, not a GUI launch**: `unlucky-launcher lst` prints
"未知命令 `lst`", hints `unlucky-launcher --help`, and exits 1. Silently opening a window would tell "未知命令 `lst`", hints `unlucky-launcher --help`, and exits 1. Silently opening a window would tell
a script that its typo took effect. The subcommand table itself lives in exactly one place — a script that its typo took effect. The subcommand table itself lives in exactly one place —
`crates/cli/src/cli/parse.rs` — so both GUI binaries and `ull` accept the same set by construction. `crates/cli/src/cli/parse.rs` — so both the GUI binary and `ull` accept the same set by construction.
## Module map ## Module map
@@ -131,8 +111,11 @@ a script that its typo took effect. The subcommand table itself lives in exactly
Unlucky-Launcher (cargo workspace) Unlucky-Launcher (cargo workspace)
├── crates/core # domain library: zero UI dependencies, eight modules ├── crates/core # domain library: zero UI dependencies, eight modules
├── crates/agent # AI agent: ToolSpec registry + built-in client + MCP server (one core, two shapes) ├── crates/agent # AI agent: ToolSpec registry + built-in client + MCP server (one core, two shapes)
├── crates/presenter # business host layer: framework-free state / host / dto / conversion
├── crates/design # design tokens: real HCT color / type / shape / spacing / elevation / motion / icons ├── crates/design # design tokens: real HCT color / type / shape / spacing / elevation / motion / icons
│ # / component metrics — zero UI dependencies, no third-party crates │ # / component metrics — zero UI dependencies, no third-party crates
├── crates/i18n # language detection, message catalogue, CJK font policy
├── crates/palette-extract # the only crate that decodes images, for the wallpaper colour pipeline
├── crates/app # HISTORICAL Slint UI (bin `unlucky-launcher`): excluded from the workspace, ├── crates/app # HISTORICAL Slint UI (bin `unlucky-launcher`): excluded from the workspace,
│ # source-only reference — never built, tested or shipped any more │ # source-only reference — never built, tested or shipped any more
├── crates/app-iced # the iced UI (bin `unlucky-launcher-iced`): the only GUI the workspace builds ├── crates/app-iced # the iced UI (bin `unlucky-launcher-iced`): the only GUI the workspace builds
@@ -140,6 +123,9 @@ Unlucky-Launcher (cargo workspace)
└── packaging # single-file artifact matrix and release scripts └── packaging # single-file artifact matrix and release scripts
``` ```
Layering: `app-iced` → `presenter` → `core` / `agent`. `design` and `i18n` are consumed by the shell
and the presenter; none of them may depend on a rendering framework, and the guards enforce it.
The eight modules of `crates/core`: The eight modules of `crates/core`:
| Module | Responsibility | | Module | Responsibility |
@@ -148,8 +134,8 @@ The eight modules of `crates/core`:
| `download` | multi-source health scoring, mirrors, chunked downloads, hash-while-downloading | | `download` | multi-source health scoring, mirrors, chunked downloads, hash-while-downloading |
| `game` | version / instance repository, snapshots and inheritance resolution | | `game` | version / instance repository, snapshots and inheritance resolution |
| `launch` | 10-stage launch pipeline, processes, JSONL event stream | | `launch` | 10-stage launch pipeline, processes, JSONL event stream |
| `auth` | Microsoft sign-in (OAuth 2.0 device code → Xbox Live → XSTS → Minecraft Services) | | `auth` | Microsoft sign-in (OAuth 2.0 device code → Xbox Live → XSTS → Minecraft Services), third-party community servers, local profiles |
| `addon` | mod loader installation, modpacks, CurseForge / Modrinth | | `addon` | mod loader installation, modpack import, CurseForge / Modrinth, pack export |
| `diagnose` | five signal collectors, YAML rule engine, hs_err parsing | | `diagnose` | five signal collectors, YAML rule engine, hs_err parsing |
| `platform` | OS abstraction: paths, processes, file locks, capability probing, runtime OS detection | | `platform` | OS abstraction: paths, processes, file locks, capability probing, runtime OS detection |
@@ -161,7 +147,7 @@ Prerequisites:
`rustfmt` and `clippy` components) `rustfmt` and `clippy` components)
- Windows: MSVC toolchain (what CI uses) or MinGW-w64 (`x86_64-pc-windows-gnu`) - Windows: MSVC toolchain (what CI uses) or MinGW-w64 (`x86_64-pc-windows-gnu`)
- Linux: `libxkbcommon-dev libfontconfig1-dev libgl1-mesa-dev libegl1-mesa-dev libwayland-dev - Linux: `libxkbcommon-dev libfontconfig1-dev libgl1-mesa-dev libegl1-mesa-dev libwayland-dev
libxcb1-dev` — winit + wgpu/tiny-skia ask for these at build time (the Slint build also needed femtovg) libxcb1-dev` — winit + wgpu/tiny-skia ask for these at build time
```bash ```bash
cargo fmt --all --check # formatting cargo fmt --all --check # formatting
@@ -170,9 +156,9 @@ cargo test --workspace # tests
cargo build --release # binaries land in target/release/ cargo build --release # binaries land in target/release/
``` ```
`--workspace` covers the eight members in the module map. `crates/app` is excluded at the workspace `--workspace` covers the members in the module map. `crates/app` is excluded at the workspace level
level (M118), so none of these commands compiles the Slint tree — and there is no build command for (M118), so none of these commands compiles the Slint tree — and there is no build command for it:
it: that crate is kept as source-only reference and can no longer be built as a workspace member. that crate is kept as source-only reference and can no longer be built as a workspace member.
What the build produces: What the build produces:
@@ -208,7 +194,7 @@ a real runner.
| Workflow | Content | | Workflow | Content |
|----------|---------| |----------|---------|
| `.github/workflows/ci.yml` | fmt + clippy + test + release build (Windows / Linux matrix); Linux runtime smoke (Xvfb: renders one real frame, then asserts the PNG is not blank); macOS runtime probe (manual dispatch only) | | `.github/workflows/ci.yml` | fmt + clippy + test + release build (Windows / Linux matrix); Linux runtime smoke (Xvfb: renders one real frame, then asserts the PNG is not blank); macOS runtime probe (manual dispatch only) |
| `.github/workflows/guards.yml` | zero-UI guard for `crates/core` (manifest plus `cargo tree`), 400-lines-per-file guard, cargo-deny license and source guard | | `.github/workflows/guards.yml` | zero-UI guards (`crates/core`, `crates/presenter`, `crates/design`), 400-lines-per-file guard, cargo-deny license and source guard |
| `.github/workflows/release.yml` | tag `v*` → per-slot builds → single-file artifacts + `SHA256SUMS.txt` → GitHub Release | | `.github/workflows/release.yml` | tag `v*` → per-slot builds → single-file artifacts + `SHA256SUMS.txt` → GitHub Release |
## Platform baseline ## Platform baseline
+18 -39
View File
@@ -28,47 +28,23 @@ Unlucky Launcher(ULL)是一个用 Rust 写的免费开源 Minecraft: Java
## 当前状态 ## 当前状态
两条轨道,还不是同一个东西——看小标题再决定下载什么。 `v0.1.0`(2026-11-01)是目前唯一的 tag,它早于 UI 重写:那批资产是 Slint 版。下面说的这套东西在开发
线上,也正是发布流水线现在会打出来的东西。
### 轨道 A —— 历史:v0.1.0,Slint 版(目前唯一的 tag) - **iced 版界面功能已齐**:十四页——实例、版本管理(三栏 + 搜索)、内容库(整合包 / Mod / 资源包 /
光影,带来源与版本筛选)、日志、设置(通用 / 外观 / Java / 下载)、账户(微软设备码、第三方社区服、
> **历史实现——已不在默认构建与发布矩阵内。** Slint 那棵树(`crates/app`)作为源码参考留在仓库里: 本地档案)、AI 右栏,以及安装流程——共用一套组件库与一套令牌。
> 已被 cargo workspace `exclude`,所以 `cargo build` / `cargo test` / `cargo clippy` 都不会碰它, - **文案跟随系统语言**:自动探测、设置里可显式切换、重启后保持。界面文案统一走一份消息目录,视图层
> 也没有任何产物从它构建。想了解那一版 UI 怎么写的就直接读源码——**不要再尝试构建它**(它的 manifest 没有硬编码字符串。
> 整份是 workspace 继承写法,离开 workspace 后连解析都过不了)。下面这段原文保留,因为它记录的是 - **外观**:窗口背景图、面板透明度、整窗透明度(Windows)、浅色 / 深色 / 跟随系统,以及在系统愿意给
> `v0.1.0` 资产里到底装了什么;从 `v0.1.0` 之后,流水线构建的是 iced 版,对外文件名不变。 的时候使用系统窗口圆角。CPU 渲染档会关掉半透明层,并在界面上说清楚。
- **整合包与安装链路都在构建里**:CurseForge 清单 / Modrinth `.mrpack` / MultiMC·Prism 导入,mrpack
- **GitHub Releases `v0.1.0`**(2026-11-01,标记为 pre-release):单文件产物覆盖 Windows x64(裸 与 CurseForge 导出,安装选项(选加载器)与**真取消**。
`.exe`)、Linux x64、macOS(arm64 与 x64)`.tar.gz`。每个 Release 另挂 `SHA256SUMS.txt` 与 - **还没做**:安装器与自动更新通道;Windows 定型后的 macOS 真机复核;整合包导入的**逐文件**冲突策略
`LICENSE` / `NOTICE` / `README.md` 三份文本——Windows 是「一个 exe 装不下文本」所以并列挂出, (当前只有一份全局策略,界面如实写明而不是假装支持);原生文件选择框(导入支持拖拽与粘贴路径)。
unix 归档里本来就装着。
- **UI**:Slint + Metro。这是已交付的图形界面——实例、下载、账户、Java、日志、设置
各页都由 core 引擎驱动(应用内 AI 对话面板仍是脚本化的占位)。
- **同一个二进制就是 CLI**:`unlucky-launcher instances list`、`unlucky-launcher launch <实例>`、
`unlucky-launcher logs <sid>` 等命令在任何 UI 代码之前分流——不开窗、不探渲染后端、不扫字体。
命令集:`instances`、`install`、`launch`、`accounts`、`sessions`、`logs`、`diag`、`config`、
`java`、`agent`,外加 `--help` / `--version`。
- **另有一个更轻的 `ull`**(`cargo build --release --bin ull`,零 UI 依赖):同一套命令,但二进制不
链接任何图形栈——连 fontconfig 都不需要。服务器、CI 容器、Termux 拿 `ull`;有桌面的机器拿
`unlucky-launcher`,一个文件同时管 GUI 与命令行。两者都从下一个 tag 起进发布资产
(`v0.1.0` 的资产早于这个变化)。
- Windows ARM64 与 Linux ARM64 两个槽位已接进发布流水线,随下一个 tag 发布;`v0.1.0` 的资产早于
这两个槽位。
### 轨道 B —— dev 上的下一代 UI(iced + M3 Expressive,施工中)
**这条轨道还没发布——要看只能自己从源码编。**
- **方向已锁死**:视觉 = Material 3 Expressive,框架 = iced 0.14,UI 层从头写;风格与框架都不会再换。
- **已完成**:令牌 crate `crates/design`(零 UI 框架依赖、零第三方 crate——真 HCT 色彩、排版档位、
形状刻度、间距、高程、动效、图标几何、组件度量)与骨架 crate `crates/app-iced`(能开窗、自绘标题栏
与窗口键、导航轨、颜色与字号只从令牌取、GPU→CPU 后端探测与自动降级、视觉验收用的截图与逐像素
对比通道)。
- **没完成**:组件库(按钮、对话框、列表、菜单、搜索栏,以及 M3 其余组件)、页面搬迁、i18n 文案。
iced 版里现在每一个页面都是占位。
- **Slint 版已退出构建,只留源码作参考**:`crates/app` 不再是 workspace 成员(M118)——源码留作历史 - **Slint 版已退出构建,只留源码作参考**:`crates/app` 不再是 workspace 成员(M118)——源码留作历史
参考,不编译、不测试、不发布,也不再有可用的构建方式。翻牌只剩**改名**这一步:在 iced 版通过等价 参考,不编译、不测试、不发布,也不再有可用的构建方式。翻牌只剩**改名**这一步:它的 bin 现在叫
验收前,它的 bin 还叫 `unlucky-launcher-iced`;发布流水线已经把它按对外名 `unlucky-launcher` 打出去。 `unlucky-launcher-iced`,发布流水线已经把它按对外名 `unlucky-launcher` 打出去。
可能会遇到的 bin,先认清楚: 可能会遇到的 bin,先认清楚:
@@ -115,8 +91,11 @@ unlucky-launcher --version # CLI,输出版本 + 系统摘要
Unlucky-Launcher (cargo workspace) Unlucky-Launcher (cargo workspace)
├── crates/core # 领域库:零 UI 依赖,八模块 ├── crates/core # 领域库:零 UI 依赖,八模块
├── crates/agent # AI Agent:ToolSpec 注册表 + 内置客户端 + MCP server(双形态共用一个内核) ├── crates/agent # AI Agent:ToolSpec 注册表 + 内置客户端 + MCP server(双形态共用一个内核)
├── crates/presenter # 业务宿主层:与框架无关的 state / host / dto / 转换
├── crates/design # 设计令牌:真 HCT 色彩 / 排版 / 形状 / 间距 / 高程 / 动效 / 图标 / 组件度量 ├── crates/design # 设计令牌:真 HCT 色彩 / 排版 / 形状 / 间距 / 高程 / 动效 / 图标 / 组件度量
│ # —— 零 UI 依赖、零第三方 crate │ # —— 零 UI 依赖、零第三方 crate
├── crates/i18n # 语言探测、消息目录、中文字体策略
├── crates/palette-extract # 唯一解码图片的 crate,供壁纸取色链路使用
├── crates/app # 历史 Slint UI(bin `unlucky-launcher`):已被 workspace `exclude`, ├── crates/app # 历史 Slint UI(bin `unlucky-launcher`):已被 workspace `exclude`,
│ # 仅作源码参考——不再被构建 / 测试 / 发布 │ # 仅作源码参考——不再被构建 / 测试 / 发布
├── crates/app-iced # iced UI(bin `unlucky-launcher-iced`):workspace 里唯一的 GUI bin ├── crates/app-iced # iced UI(bin `unlucky-launcher-iced`):workspace 里唯一的 GUI bin