Add architecture specification document
publish / publish-tauri (, ubuntu-22.04) (push) Failing after 4m52s

This commit is contained in:
valerio
2026-10-19 14:25:17 +02:00
parent 7c84ff3f74
commit dafc7fef97
+798
View File
@@ -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` |