31 KiB
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
- Portability. All versions, configuration, and templates live under one library directory that can be moved, backed up, or copied.
- Isolation. Each Blender build reads and writes its own
BLENDER_USER_RESOURCESdirectory. - Low-friction management. Install, launch, favorite, delete, and set default from one GUI.
- Desktop-native behavior. Tray support, OS shortcuts, file associations, signed auto-updates.
- 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
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.orgfor 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:
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
- Filesystem-first. The library directory is the source of truth for installed versions. There is no installation database to drift out of sync.
- Stateless UI over persistent services. Components read Svelte stores; service modules perform I/O and refresh stores.
- Thin Rust boundary. Rust handles archive extraction and process spawning. All orchestration and file layout remain in JavaScript.
- Explicit per-version isolation. Launches inject
BLENDER_USER_RESOURCESpointing at a version-specific config folder. - Idempotent maintenance. Missing icons, banners, templates, and shortcuts are regenerated when versions are rescanned.
- Graceful degradation. Missing executables are skipped; failed shortcuts and banners log warnings rather than aborting startup.
5. Repository Layout
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:
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
Settingstype 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 than2.78. -
Fetches each version folder in parallel and parses filenames of the form:
blender-<version>-<platform>-<arch>.<extension> -
Filters to the selected platform and architecture.
-
Sorts releases with a numeric dot-segment comparator.
-
Emits
BlenderRelease[]andFileLink[].
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:
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 buildblenderVersionobjects. - Resolve the executable for the current platform.
- Load templates and ensure template directories exist.
- Generate missing icons and banners.
- Create/remove Linux
.desktopentries. - 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
selectedVersionStoreafter scans.
file_utils.js
- Canvas-based icon generation with the embedded base icon and
Europa-Monofont. - Canvas/figlet-based 600×90 banner generation.
- Directory creation, recursive copy, move, and existence checks.
- Single-top-level-directory flattening.
.blenddrag-and-drop import as templates.
trayicon.js
- Creates a tray icon with "Show/Hide" and "Quit".
- Intercepts window close and hides to tray when
closeToTrayis enabled.
notification.js
- Tiny writable store consumed by
+page.svelteand rendered throughPopup.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:
- Multi-part suffixes:
.tar.xz/.txz,.tar.gz/.tgz,.tar.bz2/.tbz2/.tbz. - Single suffix:
.tar,.zip. - 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,stderrset 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
{
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
{
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
{
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
{
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
{
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.
<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.pngIcon=<version>/icon.pngMimeType=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
- Fetch the release index.
- Parse anchors ending in
/and matchingBlender<number><suffix>/. - Ignore versions at or below 2.78.
- Fetch every qualifying folder concurrently.
- Parse
blender-<version>-<platform>-<arch>.<ext>filenames. - Group by full version and select the entry matching the configured platform/architecture.
- Sort major series and minor versions descending.
- Publish results to
blenderReleases.
10.2 Install
- User selects a
FileLinkin the Download tab. downloadBlenderVersionresolves the target directory.plugin-uploadstreams the archive into that directory.- Progress updates are written to
downloadTasksStorekeyed by version. invoke('extract_archive')runs extraction off the UI thread.- The single top-level directory is flattened.
- The archive is deleted unless
keepDownloadedArchivesis true. getInstalledVersions()rescans the library and regenerates artifacts.- The release row flips to "Installed".
10.3 Launch
- User clicks the version tag, double-clicks a card, or launches a template.
- Smoothie verifies the executable with
stat. - It computes the isolated config path.
- It selects the banner: version banner or template banner.
- For a template, it passes
--app-template <name>. - It invokes
launch_binarywith: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
.blendcopies it into a new template directory asstartup.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
- Remove the version directory recursively.
- Remove the favorite entry.
- Remove the Linux desktop entry.
- Reset any download-progress entry.
- 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:
- Validate the destination is not inside the current library and vice versa.
- Optionally confirm replacement of an existing destination.
- Remove Linux desktop entries for current versions.
- Attempt
rename; fall back to recursive copy + delete for cross-device moves. - Persist the new
libraryDir. - Rescan and recreate shortcuts.
10.9 Tray and window lifecycle
- Closing the window hides it when
closeToTrayis 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)
.desktopentries created and removed automatically.- Orphaned entries cleaned up during scans.
BLENDER_USER_RESOURCESand 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
macosfilename segment. - Executable discovery currently expects
<version>/blender, while a standard macOS bundle places it atBlender.app/Contents/MacOS/Blender. - No
.appor Dock integration.
Architecture handling
- Download selection maps
x86_64 → x64andarm64 → arm64. - Settings detection returns the raw OS architecture value.
- The two mappings are not fully aligned (for example,
aarch64is not normalized toarm64), 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
- CSP is disabled (
app.security.csp = null). Tightening it is advisable now that the frontend is fully bundled. launch_binaryaccepts 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.- No checksum verification is performed on downloaded Blender archives.
.md5and.sha256files are explicitly ignored during parsing. - Archive extraction uses
mangled_name()for ZIP entries, which normalizes traversal paths; TAR extraction relies on thetarcrate's safety checks. - Persisted scope keeps directory grants across restarts; this is intentional for library access but should be reviewed when the library moves.
- Updater artifacts must remain signed; the private key is provided only to CI.
13. Build, Packaging, and Release
Local development
bun install
bun run tauri dev
- Vite serves on
localhost:1420. src-tauriis excluded from Vite file watching.- Tauri runs
bun run devbefore development andbun run buildbefore production builds.
Frontend build
adapter-staticprerenders the single route.ssr = falsebecause 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
- One active operation per version.
- Install state machine:
idle → downloading → verifying → extracting → finalizing → installed | failed. - Atomic finalize: extract to a temporary directory, then rename into place.
- Cleanup of partial state on failure.
- 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
.desktoplogic with Windows.lnkand 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:
- No installed-version state is stored outside the library except user preferences and favorites.
- Each Blender launch receives a version-specific
BLENDER_USER_RESOURCES. - Every installed version is discoverable by scanning
<library>/smoothie-library/blender. - Rust remains a capability layer, not a business-logic layer.
- Release discovery and installation are separate stages, joined by
FileLink. - UI state flows from stores; components do not maintain duplicate authoritative copies.
- Network access remains limited to Blender hosts and the updater endpoint.
- 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 |