Files
smoothie/SPEC.md
T
valerio dafc7fef97
publish / publish-tauri (, ubuntu-22.04) (push) Failing after 4m52s
Add architecture specification document
2026-10-19 14:25:17 +02:00

799 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` |