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

31 KiB
Raw Blame History

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

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:

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

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 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:

    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:

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

{
  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[]
}
{
  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.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

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.
  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