Skip to content

v2.16.0#1657

Merged
mohnjiles merged 322 commits into
mainfrom
main
Jun 8, 2026
Merged

v2.16.0#1657
mohnjiles merged 322 commits into
mainfrom
main

Conversation

@mohnjiles

Copy link
Copy Markdown
Contributor

Added

New Feature: 🧪 Image Lab - Conversational Image Generation for ComfyUI

  • We've added a brand new conversational interface for image generation! Image Lab lets you iterate on images naturally through chat, rather than just one-off prompts.
    • Local-First Power: Native support for Flux Kontext, Qwen Image Edit, and the Apache 2.0-licensed Flux.2 Klein running entirely locally via your ComfyUI backend.
    • Smart Setup: Stability Matrix automatically detects and helps you download the specific models and LoRAs needed for these local workflows.
    • Interactive Tools: Drag-and-drop image inputs, use the built-in annotation tool to draw on images, and keep persistent conversation history.
    • Cloud Option: Includes optional support for Nano Banana (Gemini 3 Pro / 2.5) and Nano Banana 2 (Gemini 3.1 Flash) for users who want to leverage external reasoning models.
  • Added Regional Prompting addon to Inference - paint detailed masks to apply different prompts, strengths, and settings to specific regions of your image
    • Multi-layer mask editor with Photoshop-style interface for managing layers with independent masks, prompts, colors, and opacity
    • Professional brush tools: freehand brush/eraser with pressure sensitivity, rectangle/ellipse shapes with fill/stroke modes, paint bucket flood fill
    • Brush feathering/softness control for smooth, blended mask edges (0 = hard edge, 1 = soft/blurred)
    • Per-layer prompt and strength controls, export/import masks as PNG, duplicate layers, image reference layers for tracing
    • GPU-accelerated rendering with compact gzip-compressed metadata serialization
  • Added official Inference support for the Z-Image (Base + Turbo), Anima, and Flux.2 model architectures — workflow-appropriate text encoders, latent shapes, schedulers, and model sampling (AuraFlow for Z-Image, Flux2Scheduler for Flux.2) are wired up automatically across Text-to-Image and Image-to-Image
  • Added an Inference Workflow selector to the Model card with profiles for Default/Checkpoint, Flux, Flux.2, Z-Image Base/Turbo, Anima, HiDream, and Custom
    • Auto (default) detects the workflow from the model's CivitAI metadata, with filename fallbacks for models without metadata, and shows the resolved profile inline below the selector
    • Sparkle button applies recommended sampler / scheduler / steps / CFG presets for the active workflow — e.g. res_multistep / simple / 8 steps / CFG 1 for Z-Image Turbo, er_sde / simple / 30 steps / CFG 4 for Anima, euler / 20 steps / CFG 5 for Flux.2
    • Choosing a non-Auto profile reveals a manual Encoder Type selector for advanced overrides (e.g. running Z-Image Turbo with the sd3 encoder)
    • Opening the model browser from the Model card pre-filters to the workflow's compatible base models, without overwriting your saved picker filters
  • Added CivArchive model browser with details page, image viewer, version selector, trigger words, and in-app downloads with tracked progress
  • Added a checkpoint organizer for previewing and reorganizing local models using connected metadata-driven folder and filename patterns (requested in #280, #424)
  • Added a new Model Picker dialog for Inference with grid/list views, search, filtering, and NSFW overlay
  • Added browse buttons to all model dropdowns in Inference (Model, Refiner, VAE, Text Encoders, CLIP Vision)
  • Added an inline search box to model combo box dropdowns with fuzzy matching
  • Added a Source button in the Inference SamplerCard that one-click matches your generation Width/Height to the loaded source image — available in Image-to-Image whenever a source image is selected
  • Added popularity counts to booru-style tag completions in the prompt editor; descriptions now show entries like 12.3K · artist so the more common tags are easier to spot at a glance
  • Added a settings gear button to the CivitAI browser's Base Models filter flyout that jumps straight to the base model filter configuration in Settings
  • Added er_sde and res_multistep to the Inference sampler list
  • Added stable_diffusion, flux2, and lumina2 Encoder Type options for UNet workflows
  • Added a Bitsandbytes NF4 launch option to Stable Diffusion WebUI Forge - Neo for low-bit (--bnb) inference
  • Added an Activity center: the sidebar download panel now has a Notifications tab alongside In Progress. Toasts are clickable — jumping to the downloaded folder, the originating page (e.g. Inference), or the activity panel — and persist into a session notification history (every notification is recorded, even ones suppressed by your settings) with read/unread indicators and a combined unread + active-download badge on the sidebar item
  • Added an "Always Show Scrollbars" toggle under Settings → Appearance. Defaults on — vertical scrollbars stay visible at their full thickness and reserve real layout space instead of fading to a thin overlay-style bar that only thickens on hover. Toggle off to restore Avalonia's classic auto-hide behavior. Single-line numeric inputs (e.g. SamplerCard Width/Height) keep their auto-hide regardless so spin-buttons aren't followed by a phantom bar
  • Added new shared model folder categories — Style Models, Audio Encoders, Model Patches, and Background Removal — for ComfyUI's style_models, audio_encoders, model_patches, and background_removal directories. Models in these folders are now indexed and symlinked alongside everything else (e.g. Flux Redux / B-Lora style models, audio encoders for video/audio workflows, BiRefNet background-removal models)
  • Added Intel GPU support for ComfyUI
  • Added "Run Python Command" option to the package card's 3-dots menu for running arbitrary Python code in the package's virtual environment
  • Added a recoverable error dialog for UI thread exceptions, with option to continue instead of exiting
  • Added enable/disable toggle for environment variables in Settings, allowing variables to be temporarily disabled without deleting them

Changed

  • Promoted the Encoder Type selector in the Inference Model card out of Advanced Options up to the main card body, so it's visible whenever a non-Auto workflow profile is active (and always when Custom is selected)
  • Tidied up the Inference SamplerCard dimensions section — Source/Presets actions are shown as labeled buttons below the dimension row
  • The Inference checkpoint dropdown no longer resets its scroll position every time the model list refreshes. The refresh now applies a single combined (local + remote) diff to the underlying source cache, rather than first resetting to local-only and then re-adding remote entries — which previously caused the open dropdown to scroll back to the top
  • Local model autocomplete in the prompt editor now uses substring matching instead of prefix-only — typing any part of a model's filename surfaces it, with names that start with your search still ranked first
  • Single-encoder UNet workflows (Anima, Flux.2, Z-Image) now use the matching CLIPLoader instead of assuming Flux-style dual encoders
  • The CivitAI model details page now collapses the preview-image area and shows a small "No preview images available" hint when a model has no images to display, letting the description card take the full vertical space instead of leaving a large empty region above it
  • Improved the Gemini API error message in Image Lab when the API returns 401/403 to point users at Google's API key restriction policy (which starts blocking unrestricted keys on June 19 2026)
  • Improved safetensor checkpoint classification to correctly detect UNet-only models for Wan Video, HiDream, Z-Image, Hunyuan3D, and diffusers-format Flux architectures, ensuring they are routed to the DiffusionModels folder
  • GGUF checkpoint downloads now go directly to the DiffusionModels folder instead of StableDiffusion
  • Updated AI-Toolkit to install torch 2.9.1 / torchvision 0.24.1 / torchaudio 2.9.1 from the cu128 index to match upstream (ostris/ai-toolkit), with a cu126 fallback for legacy NVIDIA GPUs; also pin numpy to 1.26.4 to avoid a numpy 2.x ABI break in scipy/diffusers that crashed training runs
  • Pinned kohya_ss torch to 2.7.0 / torchvision 0.22.0 (cu128) to match upstream's requirements_pytorch_windows.txt instead of resolving an untested latest, keeping the cu126 legacy-GPU fallback
  • Pinned reForge torch to 2.9.0 to match upstream (modules/launch_utils.py)
  • Updated ComfyUI installs to cu130 (cu126 for legacy NVIDIA GPUs) / rocm7.2 torch indexes depending on GPU
  • Upgraded the bundled Visual C++ redistributable from 2015–2019 (v16) to 2015–2022 (v17, build 14.40.33810+), required by modern native dependencies such as PyTorch and ONNX Runtime
  • Video files can now be opened directly from the Output browser
  • Videos will now appear with thumbnails in the Output browser
  • Configured portable Git to suppress detached HEAD advice messages

Fixed

  • Fixed Inference text encoder selections being cleared when navigating away from and back to the Inference tab — encoder slots now ignore the transient null the model dropdown reports while its list refreshes
  • Fixed UNet-only Inference model selection sometimes clearing during model-list refreshes — text encoder slots no longer disappear after generating, cancelling a generation, or reconnecting to ComfyUI
  • Fixed #1585 - FluxGym installs/updates pulling an incompatible transformers version — installs now pin transformers==4.54.1 and exclude it from the default requirements pass
  • Fixed #1641 - Cogstudio failing to set up its inference/gradio_composite_demo directory when the parent path didn't already exist
  • Fixed #1650 - ComfyUI-Manager extension installs failing on Linux with File not found: venv/uv-build-constraints.txt by no longer leaking the relative build-constraints path into the running package's environment
  • Fixed #1645 - Strix Halo / Radeon 8060S and other Display controller-class integrated GPUs not appearing in the GPU list on Linux
  • Fixed #1643 - package install and launch failures when sitecustomize.py or its compiled bytecode was corrupted by external software (e.g. some antivirus suites); the file now self-heals when out of date and its startup actions can no longer abort interpreter startup
  • Fixed the CivitAI model browser requiring two clicks of Search to show results when all base-model filters were selected — a leftover post-response sanity check from the old single-select base-model UI was rejecting the response the first time, requiring a second search to surface the cached results
  • Fixed CivitAI model cards showing "No versions available" when clicked for some models (typically recently uploaded or updated) even though the model has downloadable versions on the website — the app now retries with a different lookup path when the initial response comes back missing version data
  • Fixed $#1234 and civitai.com/models/1234 URL searches returning zero results for some models that exist and are downloadable on the website — the app now retries via a per-model lookup when the batch search misses a requested ID
  • Fixed $#1234 searches with non-LORA / non-Checkpoint targets returning no results when the Model Type dropdown wasn't set to All — ID searches intentionally bypass the type and base-model filters in the request, but the post-response check was still rejecting the returned model when its type didn't match the dropdown
  • Fixed clicking a CivitAI model card with an empty version list appearing to do nothing for ~1–2s while the recovery round-trip runs — the clicked card now shows a "Loading..." state during the recovery, and the recovered version data is cached on the card so subsequent clicks are instant
  • Fixed "Invalid download link" error when using the browser extension
  • Fixed downloaded checkpoint going to StableDiffusion folder when a saved download preference existed, even for GGUF files that should always go to DiffusionModels
  • Fixed potential crash when adding metadata to malformed or non-PNG image data in Inference
  • Fixed non-Latin-1 characters (e.g. Japanese, Chinese, Korean, emoji) in image generation parameters being stored in PNG tEXt chunks, violating the PNG specification and causing character corruption (mojibake) in standard-compliant parsers. Non-Latin-1 content now uses spec-compliant iTXt chunks with proper UTF-8 encoding (#1535)
  • Fixed batch notification firing when only one image is generated

Security

  • Updated the bundled 7-Zip binaries (Windows, Linux, macOS) to 26.01, which includes the fix for the NTFS heap buffer overflow CVE-2026-48095 (GitHub Security Lab GHSL-2026-140, CVSS 8.8) and brings years of accumulated upstream security fixes — the Windows binary in particular had been pinned at the 2018 18.01 release
  • Package updates now re-run the prerequisite setup step (as installs already do), so the bundled 7-Zip binary is refreshed on update instead of only on a fresh package install

Supporters

🌟 Visionaries

An enormous thank you to our incredible Visionaries: Waterclouds, bluepopsicle, Ibixat, Droolguy, snotty, LG, whudunit, MrMxyzptlk12836, Psilocyfer18731, KalAbaddon, and moon_milky2843! This was a huge release, and every bit of it rests on your generosity. Whether you've been cheering us on for years or only just joined, having you in our corner is what makes all of this possible. We're so grateful for you. 💛

🚀 Pioneers

And what a Pioneer crew this release! A heartfelt thank you to the regulars who keep showing up for us: Szir777, [USA]TechDude, SinthCore, Jisuren, Tigon, jweg79, rwx14662, Hurbie53, ahnhj.al, drew.lukas, Tuskaruho, Cjloha, Alligator1907, Bitti, damianpointdexter, Ghislain G, and tmdcks! Your steady support release after release is what keeps us at the keyboard. And the warmest of welcomes to our newest Pioneers: CommissarGiygas16050, qob97515211, bastardofbethlehem, and Zombop — we're thrilled you've joined us, and we can't wait to get to know you! (And to our anonymous Pioneer out there too — our thanks reaches you. 💛)

…and tool accessibility, remove unused buttons, and enhance property bindings
…ource disposal in LayeredMaskEditor and PaintCanvas
… in ComfyUI, also hide civitai browser star ratings if no rating is available
Add warnings for legacy Python in InvokeAI and NVIDIA driver versions…
…eel installations & fix some issues from github
…ic, update uv, update portablegit version for windows, and enhance loading and error handling for safetensor metadata parsing
…oved compatibility with torch/sage/triton/nunchaku
Introduce a new browsable Model Picker dialog with grid/list views, search, CivitAI metadata, and filter persistence.
Add browse buttons to all Inference model dropdowns (Model, Refiner, VAE, Text Encoders, CLIP Vision) across ModelCard and WanModelCard.

Also includes: BetterComboBox search cache invalidation on ItemsSource change, configurable search watermark, shared DetailedSearchText property on HybridModelFile, and MinDialogHeight property fix
… commands affect the clicked layer instead of currently selected layer
mohnjiles and others added 26 commits May 27, 2026 18:58
Apply the same fix from the previous Klein commit to the two providers
Klein was cloned from. Their cancellation callbacks were spinning up a
5s timer-backed CancellationTokenSource and fire-and-forgetting it
without disposing — chain a .ContinueWith(Dispose) onto the interrupt
task so the timer cleans up after the request settles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ux2-klein

Image Lab polish + Flux.2 Klein + ComfyUI folder types + scrollbar UX
Replaces the bundled 7-Zip executables on all three platforms with the
official 26.01 release (2026-04-27) from the ip7z/7zip GitHub releases.

26.01 includes the fix for the NTFS heap buffer overflow CVE-2026-48095
(GitHub Security Lab GHSL-2026-140, CVSS 8.8) and brings years of
accumulated upstream security fixes. The Windows 7za.exe had been pinned
at the 2018-era 18.01 release; Linux/macOS were on ~23.01.

  - StabilityMatrix.Avalonia/Assets/win-x64/7za.exe   18.01 -> 26.01
  - StabilityMatrix.Avalonia/Assets/linux-x64/7zzs     ~23.01 -> 26.01
  - StabilityMatrix.Avalonia/Assets/macos-arm64/7zz    ~23.01 -> 26.01
  - StabilityMatrix/Assets/7za.exe (legacy WPF)        18.01 -> 26.01

License files updated to the 26.01 text. Binary architectures match the
originals exactly (Windows PE32+ x64, Linux static ELF x64, macOS
universal x86_64+arm64). The 7za command-line surface used by
ArchiveHelper (a/t/x/-o/-y/-bsp1) is unchanged, so this is a drop-in
replacement requiring no code changes; verified with an add/test/extract
round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…026-48095

Update bundled 7-Zip to 26.01 (CVE-2026-48095)
The 7-Zip binary runs from <DataDir>/Assets, which is only re-unpacked
from the app bundle during a package *install* (SetupPrerequisitesStep ->
UnpackResourcesIfNecessary). Package *updates* and app updates never
refresh it, so an existing install could keep a stale binary (e.g. the
old 18.01 with CVE-2026-48095) indefinitely if the user only ever updated
existing packages.

Add a version-stamped refresh at startup: compare a new
Settings.LastUnpacked7zVersion against the bundled Assets.SevenZipVersion
constant in MainWindowViewModel.OnInitialLoadedAsync (after the data
directory is ensured). On mismatch, re-unpack the resources and update the
stamp. It's a cheap string compare on every launch and only does I/O the
first launch after a version bump. Failures are logged but non-fatal since
the install/update flows still re-unpack on demand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MainWindowViewModel now takes IPrerequisiteHelper, and its design-time
factory resolves it via GetRequiredService. DesignData registered
IPrerequisiteHelper as `_ => null!` in the "placeholder" block, which
returns null from GetService — and GetRequiredService can't distinguish
"registered but null" from "not registered", so it threw
"No service for type IPrerequisiteHelper has been registered" and broke
DesignDataTests.Property_ShouldBeNotNull.

Register it as a real NSubstitute mock alongside the other mock services
instead of the null placeholder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
remove unnecessary info from chagenlog
The version-stamped 7-Zip refresh previously ran before the single-instance
check, so two instances launched simultaneously could both reach the
unpack and race on writing the binary before the loser shut down. Move it
to just after the instance guard so only the surviving instance ever
writes 7za.exe. It still runs before any 7z consumer (downloads are only
listed/started later in this method, install dialogs come after), so the
write never races with an extraction either.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the startup version-stamped refresh with a SetupPrerequisitesStep
in the package update flow. Per discussion, the bundled 7z is never run on
attacker-controlled input (the only auto-extracted download is a
first-party, hash-verified zip; the model browser downloads raw files), so
the startup unpack was disproportionate and risked AV false-positives for
writing an exe on launch.

Instead, the package Update and ChangeVersion flows now run the same
SetupPrerequisitesStep that installs use, which calls
UnpackResourcesIfNecessary and refreshes the on-disk binary. Previously
those flows ran only UpdatePackageStep, so an updated package kept whatever
7z binary was already on disk.

Reverts the startup hook: MainWindowViewModel IPrerequisiteHelper
injection + refresh block, the App factory arg, Assets.SevenZipVersion,
and Settings.LastUnpacked7zVersion. Keeps the DesignData IPrerequisiteHelper
mock registration since PackageCardViewModel now depends on it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-startup

Refresh bundled 7-Zip on package update (not startup)
Documents the Windows ROCm work from LykosAI#1629 (by @NeuralFault):

- Added: expanded native Windows ROCm GPU coverage (Vega/GCN5 through
  RDNA4) via a shared helper now used by ComfyUI, SwarmUI, reForge,
  InvokeAI, and Wan2GP; plus optional ROCm package commands
  (SageAttention, Flash Attention, bitsandbytes, ROCm SDK devel).
- Changed: PyTorch TunableOp now disabled by default, with a note on
  re-enabling via PYTORCH_TUNABLEOP_ENABLED=1.
- Fixed: user-set env vars not overriding package-configured ROCm vars;
  pip show "not found" being raised as an exception instead of treated
  as a missing-package state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add v2.16.0-pre.2 changelog entries for AMD/ROCm support (LykosAI#1629)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the new 'Image Lab' conversational image generation interface, a CivArchive model browser, a checkpoint organizer, and video thumbnail generation using FFmpeg, alongside UI enhancements like fuzzy search in BetterComboBox. The code review highlighted several critical issues, including compilation errors in PaintCanvas.axaml.cs (calling Dispose on non-disposable Cursor objects) and PngDataHelper.cs (referencing an undefined variable). Additionally, the reviewer noted a missing FFmpeg download URL for Intel Macs, a lifecycle bug in BetterComboBox that breaks event handling upon detachment, potential overflow crashes in PenPointJsonConverter with negative coordinates, a circular dependency risk in NotificationService, and missing cancellation or race-condition guards in VideoThumbnailService and CivArchiveDetailsPageViewModel.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +138 to 171
public static RemoteResource FfmpegDownloadUrl =>
Compat.Switch(
(
PlatformKind.Windows | PlatformKind.X64,
new RemoteResource
{
// BtbN LGPL build - ffmpeg-n7.1-latest-win64-lgpl-7.1
Url = new Uri(
"https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-n7.1-latest-win64-lgpl-7.1.zip"
),
HashSha256 = "a77ecdc794d67401f3e4976f8856065f7762d74afd16f9c7b777ff0291a7bcaa",
}
),
(
PlatformKind.Linux | PlatformKind.X64,
new RemoteResource
{
// BtbN LGPL build - linux
Url = new Uri(
"https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-n7.1-latest-linux64-lgpl-7.1.tar.xz"
),
HashSha256 = "d7d691dfa3a6d0a75362c02274a80a1f9635bd67908561aae31ee538853ab8ce",
}
),
(
PlatformKind.MacOS | PlatformKind.Arm,
new RemoteResource
{
// evermeet.cx build for macOS arm64
Url = new Uri("https://evermeet.cx/ffmpeg/ffmpeg-7.1.1.zip"),
HashSha256 = "8d7917c1cebd7a29e68c0a0a6cc4ecc3fe05c7fffed958636c7018b319afdda4",
}
)
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The FfmpegDownloadUrl property is missing an entry for PlatformKind.MacOS | PlatformKind.X64 (Intel Macs). If an Intel Mac user runs the application, Compat.Switch will fail to find a matching platform and throw an exception or fail to download FFmpeg, breaking video thumbnail generation. Please add a fallback or an explicit entry for Intel Macs.

            (
                PlatformKind.MacOS | PlatformKind.X64,
                new RemoteResource
                {
                    // evermeet.cx build for macOS x64
                    Url = new Uri("https://evermeet.cx/ffmpeg/ffmpeg-7.1.1.zip"),
                    HashSha256 = "8d7917c1cebd7a29e68c0a0a6cc4ecc3fe05c7fffed958636c7018b319afdda4", // Replace with correct x64 hash
                }
            )

Comment on lines 556 to +564
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);

DropDownOpened -= OnDropDownOpened;
DropDownClosed -= OnDropDownClosed;
ContainerPrepared -= OnContainerPrepared;
ContainerIndexChanged -= OnContainerIndexChanged;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In OnDetachedFromVisualTree, the control unsubscribes from its own events (DropDownOpened, DropDownClosed, ContainerPrepared, ContainerIndexChanged) which were subscribed in the constructor. Since there is no OnAttachedToVisualTree override to re-subscribe them, the ComboBox will completely lose its search/filtering functionality if it is ever detached and re-attached to the visual tree (which is common when switching tabs or recycling views). Since these are events on this (the control itself), they do not cause memory leaks and do not need to be unsubscribed. Please remove these unsubscriptions, or move both subscription and unsubscription to OnAttachedToVisualTree and OnDetachedFromVisualTree respectively.

    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
    {
        base.OnDetachedFromVisualTree(e);

        if (searchTextBox is not null)
        {
            searchTextBox.TextChanged -= SearchTextBoxOnTextChanged;
            searchTextBox.KeyDown -= SearchTextBoxOnKeyDown;
        }

        if (settingsManager is not null)
        {
            settingsManager.SettingsPropertyChanged -= OnSettingsPropertyChanged;
        }

        legacySearchResetTimer.Tick -= OnLegacySearchResetTimerTick;
        StopLegacySearchResetTimer();
        HideLegacySearchPopup();
        subscription.Dispose();
    }

Comment on lines +559 to +563
if (lastCanvasCursorTool != selectedTool)
{
lastCanvasCursor?.Dispose();
lastCanvasCursor = new Cursor(StandardCursorType.Cross);
lastCanvasCursorTool = selectedTool;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

lastCanvasCursor?.Dispose(); is called on lastCanvasCursor which is of type Avalonia.Input.Cursor. However, Cursor in Avalonia does not implement IDisposable, so this will cause a compilation error. Please remove the Dispose() call.

            if (lastCanvasCursorTool != selectedTool)
            {
                lastCanvasCursor = new Cursor(StandardCursorType.Cross);
                lastCanvasCursorTool = selectedTool;
            }

Comment on lines +572 to +576
if (lastCanvasCursorTool != selectedTool)
{
lastCanvasCursor?.Dispose();
lastCanvasCursor = new Cursor(StandardCursorType.SizeAll);
lastCanvasCursorTool = selectedTool;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

lastCanvasCursor?.Dispose(); is called on lastCanvasCursor which is of type Avalonia.Input.Cursor. However, Cursor in Avalonia does not implement IDisposable, so this will cause a compilation error. Please remove the Dispose() call.

            if (lastCanvasCursorTool != selectedTool)
            {
                lastCanvasCursor = new Cursor(StandardCursorType.SizeAll);
                lastCanvasCursorTool = selectedTool;
            }

Comment on lines 45 to +57
InferenceProjectDocument projectDocument
)
{
// Validate PNG header
if (inputImage.Length < 8 || !inputImage[..8].AsSpan().SequenceEqual(PngHeader))
{
Logger.Warn(
"AddMetadata: Image data ({Size} bytes) does not have a valid PNG header, "
+ "the file may not actually be a PNG. Returning image as-is",
inputImage.Length
);
return inputImage;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In AddMetadata, the PNG header validation uses inputImage.Length and inputImage[..8], but inputImage is not a parameter of AddMetadata (the parameter is Stream inputStream), and inputImage is not defined at the start of the method. This will cause a compilation error. You should perform this validation after reading the stream into a byte array, or read the first 8 bytes from inputStream to validate the header before processing.

    public static byte[] AddMetadata(
        Stream inputStream,
        InferenceProjectDocument projectDocument
    )
    {
        using var memoryStream = new MemoryStream();
        inputStream.CopyTo(memoryStream);
        var inputImage = memoryStream.ToArray();

        // Validate PNG header
        if (inputImage.Length < 8 || !inputImage[..8].AsSpan().SequenceEqual(PngHeader))
        {
            Logger.Warn(
                "AddMetadata: Image data ({Size} bytes) does not have a valid PNG header, "
                    + "the file may not actually be a PNG. Returning image as-is",
                inputImage.Length
            );
            return inputImage;
        }

        memoryStream.SetLength(0); // Clear the stream to reuse it for writing
        var position = 8; // Skip the PNG signature
        memoryStream.Write(inputImage, 0, position);

        var metadataInserted = false;

Comment on lines +45 to +49
if (reader.TryGetUInt64(out var ulongX))
x = ulongX;
else if (reader.TryGetDouble(out var doubleX))
x = Convert.ToUInt64(doubleX);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In PenPointJsonConverter.Read, Convert.ToUInt64(doubleX) is used to convert a double to ulong. If the legacy JSON contains negative coordinates (which can happen due to offsets, drag coordinates, or window scaling), Convert.ToUInt64 will throw an OverflowException, crashing the deserialization process. A safer cast with bounds checking should be used instead.

                            if (reader.TryGetUInt64(out var ulongX))
                                x = ulongX;
                            else if (reader.TryGetDouble(out var doubleX))
                                x = doubleX >= 0 && doubleX <= ulong.MaxValue ? (ulong)doubleX : 0;

Comment on lines +56 to +60
if (reader.TryGetUInt64(out var ulongY))
y = ulongY;
else if (reader.TryGetDouble(out var doubleY))
y = Convert.ToUInt64(doubleY);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In PenPointJsonConverter.Read, Convert.ToUInt64(doubleY) is used to convert a double to ulong. If the legacy JSON contains negative coordinates, Convert.ToUInt64 will throw an OverflowException, crashing the deserialization process. A safer cast with bounds checking should be used instead.

                            if (reader.TryGetUInt64(out var ulongY))
                                y = ulongY;
                            else if (reader.TryGetDouble(out var doubleY))
                                y = doubleY >= 0 && doubleY <= ulong.MaxValue ? (ulong)doubleY : 0;

Comment on lines +23 to +28
public class NotificationService(
ILogger<NotificationService> logger,
ISettingsManager settingsManager,
INotificationHistoryService historyService,
INotificationActionDispatcher actionDispatcher
) : INotificationService, IDisposable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Injecting INotificationActionDispatcher directly into the NotificationService constructor can easily lead to circular dependencies. NotificationService is a core singleton used by almost all view models, while NotificationActionDispatcher depends on INavigationService<MainWindowViewModel>, which in turn instantiates MainWindowViewModel and its page view models (which inject INotificationService). To prevent startup circular dependency exceptions, resolve actionDispatcher lazily using IServiceProvider or Lazy<INotificationActionDispatcher> when dispatching actions.

public class NotificationService(
    ILogger<NotificationService> logger,
    ISettingsManager settingsManager,
    INotificationHistoryService historyService,
    IServiceProvider serviceProvider
) : INotificationService, IDisposable

Comment on lines +201 to +204
var result = await ProcessRunner
.GetProcessResultAsync(ffmpegPath, args.ToProcessArgs())
.ConfigureAwait(false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In GenerateThumbnailAsync, ProcessRunner.GetProcessResultAsync is called without passing the cancellationToken. If the user cancels the operation (e.g., navigates away or closes the app), the FFmpeg process will continue running in the background. Please pass cancellationToken to GetProcessResultAsync to ensure the process is terminated upon cancellation.

            var result = await ProcessRunner
                .GetProcessResultAsync(ffmpegPath, args.ToProcessArgs(), cancellationToken)
                .ConfigureAwait(false)

Comment on lines +368 to +370

IsInstalled = true;
_ = LoadInstalledLocationAsync(matchedHash);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

_ = LoadInstalledLocationAsync(matchedHash); is called as a fire-and-forget task. If the user switches versions quickly, multiple concurrent loads can run, leading to a race condition where a slower, older request overwrites InstalledLocation with a stale value. Please use a CancellationToken or track the latest task to discard stale results.

@mohnjiles mohnjiles merged commit 6d933df into LykosAI:main Jun 8, 2026
3 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

2 participants