Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions OpenBitFun-Installer/AGENTS-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Language Select → Options → Progress → Model Setup → Theme Setup
```bash
pnpm --dir OpenBitFun-Installer run installer:dev
pnpm --dir OpenBitFun-Installer run tauri:dev
pnpm --dir OpenBitFun-Installer run tauri:preview # 原生界面预览,不执行安装
pnpm --dir OpenBitFun-Installer run type-check
pnpm --dir OpenBitFun-Installer run build # React build / 复现 CI
pnpm --dir OpenBitFun-Installer run installer:build # 仅打包场景
Expand All @@ -49,6 +50,12 @@ pnpm --dir OpenBitFun-Installer run type-check # 前
cargo check --manifest-path OpenBitFun-Installer/src-tauri/Cargo.toml # Tauri/Rust 改动
```

修改原生 `--preview` 命令边界时,运行聚焦策略测试:

```bash
cargo test --manifest-path OpenBitFun-Installer/src-tauri/Cargo.toml --lib preview::tests
```

只有修改打包、payload、native bundling、安装/卸载流程、注册表、快捷方式或解压逻辑时,才运行完整安装器构建:

```bash
Expand Down
7 changes: 7 additions & 0 deletions OpenBitFun-Installer/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ below for PR scope.
```bash
pnpm --dir OpenBitFun-Installer run installer:dev
pnpm --dir OpenBitFun-Installer run tauri:dev
pnpm --dir OpenBitFun-Installer run tauri:preview # native UI only; no installation
pnpm --dir OpenBitFun-Installer run type-check
pnpm --dir OpenBitFun-Installer run build # React build / CI reproduction
pnpm --dir OpenBitFun-Installer run installer:build # packaging only
Expand All @@ -50,6 +51,12 @@ pnpm --dir OpenBitFun-Installer run type-check # fron
cargo check --manifest-path OpenBitFun-Installer/src-tauri/Cargo.toml # Tauri/Rust changes
```

For the native `--preview` command boundary, use the focused policy tests:

```bash
cargo test --manifest-path OpenBitFun-Installer/src-tauri/Cargo.toml --lib preview::tests
```

For installer payload validation and the independent Data Migrator boundary, run:

```bash
Expand Down
38 changes: 36 additions & 2 deletions OpenBitFun-Installer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,10 @@ OpenBitFun-Installer/
│ │ ├── ModelSetup.tsx # Optional model provider setup
│ │ └── ThemeSetup.tsx # Theme preview + finish
│ ├── components/
│ │ ├── BrandMark.tsx # Canonical fine-line brand artwork
│ │ ├── StepIndicator.tsx # Read-only setup stages
│ │ ├── WindowControls.tsx # Custom titlebar
│ │ ├── Checkbox.tsx # Styled checkbox
│ │ └── ProgressBar.tsx # Animated progress bar
│ │ └── ProgressBar.tsx # Accessible installation progress
│ ├── hooks/
│ │ └── useInstaller.ts # Core installer state machine
│ ├── styles/
Expand Down Expand Up @@ -144,6 +145,20 @@ Run the installer in development mode with hot reload:
pnpm run tauri:dev
```

### Native UI preview

Run `pnpm run tauri:preview` to open the actual installer window with `--preview`.
The titlebar page selector opens each existing UI page without running an
installation. Theme and form changes stay in memory; Finish only closes the
preview. No simulated progress or installation result is generated.

The native invoke boundary permits only launch context, the default path,
existing-installation detection, disk-space reads, and closing the window. All
other installer commands are rejected, including path validation (which writes
a probe file), installation, uninstallation, app launches, configuration writes,
and model requests. Launch context also skips configuration-directory creation.
This restriction is enforced by the native process even if the UI is modified.

### Uninstall Mode (Dev + Runtime)

Key behavior:
Expand Down Expand Up @@ -220,6 +235,25 @@ Shared light/dark values come from `@openbitfun/theme-openbitfun`. Installer-onl
[installerThemesData.ts](src/theme/installerThemesData.ts), and components consume only canonical
`--openbitfun-*` variables projected by [installerThemeRuntime.ts](src/theme/installerThemeRuntime.ts).

Buttons, inputs, fields, selectors, checkboxes, radio controls, page headers, and
native scrollbar styling come from the public `@openbitfun/ui` package. The
installer owns page layout and the install state binding. The `dev`, `build`, and
`type-check` entrypoints prepare the public design-system packages first, so a
clean checkout does not depend on pre-existing `dist` files.

The titlebar uses the application icon. Welcome and progress surfaces use the
canonical fine-line SVG from `assets/brand/source`. CSS masks follow the active
theme's foreground, including explicit theme choices that differ from the OS
setting. Theme choices use public radio controls with names and small color
palettes, without logos or framed thumbnails. The full installer window updates
on selection and serves as the live theme preview.

Page hierarchy uses spacing and typography. Existing-installation detection is
a short disclosure; version, location, and the uninstaller action stay inside
its details. Model endpoint and protocol fields are grouped in an advanced
disclosure, initially open for custom providers. Motion respects the system's
reduced-motion preference.

### Adding Install Steps

1. Add a new step key to `InstallStep` in [installer.ts](src/types/installer.ts)
Expand Down
6 changes: 6 additions & 0 deletions OpenBitFun-Installer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
"type": "module",
"description": "OpenBitFun Custom Installer - Modern branded installation experience",
"scripts": {
"prepare:design-system": "pnpm --dir ../design-system run build:packages",
"predev": "pnpm run prepare:design-system",
"prebuild": "pnpm run prepare:design-system",
"pretype-check": "pnpm run prepare:design-system",
"sync:model-i18n": "node scripts/sync-model-i18n.cjs",
"sync:theme-i18n": "node scripts/sync-theme-i18n.cjs",
"sync:i18n": "pnpm run sync:model-i18n && pnpm run sync:theme-i18n",
"dev": "pnpm run sync:i18n && vite",
"build": "pnpm run sync:i18n && tsc && vite build",
"preview": "vite preview",
"tauri:dev": "pnpm run sync:i18n && tauri dev",
"tauri:preview": "tauri dev -- -- --preview",
"tauri:build": "pnpm run sync:i18n && tauri build",
"tauri:build:fast": "pnpm run sync:i18n && tauri build -- --profile release-fast",
"tauri:build:exe": "pnpm run sync:i18n && tauri build --no-bundle",
Expand All @@ -25,6 +30,7 @@
},
"dependencies": {
"@openbitfun/theme-openbitfun": "workspace:^",
"@openbitfun/ui": "workspace:^",
"@tauri-apps/api": "2.11.1",
"@tauri-apps/plugin-dialog": "2.7.3",
"i18next": "^25.8.0",
Expand Down
13 changes: 13 additions & 0 deletions OpenBitFun-Installer/src-tauri/src/installer/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ struct PayloadManifestFile {
#[serde(rename_all = "camelCase")]
pub(crate) struct LaunchContext {
pub mode: String,
pub preview_only: bool,
pub uninstall_path: Option<String>,
pub app_language: Option<String>,
}
Expand Down Expand Up @@ -427,6 +428,15 @@ unsafe fn windows_sys_get_disk_free_space(

#[tauri::command]
pub(crate) fn get_launch_context() -> LaunchContext {
// Resolve preview before helpers that create the application's config directory.
if crate::preview::is_enabled() {
return LaunchContext {
mode: "install".to_string(),
preview_only: true,
uninstall_path: None,
app_language: None,
};
}
let args: Vec<String> = std::env::args().collect();
let app_language = read_saved_app_language();
if let Some(idx) = args.iter().position(|arg| arg == "--uninstall") {
Expand All @@ -436,6 +446,7 @@ pub(crate) fn get_launch_context() -> LaunchContext {
.or_else(guess_uninstall_path_from_exe);
return LaunchContext {
mode: "uninstall".to_string(),
preview_only: false,
uninstall_path,
app_language,
};
Expand All @@ -444,13 +455,15 @@ pub(crate) fn get_launch_context() -> LaunchContext {
if is_running_as_uninstall_binary() {
return LaunchContext {
mode: "uninstall".to_string(),
preview_only: false,
uninstall_path: guess_uninstall_path_from_exe(),
app_language,
};
}

LaunchContext {
mode: "install".to_string(),
preview_only: false,
uninstall_path: None,
app_language,
}
Expand Down
47 changes: 29 additions & 18 deletions OpenBitFun-Installer/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
mod installer;
mod preview;

use installer::commands;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let preview_only = preview::is_enabled();
let handler: fn(tauri::ipc::Invoke<tauri::Wry>) -> bool = tauri::generate_handler![
commands::get_launch_context,
commands::get_default_install_path,
commands::get_initial_install_path,
commands::get_existing_installation,
commands::launch_registered_uninstaller,
commands::get_disk_space,
commands::validate_install_path,
commands::start_installation,
commands::set_model_config,
commands::test_model_config_connection,
commands::list_model_config_models,
commands::set_theme_preference,
commands::uninstall,
commands::launch_application,
commands::launch_legacy_data_migrator,
commands::close_installer,
];
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![
commands::get_launch_context,
commands::get_default_install_path,
commands::get_initial_install_path,
commands::get_existing_installation,
commands::launch_registered_uninstaller,
commands::get_disk_space,
commands::validate_install_path,
commands::start_installation,
commands::set_model_config,
commands::test_model_config_connection,
commands::list_model_config_models,
commands::set_theme_preference,
commands::uninstall,
commands::launch_application,
commands::launch_legacy_data_migrator,
commands::close_installer,
])
.invoke_handler(move |invoke| {
if preview_only && !preview::allows_command(invoke.message.command()) {
invoke
.resolver
.reject("Installer UI preview does not execute this operation.");
return true;
}
handler(invoke)
})
.run(tauri::generate_context!())
.expect("error while running OpenBitFun Installer");
}
54 changes: 54 additions & 0 deletions OpenBitFun-Installer/src-tauri/src/preview.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! Native UI preview: only audited, read-only installer commands can run.

pub(crate) fn is_enabled() -> bool {
std::env::args_os().any(|argument| argument == "--preview")
}

pub(crate) fn allows_command(command: &str) -> bool {
matches!(
command,
"get_launch_context"
| "get_default_install_path"
| "get_existing_installation"
| "get_disk_space"
| "close_installer"
)
}

#[cfg(test)]
mod tests {
use super::allows_command;

#[test]
fn preview_rejects_writes_process_launches_network_and_unknown_commands() {
for command in [
"get_initial_install_path",
"validate_install_path",
"start_installation",
"uninstall",
"launch_registered_uninstaller",
"launch_application",
"launch_legacy_data_migrator",
"set_model_config",
"set_theme_preference",
"test_model_config_connection",
"list_model_config_models",
"future_command",
] {
assert!(!allows_command(command), "preview allowed {command}");
}
}

#[test]
fn preview_allows_read_only_data_and_window_close() {
for command in [
"get_launch_context",
"get_default_install_path",
"get_existing_installation",
"get_disk_space",
"close_installer",
] {
assert!(allows_command(command), "preview rejected {command}");
}
}
}
Loading
Loading