You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Pinned favorites in the version list: Version entries gain a star favorite button (auto_tint follows the theme); pinned entries are placed first in favorite order and the rest sort by real version descending. Pins persist per Profile (VersionPinManager, merged into the AppSettings store). Pinned entries support long-press drag reordering within the pinned range only; on release the order merges back into the full pinned list for persistence, and the main screen's long-press version-card quick-switch popup shows the same order. The list also switched to DiffUtil partial refresh: star/check state rebinds locally via payloads, so favoriting and selection no longer replay the whole list's animation; the viewport is anchored around diff dispatch so favoriting/unfavoriting never scrolls, and scroll-to-selected happens only on dataset changes. Also fixes an out-of-bounds crash from DiffCallback sharing a mutable list reference across cold-load snapshot → recomputed emissions
Layout copy: The layout manager gained a copy feature that pops up a dialog to edit the info and generates a random new id. Key data of lightweight objects is completed before cloning, and ControlViewGroup/ViewData clones are now deep copies that carry the dataLoaded flag, fixing copies rendering nothing in the editor due to failed lazy loading
⚡ Improvements
Automatic mod list refresh (#1896): The mod manager page reloads automatically after downloads land on disk or mods are updated (ModsChanged event stream with 300ms debounce); changes during a scan are reconciled with a supplementary scan in the completion callback, and re-entering ModListPage no longer rescans unconditionally (compared against the most recent scan). The same rework moves the version list, download manifests, and search onto ViewModel state machines: snapshots emit first for instant rendering with full recomputation in the background; download manifests cache across pages by libraryId so re-entering no longer re-fetches, and failures keep the previous list with a retry entry; the mod detail page pins its owning mode via pinnedPageId, fixing mods being downloaded into the shader-pack directory; temp pages remove their view on dismiss immediately, leaving no mistappable fade-out period
🐛 Bug Fixes
UI freezing when launching modded instances on 26.3+: NeoForge 12 removed the -Dfml.earlyprogresswindow property channel (FMLConfig only reads config/fml.toml), so the old flag stopped working; the early-loading window's ANGLE/Vulkan render thread allocates gralloc buffers repeatedly during the startup scan and deadlocks with the launcher UI's present on the vendor gralloc process's shared lock, starving RenderThread and freezing the whole UI until ANR. generateOptionsTxt now unconditionally merges earlyWindowControl = false into fml.toml line-wise (the key goes at the file head to avoid falling into table scope, with other user config preserved)
Black bars and misaligned clicks after runtime window resize under SDL rendering: The SDL window consumes no GLFW window events, so changing the resolution in-game left the view size unchanged, SizeChanged never fired, and viewport and buffer sizes diverged; everything now converges on SdlBridge.syncResolution, with render window size computation unified and the render state of the surface-reuse branch restored
Forced-resolution letterbox misplacement: Now driven by layout callbacks and computed from the parent container's measured size instead of a possibly stale static layout snapshot
External keyboards unable to type text in-game under SDL mode: The SDL render path (MC 26.3+) registers no GLFW char callback, so the only text channel is SDL's text-input channel — which receives IME commits only, and physical keyboard presses never synthesized text. While the channel is active (an in-game text field is focused, judged from the native true state), printable key characters are additionally committed through the SDL text channel paired with the key event, matching desktop semantics where keydown and char events arrive together; with the channel closed only key events are forwarded, leaving game key bindings untouched
Old-format (64x32) skins rendering incorrectly after switching pages (#1928): Textures re-uploaded after surface recreation were not size-normalized — the bitmap uploaded at its native 64x32 was sampled by 64x64 UVs, misplacing everything once a page switch triggered a re-upload; the fix normalizes before writing back to the texture
"Request highest refresh rate" toggle crashing inside the layout editor (#1927): The toggle callback cast its host straight to JVMActivity, throwing ClassCastException in non-game hosts (the layout editor); it now instanceof-checks and only applies the max-refresh-rate policy when the host is a JVMActivity
Icon-less Modrinth entries crashing the favorites page: icon_url is null for entries without icons and flowed into a Kotlin non-null field; both toMod() sites now fall back to an empty string
Aggregation-search category tags leaking internal key names: When a Modrinth entry's category/loader slug (e.g. babric) lacks a localized string, getLocalizedText fell back to the key itself and the list displayed modrinth_category_babric; 13 missing strings were added against the Modrinth API (babric/bta_babric/canvas/geyser/iris/java_agent/legacy_fabric/liteloader/minecraft/nilloader/optifine/ornithe/vanilla), and getLocalizedCategory falls back to the raw slug to prevent future leaks
Favorites-page one-click download neither cancellable nor re-trigger-safe: Parsing now runs in a cancellable coroutine, and clicking again while loading has not been reset shows a wait hint
Wrong SDL GL library path for custom renderer plugins: The v1 plugin convention is that a leading / in glName/eglName means relative to the plugin's nativeLibraryDir, but the SDL GL branch passed it through as absolute, producing broken paths like /libX.so that only worked thanks to SDL's soname fallback over LD_LIBRARY_PATH after dlopen failure. The path is now joined against the plugin directory, and mirroring the EGL branch an existence check was added: when the path does not exist the environment variable is left unset, letting SDL resolve by its own defaults