Add architecture specification document
publish / publish-tauri (, ubuntu-22.04) (push) Failing after 4m52s
publish / publish-tauri (, ubuntu-22.04) (push) Failing after 4m52s
This commit is contained in:
@@ -0,0 +1,798 @@
|
||||
# Smoothie — Architecture Specification
|
||||
|
||||
**Status:** As-built (v0.1.2)
|
||||
**Scope:** Desktop application for discovering, installing, isolating, and launching Blender builds.
|
||||
**Audience:** Maintainers and contributors.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
Smoothie is a Tauri 2 desktop application that manages multiple Blender installations from a single, portable library folder.
|
||||
|
||||
The application:
|
||||
|
||||
- Discovers official Blender builds from `download.blender.org`.
|
||||
- Downloads and extracts builds into a user-configurable library.
|
||||
- Isolates each install's Blender user resources so versions and templates do not share configuration.
|
||||
- Launches Blender with per-install environment variables and optional application templates.
|
||||
- Registers builds with the operating system and creates desktop shortcuts (Linux).
|
||||
- Generates per-install icons and splash banners.
|
||||
- Keeps update, tray, notification, and window-state behavior in the Tauri host.
|
||||
|
||||
### Goals
|
||||
|
||||
1. **Portability.** All versions, configuration, and templates live under one library directory that can be moved, backed up, or copied.
|
||||
2. **Isolation.** Each Blender build reads and writes its own `BLENDER_USER_RESOURCES` directory.
|
||||
3. **Low-friction management.** Install, launch, favorite, delete, and set default from one GUI.
|
||||
4. **Desktop-native behavior.** Tray support, OS shortcuts, file associations, signed auto-updates.
|
||||
5. **Thin native layer.** Rust is used only for capabilities the webview cannot perform reliably.
|
||||
|
||||
### Non-goals
|
||||
|
||||
- Building or compiling Blender from source.
|
||||
- Hosting or mirroring Blender binaries.
|
||||
- Multi-user or cloud-synced library management.
|
||||
- Plugin/add-on marketplace functionality.
|
||||
- Mobile support (the Tauri entry point exists, but the capabilities and UX target desktop).
|
||||
|
||||
---
|
||||
|
||||
## 2. System Context
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
User[User] --> UI[Svelte 5 UI]
|
||||
UI --> Services[JS service modules]
|
||||
Services --> Stores[Svelte stores]
|
||||
Services --> TauriJS[Tauri JS APIs]
|
||||
Services --> Commands[Rust commands]
|
||||
TauriJS --> Plugins[Tauri plugins]
|
||||
Plugins --> Net[download.blender.org]
|
||||
Plugins --> OS[OS: dialogs, notifications, shell, tray]
|
||||
Commands --> FS[(Local filesystem)]
|
||||
Plugins --> FS
|
||||
Commands --> Blender[Blender process]
|
||||
Plugins --> Updater[Update endpoint]
|
||||
FS --> Library[Portable Blender library]
|
||||
```
|
||||
|
||||
Smoothie has no application server. The only runtime network dependencies are:
|
||||
|
||||
- `https://download.blender.org` for release discovery and downloads.
|
||||
- The configured updater endpoint for signed application updates.
|
||||
|
||||
---
|
||||
|
||||
## 3. Technology Stack
|
||||
|
||||
| Layer | Technology | Notes |
|
||||
| --- | --- | --- |
|
||||
| Desktop shell | Tauri 2 (Rust, edition 2021) | One window, one main process |
|
||||
| Frontend | SvelteKit 2 + Svelte 5 runes | `adapter-static`, prerendered, SSR disabled |
|
||||
| Bundler | Vite 6 | Fixed dev port `1420` |
|
||||
| Package manager | Bun | `bun.lock` committed |
|
||||
| UI | Hand-written CSS + `@webtui/css` | Splitpanes, tabs, custom components |
|
||||
| Persistence | `@tauri-apps/plugin-store` | `settings.json`, `versions.json` |
|
||||
| Downloads | `@tauri-apps/plugin-upload` | Streaming download with progress |
|
||||
| HTTP | `@tauri-apps/plugin-http` | Scoped to Blender hosts |
|
||||
| Filesystem | `@tauri-apps/plugin-fs` + persisted scope | Library and shortcut operations |
|
||||
| OS integration | dialog, os, shell, notification, opener, window-state | Linux shortcut creation in JS |
|
||||
| Updates | `@tauri-apps/plugin-updater` | Signed artifacts |
|
||||
| Archives | Rust crates: `zip`, `tar`, `flate2`, `xz2`, `bzip2` | Custom Tauri commands |
|
||||
|
||||
---
|
||||
|
||||
## 4. High-Level Architecture
|
||||
|
||||
Smoothie is organized into four layers:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Presentation
|
||||
Routes[routes/+page.svelte]
|
||||
Components[lib/components]
|
||||
Styles[app.css and component styles]
|
||||
end
|
||||
|
||||
subgraph Application
|
||||
Settings[settings.js]
|
||||
Fetch[blenderfetch.js]
|
||||
Download[download.js]
|
||||
Library[library.js]
|
||||
FileUtils[file_utils.js]
|
||||
Tray[trayicon.js]
|
||||
Notify[notification.js]
|
||||
end
|
||||
|
||||
subgraph PlatformBoundary
|
||||
TauriAPI[Tauri core API]
|
||||
TauriPlugins[Tauri plugins]
|
||||
RustCommands[Rust commands]
|
||||
end
|
||||
|
||||
subgraph State
|
||||
SvelteStores[Svelte writable stores]
|
||||
LazyStores[plugin-store JSON files]
|
||||
Disk[Library filesystem]
|
||||
end
|
||||
|
||||
Presentation --> Application
|
||||
Application --> PlatformBoundary
|
||||
Application --> SvelteStores
|
||||
Application --> LazyStores
|
||||
PlatformBoundary --> Disk
|
||||
```
|
||||
|
||||
### Architectural principles
|
||||
|
||||
1. **Filesystem-first.** The library directory is the source of truth for installed versions. There is no installation database to drift out of sync.
|
||||
2. **Stateless UI over persistent services.** Components read Svelte stores; service modules perform I/O and refresh stores.
|
||||
3. **Thin Rust boundary.** Rust handles archive extraction and process spawning. All orchestration and file layout remain in JavaScript.
|
||||
4. **Explicit per-version isolation.** Launches inject `BLENDER_USER_RESOURCES` pointing at a version-specific config folder.
|
||||
5. **Idempotent maintenance.** Missing icons, banners, templates, and shortcuts are regenerated when versions are rescanned.
|
||||
6. **Graceful degradation.** Missing executables are skipped; failed shortcuts and banners log warnings rather than aborting startup.
|
||||
|
||||
---
|
||||
|
||||
## 5. Repository Layout
|
||||
|
||||
```text
|
||||
smoothie/
|
||||
├── src/ # SvelteKit frontend
|
||||
│ ├── app.css # Global styles and theme variables
|
||||
│ ├── app.html # Webview shell document
|
||||
│ ├── routes/
|
||||
│ │ ├── +layout.js # prerender = true, ssr = false
|
||||
│ │ ├── +layout.svelte
|
||||
│ │ └── +page.svelte # Main application shell and bootstrap
|
||||
│ └── lib/
|
||||
│ ├── assets/ # Base icon and embedded font
|
||||
│ ├── components/ # UI components
|
||||
│ ├── blenderfetch.js # Release discovery and filename parsing
|
||||
│ ├── download.js # Download/extract pipeline
|
||||
│ ├── file_utils.js # Icons, banners, paths, copy/move helpers
|
||||
│ ├── library.js # Installed versions, launch, templates, shortcuts
|
||||
│ ├── notification.js # Transient notification store
|
||||
│ ├── settings.js # Settings, defaults, platform detection
|
||||
│ ├── transition.js # Crossfade transition
|
||||
│ └── trayicon.js # Tray menu and close-to-tray behavior
|
||||
├── src-tauri/
|
||||
│ ├── src/
|
||||
│ │ ├── lib.rs # Commands and plugin registration
|
||||
│ │ └── main.rs # Entry point
|
||||
│ ├── capabilities/desktop.json # Permissions and scopes
|
||||
│ ├── icons/ # App and platform icons
|
||||
│ ├── Cargo.toml
|
||||
│ ├── build.rs
|
||||
│ └── tauri.conf.json # App, window, bundle, updater config
|
||||
├── static/ # Static web assets
|
||||
├── .gitea/workflows/build.yml # Release build workflow
|
||||
├── package.json
|
||||
├── svelte.config.js
|
||||
└── vite.config.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Frontend Architecture
|
||||
|
||||
### 6.1 Application shell
|
||||
|
||||
`src/routes/+page.svelte` owns:
|
||||
|
||||
- The splash screen and initialization sequence.
|
||||
- The top-level tab state (`library`, `download`, `settings`).
|
||||
- Subscription wiring between Svelte stores and component props.
|
||||
- Global CSS variables and layout.
|
||||
|
||||
Bootstrap order:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UI as +page.svelte
|
||||
participant App as Tauri app API
|
||||
participant Settings as settings.js
|
||||
participant Fetch as blenderfetch.js
|
||||
participant Library as library.js
|
||||
participant Tray as trayicon.js
|
||||
|
||||
UI->>App: getVersion()
|
||||
UI->>Settings: getSettings()
|
||||
Settings->>Settings: detect platform/arch
|
||||
Settings->>Settings: create library structure
|
||||
UI->>Fetch: getBlenderReleases()
|
||||
Fetch->>Fetch: scrape and parse release pages
|
||||
UI->>Library: getInstalledVersions()
|
||||
Library->>Library: scan library, find executables
|
||||
Library->>Library: generate icons/banners, shortcuts
|
||||
UI->>Tray: setupTrayIcon()
|
||||
UI->>UI: subscribe stores and show main UI
|
||||
```
|
||||
|
||||
Initialization is intentionally serial and surfaced through splash status text. No routing is used beyond the single route; tabs are local component state.
|
||||
|
||||
### 6.2 Components
|
||||
|
||||
| Component | Responsibility |
|
||||
| --- | --- |
|
||||
| `Library.svelte` | Two-pane view: installed/favorite versions on the left, templates for the selected version on the right |
|
||||
| `Download.svelte` | Lists major releases and their available minor versions |
|
||||
| `Release.svelte` | Per-release row: version selector, progress, download/installed state |
|
||||
| `VersionCard.svelte` | Version tile: favorite, select, launch, delete, set default |
|
||||
| `TemplateCard.svelte` | Launch base Blender or a template; delete template |
|
||||
| `Dropzone.svelte` | Accepts dropped `.blend` files as templates for the selected version |
|
||||
| `Settings.svelte` | Displays settings and moves the library directory |
|
||||
| `Splash.svelte` | Startup status display |
|
||||
| `Dialog.svelte` | Promise-based confirmation dialog (`show({...})`) |
|
||||
| `Popup.svelte` | Transient success/error toast |
|
||||
| `Menu.svelte`, `MenuItem.svelte` | Context menu primitives |
|
||||
| `Progress.svelte` | Download/extraction progress display |
|
||||
| `Button.svelte` | Shared button styles and variants |
|
||||
|
||||
### 6.3 State management
|
||||
|
||||
State is split into two categories:
|
||||
|
||||
**In-memory Svelte stores**
|
||||
|
||||
| Store | Module | Shape |
|
||||
| --- | --- | --- |
|
||||
| `currentSettings` | `settings.js` | `Settings` |
|
||||
| `installedVersionsStore` | `library.js` | `blenderVersion[]` |
|
||||
| `selectedVersionStore` | `library.js` | `blenderVersion \| null` |
|
||||
| `blenderReleases` | `blenderfetch.js` | `BlenderRelease[]` |
|
||||
| `downloadTasksStore` | `download.js` | `DownloadTask[]` |
|
||||
| `notification` | `notification.js` | `{ message, timeout } \| null` |
|
||||
|
||||
**Persisted stores (`plugin-store`)**
|
||||
|
||||
| File | Keys | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `settings.json` | Setting names | User preferences |
|
||||
| `versions.json` | Version string → boolean | Favorite flags |
|
||||
|
||||
The UI does not cache installed versions independently of `installedVersionsStore`; components rescan through `getInstalledVersions()` after mutations.
|
||||
|
||||
### 6.4 Service modules
|
||||
|
||||
#### `settings.js`
|
||||
|
||||
- Defines the `Settings` type and defaults.
|
||||
- Detects host platform and architecture via `plugin-os`.
|
||||
- Persists settings through `LazyStore`.
|
||||
- Creates the library structure when the library directory changes.
|
||||
- Exposes `BASE_LIBRARY_DIR = "smoothie-library"`.
|
||||
|
||||
#### `blenderfetch.js`
|
||||
|
||||
- Fetches `https://download.blender.org/release/`.
|
||||
- Parses the index with `DOMParser`.
|
||||
- Selects version folders matching `Blender<version>/` and greater than `2.78`.
|
||||
- Fetches each version folder in parallel and parses filenames of the form:
|
||||
|
||||
```text
|
||||
blender-<version>-<platform>-<arch>.<extension>
|
||||
```
|
||||
|
||||
- Filters to the selected platform and architecture.
|
||||
- Sorts releases with a numeric dot-segment comparator.
|
||||
- Emits `BlenderRelease[]` and `FileLink[]`.
|
||||
|
||||
This module is deliberately source-specific. It is the only component that understands Blender's download directory naming convention.
|
||||
|
||||
#### `download.js`
|
||||
|
||||
Implements the install pipeline:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UI as Release.svelte
|
||||
participant DL as download.js
|
||||
participant Store as plugin-upload
|
||||
participant Rust as extract_archive
|
||||
participant Lib as library.js
|
||||
|
||||
UI->>DL: downloadBlenderVersion(link)
|
||||
DL->>DL: read settings and compute target dir
|
||||
DL->>Store: download(url, target/archive)
|
||||
loop progress
|
||||
Store-->>DL: loaded / total
|
||||
DL->>DL: updateDownloadProgress(version)
|
||||
end
|
||||
DL->>Rust: extract_archive(archive, target)
|
||||
Rust->>Rust: spawn_blocking extraction
|
||||
Rust->>Rust: strip single top-level directory
|
||||
DL->>DL: remove archive unless keepDownloadedArchives
|
||||
DL->>Lib: getInstalledVersions()
|
||||
DL-->>UI: { success, message, path }
|
||||
```
|
||||
|
||||
Key behaviors:
|
||||
|
||||
- Target directory: `<libraryDir>/smoothie-library/blender/<version>`.
|
||||
- Progress is keyed by version string, not by task ID.
|
||||
- On download failure, an empty target directory is removed.
|
||||
- On extraction failure, the archive is retained and the error is returned.
|
||||
- Extraction is delegated to a single Rust command; the JS layer only decides retention and refresh.
|
||||
|
||||
#### `library.js`
|
||||
|
||||
The domain core. Responsibilities:
|
||||
|
||||
- Scan `<library>/blender/*` and build `blenderVersion` objects.
|
||||
- Resolve the executable for the current platform.
|
||||
- Load templates and ensure template directories exist.
|
||||
- Generate missing icons and banners.
|
||||
- Create/remove Linux `.desktop` entries.
|
||||
- Launch Blender with isolated environment variables.
|
||||
- Register a build as the system default (`blender --register`).
|
||||
- Maintain favorites.
|
||||
- Delete versions and clean up associated resources.
|
||||
- Refresh `selectedVersionStore` after scans.
|
||||
|
||||
#### `file_utils.js`
|
||||
|
||||
- Canvas-based icon generation with the embedded base icon and `Europa-Mono` font.
|
||||
- Canvas/figlet-based 600×90 banner generation.
|
||||
- Directory creation, recursive copy, move, and existence checks.
|
||||
- Single-top-level-directory flattening.
|
||||
- `.blend` drag-and-drop import as templates.
|
||||
|
||||
#### `trayicon.js`
|
||||
|
||||
- Creates a tray icon with "Show/Hide" and "Quit".
|
||||
- Intercepts window close and hides to tray when `closeToTray` is enabled.
|
||||
|
||||
#### `notification.js`
|
||||
|
||||
- Tiny writable store consumed by `+page.svelte` and rendered through `Popup.svelte`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Native (Rust) Layer
|
||||
|
||||
`src-tauri/src/lib.rs` exposes seven commands and registers twelve plugins.
|
||||
|
||||
### 7.1 Commands
|
||||
|
||||
| Command | Signature | Behavior |
|
||||
| --- | --- | --- |
|
||||
| `extract_zip` | `(archive_path, target_dir)` | Extracts ZIP entries, creating parent directories |
|
||||
| `extract_tar` | `(archive_path, target_dir)` | Extracts uncompressed TAR |
|
||||
| `extract_tar_gz` | `(archive_path, target_dir)` | Extracts gzip-compressed TAR |
|
||||
| `extract_tar_xz` | `(archive_path, target_dir)` | Extracts xz-compressed TAR |
|
||||
| `extract_tar_bz2` | `(archive_path, target_dir)` | Extracts bzip2-compressed TAR |
|
||||
| `extract_archive` | `(archive_path, target_dir)` | Async dispatcher; detects format by filename; runs in `spawn_blocking` |
|
||||
| `launch_binary` | `(path, args, env?)` | Spawns a detached process with null stdio |
|
||||
|
||||
Format detection in `extract_archive`:
|
||||
|
||||
1. Multi-part suffixes: `.tar.xz` / `.txz`, `.tar.gz` / `.tgz`, `.tar.bz2` / `.tbz2` / `.tbz`.
|
||||
2. Single suffix: `.tar`, `.zip`.
|
||||
3. Unknown suffix: try ZIP, then uncompressed TAR.
|
||||
|
||||
All archive extractors call `strip_single_top_level_directory`, which flattens an archive that produced exactly one directory. This is required because Blender archives contain a versioned wrapping folder.
|
||||
|
||||
### 7.2 Plugins
|
||||
|
||||
Registered in `lib.rs` and declared in the frontend dependency set:
|
||||
|
||||
- `updater`, `upload`, `shell`, `fs`, `persisted-scope`, `store`, `os`, `notification`, `http`, `dialog`, `window-state`, `opener`.
|
||||
|
||||
`window-state` is registered twice (once in the builder chain and once in `setup`); this is redundant but harmless and should be consolidated.
|
||||
|
||||
### 7.3 Process model
|
||||
|
||||
Blender is launched as a detached child process with:
|
||||
|
||||
- `stdin`, `stdout`, `stderr` set to null.
|
||||
- Optional environment variables.
|
||||
- No process tracking or lifecycle supervision.
|
||||
|
||||
Smoothie does not wait for Blender and does not receive exit status.
|
||||
|
||||
---
|
||||
|
||||
## 8. Data Model
|
||||
|
||||
### 8.1 `Settings`
|
||||
|
||||
```js
|
||||
{
|
||||
libraryDir: string, // user-visible library root
|
||||
defaultPlatform: 'windows' | 'linux' | 'macos',
|
||||
defaultArch: string, // host architecture string
|
||||
keepDownloadedArchives: boolean, // default false
|
||||
autoCreateShortcuts: boolean, // default true (Linux desktop entries)
|
||||
theme: 'light' | 'dark', // default 'light'
|
||||
closeToTray: boolean // default true
|
||||
}
|
||||
```
|
||||
|
||||
Defaults are derived at runtime from `appDataDir()`, `plugin-os` platform, and `plugin-os` architecture. Missing persisted fields are patched from defaults on load.
|
||||
|
||||
### 8.2 `BlenderRelease`
|
||||
|
||||
```js
|
||||
{
|
||||
original: string, // original href, e.g. "Blender4.2/"
|
||||
version: string, // major series, e.g. "4.2"
|
||||
fullPath: string, // absolute folder URL
|
||||
lastModified: null, // reserved
|
||||
links: FileLink[]
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 `FileLink`
|
||||
|
||||
```js
|
||||
{
|
||||
version: string, // full version, e.g. "4.2.1"
|
||||
downloadUrl: string,
|
||||
platform: 'linux' | 'windows' | 'macos',
|
||||
architecture: 'x64' | 'arm64',
|
||||
fileExtension: 'tar.xz' | 'zip' | ...,
|
||||
status: 'pending' | 'downloading' | 'completed' | 'error',
|
||||
percent: number
|
||||
}
|
||||
```
|
||||
|
||||
### 8.4 `blenderVersion`
|
||||
|
||||
```js
|
||||
{
|
||||
version: string,
|
||||
path: string,
|
||||
executable: string,
|
||||
platform: string,
|
||||
favourite: boolean,
|
||||
templates: string[] // template folder names
|
||||
}
|
||||
```
|
||||
|
||||
`installDate` is declared in the JSDoc but not populated.
|
||||
|
||||
### 8.5 `DownloadTask`
|
||||
|
||||
```js
|
||||
{
|
||||
version: string,
|
||||
loaded: number,
|
||||
total: number,
|
||||
percent: number
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Storage Layout
|
||||
|
||||
### 9.1 Library
|
||||
|
||||
`BASE_LIBRARY_DIR = "smoothie-library"` is created under the configured `libraryDir`.
|
||||
|
||||
```text
|
||||
<libraryDir>/
|
||||
└── smoothie-library/
|
||||
├── blender/
|
||||
│ └── <version>/
|
||||
│ ├── blender # or blender.exe
|
||||
│ ├── icon.png # generated
|
||||
│ ├── banner.png # generated
|
||||
│ ├── <major.minor>/
|
||||
│ │ └── scripts/
|
||||
│ │ └── startup/
|
||||
│ │ └── bl_app_templates_system/
|
||||
│ │ └── <Template>/
|
||||
│ │ ├── startup.blend
|
||||
│ │ └── banner.png
|
||||
│ └── ...
|
||||
└── config/
|
||||
└── <version>/ # BLENDER_USER_RESOURCES
|
||||
```
|
||||
|
||||
The `config/<version>` folder is what makes installations independent. Blender writes preferences, startup files, add-ons, and caches there instead of the user's global Blender configuration.
|
||||
|
||||
### 9.2 OS artifacts
|
||||
|
||||
| Platform | Artifact | Location |
|
||||
| --- | --- | --- |
|
||||
| Linux | Desktop entry | `$LOCALDATA/applications/blender-smoothie-<version>.desktop` |
|
||||
| Windows | No shortcut creation implemented | — |
|
||||
| macOS | No shortcut creation implemented | — |
|
||||
|
||||
Desktop entries embed:
|
||||
|
||||
- `BLENDER_USER_RESOURCES=<library>/config/<version>`
|
||||
- `BLENDER_CUSTOM_SPLASH_BANNER=<version>/banner.png`
|
||||
- `Icon=<version>/icon.png`
|
||||
- `MimeType=application/x-blender`
|
||||
|
||||
### 9.3 Settings and favorites
|
||||
|
||||
Both are managed by `plugin-store`:
|
||||
|
||||
- `settings.json` — scalar key/value pairs.
|
||||
- `versions.json` — version string → favorite boolean.
|
||||
|
||||
These files live in the Tauri-managed application data/config location, not in the portable library. Moving machines requires copying the library separately from these preferences.
|
||||
|
||||
---
|
||||
|
||||
## 10. Key Workflows
|
||||
|
||||
### 10.1 Release discovery
|
||||
|
||||
1. Fetch the release index.
|
||||
2. Parse anchors ending in `/` and matching `Blender<number><suffix>/`.
|
||||
3. Ignore versions at or below 2.78.
|
||||
4. Fetch every qualifying folder concurrently.
|
||||
5. Parse `blender-<version>-<platform>-<arch>.<ext>` filenames.
|
||||
6. Group by full version and select the entry matching the configured platform/architecture.
|
||||
7. Sort major series and minor versions descending.
|
||||
8. Publish results to `blenderReleases`.
|
||||
|
||||
### 10.2 Install
|
||||
|
||||
1. User selects a `FileLink` in the Download tab.
|
||||
2. `downloadBlenderVersion` resolves the target directory.
|
||||
3. `plugin-upload` streams the archive into that directory.
|
||||
4. Progress updates are written to `downloadTasksStore` keyed by version.
|
||||
5. `invoke('extract_archive')` runs extraction off the UI thread.
|
||||
6. The single top-level directory is flattened.
|
||||
7. The archive is deleted unless `keepDownloadedArchives` is true.
|
||||
8. `getInstalledVersions()` rescans the library and regenerates artifacts.
|
||||
9. The release row flips to "Installed".
|
||||
|
||||
### 10.3 Launch
|
||||
|
||||
1. User clicks the version tag, double-clicks a card, or launches a template.
|
||||
2. Smoothie verifies the executable with `stat`.
|
||||
3. It computes the isolated config path.
|
||||
4. It selects the banner: version banner or template banner.
|
||||
5. For a template, it passes `--app-template <name>`.
|
||||
6. It invokes `launch_binary` with:
|
||||
- `BLENDER_USER_RESOURCES=<library>/config/<version>`
|
||||
- `BLENDER_CUSTOM_SPLASH_BANNER=<banner.png>`
|
||||
|
||||
### 10.4 Templates
|
||||
|
||||
- Template directories live inside the installed Blender build, not the config directory.
|
||||
- A template is valid when it contains `startup.blend`.
|
||||
- Built-in directories (`Storyboarding`, `2D_Animation`, `Sculpting`, `VFX`, `Video_Editing`) are ignored.
|
||||
- Dropping a `.blend` copies it into a new template directory as `startup.blend`.
|
||||
- Template deletion removes the directory recursively.
|
||||
- Template banners are generated lazily.
|
||||
|
||||
### 10.5 Favorites and selection
|
||||
|
||||
- Favorites are toggled in memory and persisted immediately.
|
||||
- On scan, the previously selected version is refreshed by version string.
|
||||
- If nothing is selected:
|
||||
- Prefer the first favorite.
|
||||
- Otherwise select the first installed version.
|
||||
|
||||
### 10.6 Delete
|
||||
|
||||
1. Remove the version directory recursively.
|
||||
2. Remove the favorite entry.
|
||||
3. Remove the Linux desktop entry.
|
||||
4. Reset any download-progress entry.
|
||||
5. Update the in-memory installed list.
|
||||
|
||||
### 10.7 Set default
|
||||
|
||||
`registerVersion` invokes `launch_binary` with `--register`, letting Blender register `.blend` file associations. The result of the invocation is not awaited or reported.
|
||||
|
||||
### 10.8 Move library
|
||||
|
||||
Implemented in `Settings.svelte`:
|
||||
|
||||
1. Validate the destination is not inside the current library and vice versa.
|
||||
2. Optionally confirm replacement of an existing destination.
|
||||
3. Remove Linux desktop entries for current versions.
|
||||
4. Attempt `rename`; fall back to recursive copy + delete for cross-device moves.
|
||||
5. Persist the new `libraryDir`.
|
||||
6. Rescan and recreate shortcuts.
|
||||
|
||||
### 10.9 Tray and window lifecycle
|
||||
|
||||
- Closing the window hides it when `closeToTray` is true.
|
||||
- Tray menu toggles visibility and can destroy the window.
|
||||
- Window size and position are persisted by `window-state`.
|
||||
- Default window is 1024×576, resizable, bounded to 512×288 minimum and 1920×1080 maximum.
|
||||
|
||||
---
|
||||
|
||||
## 11. Platform Integration
|
||||
|
||||
### Linux (primary, most complete)
|
||||
|
||||
- `.desktop` entries created and removed automatically.
|
||||
- Orphaned entries cleaned up during scans.
|
||||
- `BLENDER_USER_RESOURCES` and splash banner configured in the entry.
|
||||
- Archives typically `.tar.xz`.
|
||||
|
||||
### Windows (partial)
|
||||
|
||||
- Executable name: `blender.exe`.
|
||||
- Archives typically `.zip`.
|
||||
- No Start Menu or desktop shortcut creation.
|
||||
- File association delegated to `blender --register`.
|
||||
|
||||
### macOS (partial)
|
||||
|
||||
- Platform mapping is `darwin → macos`.
|
||||
- Download links use the `macos` filename segment.
|
||||
- Executable discovery currently expects `<version>/blender`, while a standard macOS bundle places it at `Blender.app/Contents/MacOS/Blender`.
|
||||
- No `.app` or Dock integration.
|
||||
|
||||
### Architecture handling
|
||||
|
||||
- Download selection maps `x86_64 → x64` and `arm64 → arm64`.
|
||||
- Settings detection returns the raw OS architecture value.
|
||||
- The two mappings are not fully aligned (for example, `aarch64` is not normalized to `arm64`), which can cause missing download matches on ARM Linux.
|
||||
|
||||
---
|
||||
|
||||
## 12. Security and Capabilities
|
||||
|
||||
### Current posture
|
||||
|
||||
- The webview loads only bundled local content.
|
||||
- HTTP is scoped to `https://*.blender.org`.
|
||||
- Filesystem access is scoped to app directories plus persisted paths granted through the directory picker.
|
||||
- The updater uses a Minisign public key and signed artifacts.
|
||||
|
||||
Relevant capability entry points in `src-tauri/capabilities/desktop.json`:
|
||||
|
||||
- Window show/hide/close/destroy.
|
||||
- Dialog open and confirm.
|
||||
- App-scoped read/write, recursive.
|
||||
- FS scope for `$LOCALDATA/applications`.
|
||||
- Shell spawn.
|
||||
- HTTP for `*.blender.org`.
|
||||
- Store, OS, notification, upload, updater.
|
||||
|
||||
### Security considerations
|
||||
|
||||
1. **CSP is disabled** (`app.security.csp = null`). Tightening it is advisable now that the frontend is fully bundled.
|
||||
2. **`launch_binary` accepts an arbitrary path and arguments** from the frontend. The trust boundary is the local webview, but an allowlist or explicit launch token would reduce blast radius if UI content were ever compromised.
|
||||
3. **No checksum verification** is performed on downloaded Blender archives. `.md5` and `.sha256` files are explicitly ignored during parsing.
|
||||
4. **Archive extraction** uses `mangled_name()` for ZIP entries, which normalizes traversal paths; TAR extraction relies on the `tar` crate's safety checks.
|
||||
5. **Persisted scope** keeps directory grants across restarts; this is intentional for library access but should be reviewed when the library moves.
|
||||
6. **Updater artifacts** must remain signed; the private key is provided only to CI.
|
||||
|
||||
---
|
||||
|
||||
## 13. Build, Packaging, and Release
|
||||
|
||||
### Local development
|
||||
|
||||
```bash
|
||||
bun install
|
||||
bun run tauri dev
|
||||
```
|
||||
|
||||
- Vite serves on `localhost:1420`.
|
||||
- `src-tauri` is excluded from Vite file watching.
|
||||
- Tauri runs `bun run dev` before development and `bun run build` before production builds.
|
||||
|
||||
### Frontend build
|
||||
|
||||
- `adapter-static` prerenders the single route.
|
||||
- `ssr = false` because Tauri has no Node server.
|
||||
- Output is written to `build/` and bundled by Tauri.
|
||||
|
||||
### Packaging
|
||||
|
||||
- `bundle.targets = "all"`.
|
||||
- `createUpdaterArtifacts = true`.
|
||||
- Icons for Linux, Windows, macOS, Android, and iOS are present, though desktop is the supported target.
|
||||
|
||||
### Release workflow
|
||||
|
||||
`.gitea/workflows/build.yml`:
|
||||
|
||||
- Triggers on pushes to `master`.
|
||||
- Builds on `ubuntu-22.04`.
|
||||
- Installs Bun, Rust stable, and Linux system dependencies.
|
||||
- Invokes a Tauri action that publishes a draft Gitea release tagged `app-v__VERSION__`.
|
||||
- Signs update artifacts via CI secrets.
|
||||
- Uploads artifacts to `https://git.floatingpoint.ch`.
|
||||
|
||||
### Update configuration
|
||||
|
||||
- Endpoint: `https://git.floatingpoint.ch/valerio/smoothie/releases/download/latest/update.json`.
|
||||
- Signed with a Minisign key embedded in `tauri.conf.json`.
|
||||
|
||||
---
|
||||
|
||||
## 14. Reliability, Concurrency, and Error Handling
|
||||
|
||||
### Current behavior
|
||||
|
||||
- Service functions generally return `{ success, message, path? }` rather than throwing to the UI.
|
||||
- Known recoverable failures are logged with `console.warn`.
|
||||
- Downloads are tracked by version string, so two concurrent installs of the same version would collide.
|
||||
- There is no global install queue or lock.
|
||||
- Partial extraction leaves files behind; only a completely empty target directory is cleaned after a download failure.
|
||||
- Store writes are immediate but not transactional across multiple keys.
|
||||
- Installed-version scanning treats the filesystem as authoritative and tolerates malformed version folders by skipping them.
|
||||
- A missing executable causes the version folder to be skipped, with removal commented out.
|
||||
|
||||
### Recommended invariants for future work
|
||||
|
||||
1. One active operation per version.
|
||||
2. Install state machine: `idle → downloading → verifying → extracting → finalizing → installed | failed`.
|
||||
3. Atomic finalize: extract to a temporary directory, then rename into place.
|
||||
4. Cleanup of partial state on failure.
|
||||
5. Explicit cancellation through an operation ID.
|
||||
|
||||
---
|
||||
|
||||
## 15. Known Limitations and Architectural Risks
|
||||
|
||||
| Area | Issue | Impact |
|
||||
| --- | --- | --- |
|
||||
| Release discovery | HTML scraping with regexes tied to current site markup | Breaks if Blender changes its index or filenames |
|
||||
| Release discovery | Fetches every major-version folder on every launch; no cache | Startup latency and N+1 requests |
|
||||
| Downloads | No checksum or signature verification | Supply-chain risk |
|
||||
| Downloads | No cancellation despite a "Cancel" label | Confusing UX and orphaned work |
|
||||
| Extraction | Not atomic; failure can leave partial installs | Library can contain unusable versions |
|
||||
| Concurrency | No per-version lock or queue | Duplicate downloads and progress collisions |
|
||||
| macOS | Executable discovery does not handle `.app` bundles | macOS installations are likely unusable |
|
||||
| Architecture | `aarch64` is not normalized to `arm64` consistently | ARM Linux builds may not appear |
|
||||
| Launch | Detached processes are not tracked | No exit status, crash detection, or running indicator |
|
||||
| Registration | `--register` result is not awaited | UI may report success without confirmation |
|
||||
| Portability | Settings and favorites live outside the library | Full setup is not completely portable |
|
||||
| Security | CSP disabled; arbitrary binary launch command | Hardening opportunity |
|
||||
| Testing | No automated tests or test harness | Regressions rely on manual verification |
|
||||
| Versioning | `package.json` and `tauri.conf.json` are `0.1.2`; `Cargo.toml` is `0.1.0` | Inconsistent release metadata |
|
||||
| Version scan | `exists()` is not awaited in one guard | Incorrect truthiness for missing library paths |
|
||||
| Window state | Plugin is registered twice | Redundant initialization |
|
||||
|
||||
---
|
||||
|
||||
## 16. Extension Points
|
||||
|
||||
The architecture anticipates the following extensions without structural change:
|
||||
|
||||
- **Additional download sources / mirrors.** Add a source adapter that produces `BlenderRelease[]`.
|
||||
- **Checksum verification.** Add a verification step between download and extraction.
|
||||
- **Install queue.** Introduce an operation manager keyed by version, with cancellation and persisted job state.
|
||||
- **Platform-specific shortcut adapters.** Extend the current Linux-only `.desktop` logic with Windows `.lnk` and macOS `.app`/alias handling.
|
||||
- **Typed domain layer.** Migrate JSDoc types to TypeScript and define a typed IPC contract.
|
||||
- **Library manifest.** Add an optional manifest for migration, integrity checks, and machine-readable provenance while keeping the filesystem authoritative.
|
||||
- **Plugin management.** Extend the library model with per-version add-on directories and enable/disable state.
|
||||
- **Template metadata.** Store display names, descriptions, and thumbnails alongside `startup.blend`.
|
||||
|
||||
---
|
||||
|
||||
## 17. Architectural Invariants
|
||||
|
||||
These should remain true unless the architecture is deliberately revised:
|
||||
|
||||
1. No installed-version state is stored outside the library except user preferences and favorites.
|
||||
2. Each Blender launch receives a version-specific `BLENDER_USER_RESOURCES`.
|
||||
3. Every installed version is discoverable by scanning `<library>/smoothie-library/blender`.
|
||||
4. Rust remains a capability layer, not a business-logic layer.
|
||||
5. Release discovery and installation are separate stages, joined by `FileLink`.
|
||||
6. UI state flows from stores; components do not maintain duplicate authoritative copies.
|
||||
7. Network access remains limited to Blender hosts and the updater endpoint.
|
||||
8. The library remains relocatable and transferable as a directory tree.
|
||||
|
||||
---
|
||||
|
||||
## 18. Glossary
|
||||
|
||||
| Term | Meaning |
|
||||
| --- | --- |
|
||||
| Library | `<libraryDir>/smoothie-library`, the portable root containing builds and configs |
|
||||
| Build / Version | An extracted Blender installation under `blender/<version>` |
|
||||
| Template | A folder containing `startup.blend`, launchable via `--app-template` |
|
||||
| Favorite | User-marked version persisted in `versions.json` |
|
||||
| Default version | The build registered with the OS via `blender --register` |
|
||||
| Config isolation | Per-version `BLENDER_USER_RESOURCES` directory |
|
||||
| Splash banner | Generated PNG passed through `BLENDER_CUSTOM_SPLASH_BANNER` |
|
||||
Reference in New Issue
Block a user