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
Source-level analysis of what moving Nucleus from Compose Multiplatform 1.12.0 to 1.13.0-alpha01 breaks, what it changes underneath, and what it buys us. Everything below was checked against cloned trees and published artifacts, not against the release notes alone, and re-verified in two further independent passes (the comments below list what each pass corrected):
JetBrains/compose-multiplatform-core at v1.12.0 (f29d2f99) and v1.13.0-alpha01 (a9e47e8a)
JetBrains/compose-multiplatform (Gradle plugin) at both tags
JetBrains/skiko at v0.150.1 and v0.152.0-alpha02, plus a javap diff of the two skiko-awt jars and the entry listing of skiko-awt-runtime-all
Maven Central POM / .module metadata for ui-desktop, ui-skiko-desktop, ui-graphics-desktop, foundation-desktop, desktop-jvm, desktop-jvm-macos-arm64, skiko-awt, Jewel, Material3
Jewel 0.41.0 jars scanned for the Skiko symbols that changed shape
Verdict: it compiles, but it does not run as-is: Compose 1.13 requires the host to register the Skiko backend (registerSkikoComposeImplementation()), which only the AWT ComposeContainer does. Nucleus builds scenes directly, so every Tao window would fail at construction. That is a one-line fix. After that, what moves is a hidden Skiko bump, an artifact split that changes how natives arrive (fat jar), two API members our Tao layer has to follow, a Pan-classification change that lands on TaoSceneScrollRouter, and packaging / GraalVM metadata rot. Skia Graphite is not in Compose 1.13; it is a separate Skiko module Compose does not consume anywhere.
Nucleus today
1.13.0-alpha01
Compose
1.12.0
1.13.0-alpha01 (Jetpack 1.13.0-alpha02)
Skiko
0.150.1
0.152.0-alpha02
Skia
m150
m152
Skiko natives on the classpath
skiko-awt-runtime-<os>-<arch> via desktop-jvm-<target>
skiko-awt-runtime-all via ui-skiko-desktop (55 MB jar, 130 MB unpacked, all six natives + icudtl.dat)
Kotlin stdlib required
2.4.10
≥ 2.2.20, fine
kotlinx-coroutines required
1.11.0
≥ 1.9.0, fine
material3
1.12.0-alpha03
1.13.0-alpha01 published
Compose Hot Reload preferred
1.2.0
1.3.0-alpha01
Jewel, latest on Central
0.39.1 (built on Compose 1.11)
0.41.0-262.10968.63 (built on Compose 1.12.0), nothing for 1.13 yet
Gotcha: gradle/libs.versions.toml in the core repo still says skiko = "0.150.1" at both tags. The published ui-graphics-desktop POM/.module is the truth and says 0.152.0-alpha02.
What breaks or moves in Nucleus
0. Runtime break: the Skiko backend must be registered by the host
ui-graphics and ui-text no longer contain their Skia actuals. The non-Android actuals go through process-wide registries and the Skia implementation lives in the new ui-skiko artifact:
// ui-graphics, nonAndroidMaininternalfunrequireCurrent(): PlatformGraphics=
implementation ?: error("No Compose UI graphics implementation is registered.")
// ui-text, nonAndroidMain
implementation ?: error("No Compose UI text implementation is registered.")
Paint(), Path(), ImageBitmap(...), ColorFilter.tint, every shader, Paragraph(...), findPrecedingBreak, and createFontFamilyResolver() all go through them. The registration entry point is:
It is called from ComposeContainer.desktop.kt (AWT, in init), ImageComposeScene, the iOS / macOS / web containers and the test bases. Nothing in BaseComposeScene, RootNodeOwner, CanvasLayersComposeScene or PlatformLayersComposeScene calls it. Nucleus creates its scenes directly in TaoSceneBundle.kt (canvasLayersSceneBundle / platformLayersSceneBundle), and RootNodeOwner runs fontFamilyResolver = createFontFamilyResolver() in a property initializer → every Tao window throws "No Compose UI text implementation is registered." before its first frame: CanvasLayersComposeScene at construction (eager mainOwner), PlatformLayersComposeScene at first use (mainOwner by lazy). Our 13 direct Compose Paint() / Path() / ImageBitmap() / ColorFilter.tint call sites in main code (8 files: InsideBorderModifier.kt, WindowControlsWindows.kt, WindowControlsLinux.kt, DialogTitleBar.kt, TransferDrag.kt, SpellcheckUnderline.kt, PainterToRgba.kt, ContextMenuFlyout.kt) hit the graphics variant the same way.
Fix: call registerSkikoComposeImplementation() at the top of canvasLayersSceneBundle and platformLayersSceneBundle in TaoSceneBundle.kt. It is idempotent for the same instance and synchronized. Nucleus already opts into InternalComposeUiApi per file (22 files today). Those two factories are also what TaoSceneTestHarness, NativePopupLayersTest and TaoSceneOuterLocalsBridgeTest go through, so the same line covers the tests; the ui-test-junit4 based tests (NucleusWindowHostTest, ContextMenuDividerCapabilityTest, ProvideNucleusSystemThemeE2ETest) self-register through ComposeUiTest.
1. window.v2 clone must follow an upstream rename
WindowGeometryProviders.desktop.kt renames AlignedToScreen → AlignedToScreenAvailableBounds and CenteredOnScreen → CenteredInScreenBounds (core #3291). The latter becomes a val plus a fun CenteredInScreenBounds(offset: DpOffset) that centers on screen.bounds, ignoring insets. No deprecated aliases upstream. Our member-for-member clone in decorated-window-tao/src/main/kotlin/dev/nucleusframework/window/tao/v2/WindowProviders.kt still has the old names. Public API frozen by BCV → keep the old names as @Deprecated aliases, add the new ones, ./gradlew apiDump. The other v2 files (Screen, WindowGeometry, WindowState, DialogState) are unchanged upstream.
2. PlatformContext.taskDispatchers is a new abstract member
interfacePlatformContext {
val taskDispatchers:TaskDispatchers// no default...
}
openclassEmpty : PlatformContext {
overrideval taskDispatchers:TaskDispatchers=DefaultTaskDispatchers// Default = Dispatchers.Default, IO = Dispatchers.Default
}
Nucleus does not break: every Tao context extends PlatformContext.Empty() through TaoPlatformContextBase (TaoKeepScreenOn.kt:61), and the EDT-guard wrapper in TaoSceneBundle.kt uses PlatformContext by this. But Empty maps IO to Dispatchers.Default, while the AWT ComposeSceneMediator maps it to Dispatchers.IO. Override taskDispatchers in TaoPlatformContextBase for parity. RootNodeOwner exposes it as Owner.taskDispatchers. Also new with defaults: mediaScope: UiMediaScope (experimental, gated by ComposeUiFlags.isMediaQueryIntegrationEnabled) and hapticFeedback now defaulting to a no-op.
3. Skia graphics and text helpers moved to the new artifact org.jetbrains.compose.ui:ui-skiko
asComposeCanvas, nativeCanvas, toComposeImageBitmap, asSkiaBitmap, toAwtImage, SkiaGraphicsContext, SkiaBackedCanvas, SkikoGraphicsLayer, and on the text side SkikoParagraph, FontLoader, PlatformFont, AwtFontInterop, JetBrainsRuntimeFontFamilies, ReflectionUtil, and the desktop Font(resource = …) / Font(file = …) factories all left ui-graphics-desktop / ui-text-desktop for ui-skiko (src/nonAndroidMain, src/desktopMain). Same packages, same JVM class names (SkiaBackedCanvas_skikoKt, SkiaImageAsset_skikoKt, DesktopImageConverters_desktopKt, DesktopFont_desktopKt, PlatformFont_skikoKt), pulled at compile scope by ui-desktop and foundation-desktop → source and binary compatible. Nucleus call sites: scene/TaoSceneBundle.kt (3), scene/TaoComposeSceneHostLinux.kt (2), nucleus-application/.../contextmenu/ContextMenuFlyout.kt (2), plus compose-demo, tao-demo, benchmark-demo. Nothing to change in code, but one more jar for ProGuard / uber-jar / GraalVM metadata to see, and ui-skiko-desktop is what now carries the Skiko native runtime dependency (see 10).
4. Trackpad Pan events are now classified by Compose
ComposeSceneInputHandler.skiko.kt tags PanStart / PanMove / PanEnd with PointerClassification.Pan + isGestureStart / isGestureEnd (new fields on PointerInputEvent; ComposeScene.sendPointerEvent is unchanged). Effects in common code, unconditional:
PointerInputEventProcessor: a Pan event is treated as a hover stream regardless of pressed state (isHover = true).
HitPathTracker: no new hit tests during a pan ("prevent entering new items while scrolling"); isIn is still recomputed for existing hit paths; pointer ids are pruned immediately on release during a pan.
Gated by ComposeUiFlags.isTrackpadPanHoverFixEnabled, default false (TODO CMP-10707 "restore to the AOSP value"): the synthesis of Enter / Exit for a Pan event when isIn != hasEntered, i.e. hover Exit for items that scroll out from under a stationary cursor. So today's release changes the hit-path bookkeeping but not yet the Enter/Exit synthesis on desktop.
This is exactly the bookkeeping TaoSceneScrollRouter does by hand since #654 (deliver the deferred PanEnd to the node that received the PanMoves). Needs headful re-testing: hover state during trackpad scroll, deferred PanEnd, NSPanel popups; then try isTrackpadPanHoverFixEnabled = true since it is the behaviour we want. The -Dnucleus.tao.trackpadPanEvents=false path (everything as Scroll) is untouched.
ComposeUiFlags.useLegacyRenderNodeLayers and LegacyRenderNodeLayer.skiko.kt are gone (core #3213). New ComposeUiFlags.useSnapshotCache (default true) feeds Skiko's RenderNodeContext(measureDrawBounds, snapshotCache): every GraphicsLayer records an immutable SkPicture replayed until its content changes (skiko #1022). Nucleus references none of these flags. Memory grows with the number of layers; the known regression in the release is Web-only (CMP-10732), but watch windows with many popup layers.
Other new ComposeUiFlags (Jetpack side): isMinimalistLocalsEnabled (false, lets LocalX fall back to LocalOwner instead of being eagerly provided), isVectorDrawCacheSharingEnabled (true, documented as a no-op for now), isVelocityTrackerMinSampleSizeFixEnabled (true); isClearNestedScrollCoroutineScopeFixEnabled removed.
6. No more snapshot apply between layout and draw
BaseComposeScene.render dropped Snapshot.sendApplyNotifications() between the layout and draw phases. State written during layout is no longer visible to draw in the same frame. Nucleus already pumps Snapshot.sendApplyNotifications() itself around frames (TaoMainDispatcher, TaoApplicationCompose, TaoAccessibility), so only state written during the layout pass is affected. DockLayoutState lives in snapshot state → DockLayoutMonkeyHeadfulCases is the safety net.
7. Window insets plumbing
excludeWindowInsets removed from Popup.skiko.kt / Dialog.skiko.kt, RootWindowInsetsProviderModifierElement removed from RootNodeOwner, foundation-layout's WindowInsetsPadding.skiko.kt now reads LocalPlatformWindowInsets directly. PlatformContext.windowInsets (overridden by our three scene hosts) is still what CompositionLocals.skiko.kt:106 provides. Neutral, but layers that relied on usePlatformInsets exclusion render differently.
8. Friend-package accessors and implemented interfaces survive
androidx.compose.ui.draganddrop.TaoTransferableAccess → AwtDragAndDropTransferable.toAwtTransferable() still at DragAndDrop.desktop.kt:122-131.
androidx.compose.ui.scene.TaoComposeSceneContextAccess → LocalComposeSceneContext still a top-level internal val in ComposeSceneContext.skiko.kt:33.
CanvasLayersComposeScene, PlatformLayersComposeScene, ComposeScene, ComposeSceneLayer, ComposeSceneContext, ComposeSceneDragAndDropNode, PlatformDragAndDropManager, PlatformScreenReader, PlatformClipboard: files byte-identical between the two tags.
Every Compose interface Nucleus implements (WindowInfo in TaoWindowInfo / StandalonePopupWindowInfo, TextToolbar in TaoTextToolbar, UriHandler in TaoLinuxUriHandler, anonymous ViewConfiguration, ComposeSceneContext, PlatformContext) only gained explicit public modifiers upstream (explicit API mode). No new abstract members besides PlatformContext.taskDispatchers.
Also verified unchanged on the 1.13 sources: SaveableStateRegistry (our RelocatingSaveableStateRegistry), MonotonicFrameClock (TaoFrameClock), Applier (NoOpApplier), ModifierNodeElement, the DragAndDropSourceModifierNode(onStartTransfer) factory and DragAndDropStartTransferScope (TransferDrag), Modifier.dragAndDropTarget (DockTransferTarget, TabStrip), and the old context-menu API we hook (TextContextMenu / LocalTextContextMenu / ContextMenuRepresentation / ContextMenuArea in NativeTextContextMenu, NativeContextMenuRepresentation): ComposeFoundationFlags.isNewContextMenuEnabled stays false on skiko in 1.13, so that path is still the one used. The hapticFeedback default swap is neutral, DefaultHapticFeedback was already empty.
9. Skiko 0.152.0-alpha02, what touches Nucleus
FontStyle, FontWeight, FontWidth, FontMetrics became value classes (skiko #1209). Binary break for any jar compiled against 0.150 that calls them (FontMgr.matchFamilyStyle-ooATI1c, Font.getMetrics-l-OL_C8, …). Nucleus imports none of the four. Jewel 0.41.0 jars (jewel-foundation, jewel-ui, jewel-int-ui-standalone, jewel-markdown-*) reference none of org/jetbrains/skia/FontStyle|FontWeight|FontWidth|FontMetrics|FontMgr, nor any Compose scene/PlatformContext/ComposeUiFlags internals → binary-safe with respect to this change, even though built on Compose 1.12.0.
Skottie + sksg removed from the core binary (skiko-skottie is separate). Nucleus does not use them, butplugin-build/plugin/src/main/resources/nucleus/graalvm/library-metadata/skia-skiko.json and platform-metadata/macos-reachability-metadata.json still declare org.jetbrains.skia.skottie.Logger, skottie.LogLevel and a 15-float FontMetrics.<init> that the 0.152 JNI no longer constructs (interop.cc returns a float[]). Dead entries to remove.
DirectContext(ptr, managed): Surface.recordingContext now returns a borrowed context (managed = false); Canvas.recordingContext is new. If any Nucleus code closes a context obtained from a surface, review it.
ColorFilter.makeBlend now returns ColorFilter?; Path.updateBoundsCache() is a hidden deprecated no-op. Nucleus calls neither.
Rect.fromInteropPointer, SurfaceProps.packToIntArray, LibraryLoader now public @InternalSkikoApi (were internal).
Per-OS native size is flat (libskiko-macos-arm64.dylib 21.44 MB → 21.43 MB); the dead-code stripping in #1228 is offset by m152.
1.13.0-alpha01: desktop-jvm-macos-arm64 POM → desktop 1.13.0-alpha01 only. ui-skiko-desktop → skiko-awt-runtime-all 0.152.0-alpha02 (compile scope).skiko-awt itself only publishes version constraints on the per-OS runtimes, no dependency. skiko-awt-runtime-all exists on Central at exactly one version, 0.152.0-alpha02.
The fat jar: 55.3 MB compressed, 130.8 MB unpacked, entries at the jar root: libskiko-linux-{x64,arm64}.so, libskiko-macos-{x64,arm64}.dylib, skiko-windows-{x64,arm64}.dll, icudtl.dat, plus .sha256 files. Same entry names as the per-OS jars.
Consequences for Nucleus:
Our fork of the packaging plugin (AbstractJPackageTask.kt:966, isSkikoForCurrentOS) only recognises jars named skiko-awt-runtime-<os>-<arch>. With 1.13 it recognises nothing, so the 55 MB jar is copied whole into the distributable (all six natives) and the pre-extraction that sets -Dskiko.library.path never runs (Skiko falls back to extracting into ~/.skiko at first start). Port the new skikoUtils.kt filter: jar name skiko-* containing -awt-runtime, keep only entries containing -<os>-<arch> plus icudtl.dat on Windows.
The exclude(group = "org.jetbrains.skiko", module = "skiko-awt-runtime-all") in examples/jewel-demo, jewel-tabs-demo, scheduler-demo must go: on 1.13 it removes the only source of natives and the app dies at Skiko load. (Jewel's own POMs declare no Skiko runtime dependency; the exclusion was defensive.)
GraalVM uber-jar extraction (include("libskiko-<os>-<arch>.dylib"), .so, .dll + icudtl.dat) keeps working, entry names did not change, but the uber jar grows by ~55 MB unless the other-OS entries are excluded when it is built.
The Compose plugin deprecates compose.desktop.currentOs (still resolves to desktop-jvm-<target>); its new compose { dependencyCompatibility { exclude("group[:module]") } } and the runtime-libraries check now also flag Skiko version mismatches. Jewel 0.41 on a 1.13 build will trigger it.
New AOT DSL in JvmApplicationBuildType: aot { mode = AotMode.AotPrebuild | AppCdsPrebuild | AppCdsAuto }, training run with -XX:AOTCacheOutput=$APPDIR/app.aot and -Dcompose.aot.training-run=true, runtime -XX:AOTCache=$APPDIR/app.aot, createDistributable split into createDistributableImpl + createAotArchive, stripNativeCommands disabled when generating the JRE CDS archive (generateJreCdsArchive). Same archive name as our aotCache / aotTraining(). No break (fork), but generateJreCdsArchive (a classes.jsa for the jlinked runtime) is worth adopting.
--app-image is now always the app dir (<name>.app on macOS, <name> elsewhere) and installers are built from the app image on every OS, not only macOS.
Other fork-merge material: AbstractJLinkTask.generateJreCdsArchive (jlink --generate-cds-archive, refused when stripNativeCommands is on), AbstractCheckNativeDistributionRuntime.aotModes (per-mode minimum JDK check), and an executePackagedApp helper that replaces the hand-rolled app-dir lookup in AbstractRunDistributableTask.
Skia Graphite: what it actually is
Zero references in the Compose 1.13.0-alpha01 tree. Graphite exists only as a Skiko module skiko-graphite (org.jetbrains.skia.gpu.graphite: GraphiteContext, Recorder, Recording, InsertRecordingInfo, SurfaceFactory, BackendTexture, BackendSemaphore, VulkanTypes, GraphiteImageProvider.cc), published as Apple klibs since 0.151.0 and as skiko-graphite-awt + per-OS runtime jars (macOS / Linux / Windows, x64 + arm64) since 0.152.0 (skiko #1245, #1261 "Add Graphite Vulkan", #1277). Backends: Metal and Vulkan. No Dawn, no D3D12.
Compose does not need to know about it: GraphicsLayer goes through Skiko's RenderNode, which records SkPictures and replays them onto whatever SkCanvas the host provides. The backend decision lives entirely in the host surface, i.e. in our MetalSceneRenderer / GlSceneRenderer (DirectContext.makeMetal / makeGLWithInterface + Surface.makeFromBackendRenderTarget). Going Graphite would mean GraphiteContext + Recorder + SurfaceFactory on macOS, and a new Vulkan backend on Linux/Windows where we are EGL / ANGLE today; it would also break the Ganesh-based zero-copy TextureView import and the ExternalTexture roadmap (#338). Nothing to do for 1.13. Worth tracking because Ganesh is end-of-life upstream.
What we gain
For free:
Linux theme.cc tries libdbus-1.so.3 before libdbus-1.so (skiko #1276) → fixes SystemTheme.UNKNOWN on distros without the dev symlink, which ProvideNucleusSystemTheme reads through currentSystemTheme.
Proposed order (spike branch; v1.13.0-alpha02+dev4828 is already tagged upstream)
Bump compose and material3 to 1.13.0-alpha01, hotReload to 1.3.0-alpha01 (only tao-demo applies it), and navigation3 (only compose-demo, currently 1.1.1 built on Compose 1.10.0) to 1.2.0-beta01 with lifecycle-viewmodel-navigation3 2.11.0.
Call registerSkikoComposeImplementation() before any scene is created (TaoSceneBundle / DecoratedWindow bootstrap) and in TaoSceneTestHarness.
Remove the skiko-awt-runtime-all exclusions from jewel-demo, jewel-tabs-demo, scheduler-demo.
Mirror the v2 rename in tao/v2/WindowProviders.kt with @Deprecated aliases, apiDump.
Override taskDispatchers in TaoPlatformContextBase (Default / IO).
Port the skikoUtils.kt native filter into our AbstractJPackageTask.kt; check the uber jar size for the GraalVM path.
Remove the skottie / 15-arg FontMetrics entries from the plugin's GraalVM metadata.
Headful: trackpad pan + hover (TaoSceneScrollRouter), NSPanel popups, DockLayoutMonkeyHeadfulCases, TextureViewMonkeyHeadfulCases; then evaluate isTrackpadPanHoverFixEnabled.
Optional: Image.adoptTextureFrom(alphaType) in TextureView, feed mediaScope from Tao, exclude Jewel from the plugin's compatibility check until a 1.13 Jewel exists.
Not affected (checked)
ResourceFont / FileFont constructors now @InternalComposeUiApi; PlatformTextInputSession & co only gained explicit public; InteropContainer.scheduleUpdate(holder) new default method; Wrapper.skiko.kt no longer passes uriHandler (now RootNodeOwner.uriHandler lazily); ComposeSceneMediator.boundsOnScreenPx() / drawContentInto(), ComposeDesktopEntryPoint.captureContentToImage() and the Skiko metalSynchronousLiveResize / direct3DSynchronousLiveResize properties are AWT SkiaLayer only. No Nucleus native code includes Skia headers. graalvm-runtime's @TargetClass substitutions target JDK classes only (java.awt.SplashScreen, sun.awt.X11.XToolkit, sun.awt.Win32FontManager, java.awt.Font, sun.awt.FcFontManager). The shipped ProGuard rule -keep class org.jetbrains.skiko.** { *; } is unaffected. Nucleus does not use FontLoader / PlatformFont / Typeface(skia) / FontRasterizationSettings directly.
Foundation's scrolling was refactored (TrackpadScrollingLogic / MouseWheelScrollingLogic generalised into Trackpad1DScrollingLogic / Trackpad2DScrollingLogic over ScrollValueAdapters in NonTouchScrollingLogic.kt); the 45° axis lock moved from atan2(|y|, |x|) >= π/4 in Scrollable.kt to abs(y) >= abs(x) in OneDimensionalScrollValueAdapter.toScrollValue, which is the same rule, so diagonal trackpad pans reaching Modifier.scrollable behave as before. ComposeFoundationFlags: isBasicTextFieldSizeOptimizationEnabled flipped to true, a set of new lazy cache-window flags, isClearNestedScrollCoroutineScopeFixEnabled removed. SaveableStateHolderImpl internals changed (SnapshotStateSet keys, direct performSave()), API identical. Transitive versions unchanged: androidx.collection 1.5.0, lifecycle-runtime-compose 2.9.6, lifecycle-viewmodel 2.11.0, savedstate-compose 1.4.0; runtime-retain 1.12.0 → 1.13.0-alpha02. swing-tao-demo uses no ComposePanel / SwingPanel, so the Swing double-buffering change (#3336) touches nothing.
Summary
Source-level analysis of what moving Nucleus from Compose Multiplatform 1.12.0 to 1.13.0-alpha01 breaks, what it changes underneath, and what it buys us. Everything below was checked against cloned trees and published artifacts, not against the release notes alone, and re-verified in two further independent passes (the comments below list what each pass corrected):
JetBrains/compose-multiplatform-coreatv1.12.0(f29d2f99) andv1.13.0-alpha01(a9e47e8a)JetBrains/compose-multiplatform(Gradle plugin) at both tagsJetBrains/skikoatv0.150.1andv0.152.0-alpha02, plus ajavapdiff of the twoskiko-awtjars and the entry listing ofskiko-awt-runtime-all.modulemetadata forui-desktop,ui-skiko-desktop,ui-graphics-desktop,foundation-desktop,desktop-jvm,desktop-jvm-macos-arm64,skiko-awt, Jewel, Material3Verdict: it compiles, but it does not run as-is: Compose 1.13 requires the host to register the Skiko backend (
registerSkikoComposeImplementation()), which only the AWTComposeContainerdoes. Nucleus builds scenes directly, so every Tao window would fail at construction. That is a one-line fix. After that, what moves is a hidden Skiko bump, an artifact split that changes how natives arrive (fat jar), two API members our Tao layer has to follow, a Pan-classification change that lands onTaoSceneScrollRouter, and packaging / GraalVM metadata rot. Skia Graphite is not in Compose 1.13; it is a separate Skiko module Compose does not consume anywhere.skiko-awt-runtime-<os>-<arch>viadesktop-jvm-<target>skiko-awt-runtime-allviaui-skiko-desktop(55 MB jar, 130 MB unpacked, all six natives +icudtl.dat)Gotcha:
gradle/libs.versions.tomlin the core repo still saysskiko = "0.150.1"at both tags. The publishedui-graphics-desktopPOM/.moduleis the truth and says0.152.0-alpha02.What breaks or moves in Nucleus
0. Runtime break: the Skiko backend must be registered by the host
ui-graphicsandui-textno longer contain their Skia actuals. The non-Android actuals go through process-wide registries and the Skia implementation lives in the newui-skikoartifact:Paint(),Path(),ImageBitmap(...),ColorFilter.tint, every shader,Paragraph(...),findPrecedingBreak, andcreateFontFamilyResolver()all go through them. The registration entry point is:It is called from
ComposeContainer.desktop.kt(AWT, ininit),ImageComposeScene, the iOS / macOS / web containers and the test bases. Nothing inBaseComposeScene,RootNodeOwner,CanvasLayersComposeSceneorPlatformLayersComposeScenecalls it. Nucleus creates its scenes directly inTaoSceneBundle.kt(canvasLayersSceneBundle/platformLayersSceneBundle), andRootNodeOwnerrunsfontFamilyResolver = createFontFamilyResolver()in a property initializer → every Tao window throws "No Compose UI text implementation is registered." before its first frame:CanvasLayersComposeSceneat construction (eagermainOwner),PlatformLayersComposeSceneat first use (mainOwner by lazy). Our 13 direct ComposePaint()/Path()/ImageBitmap()/ColorFilter.tintcall sites in main code (8 files:InsideBorderModifier.kt,WindowControlsWindows.kt,WindowControlsLinux.kt,DialogTitleBar.kt,TransferDrag.kt,SpellcheckUnderline.kt,PainterToRgba.kt,ContextMenuFlyout.kt) hit the graphics variant the same way.Fix: call
registerSkikoComposeImplementation()at the top ofcanvasLayersSceneBundleandplatformLayersSceneBundleinTaoSceneBundle.kt. It is idempotent for the same instance andsynchronized. Nucleus already opts intoInternalComposeUiApiper file (22 files today). Those two factories are also whatTaoSceneTestHarness,NativePopupLayersTestandTaoSceneOuterLocalsBridgeTestgo through, so the same line covers the tests; theui-test-junit4based tests (NucleusWindowHostTest,ContextMenuDividerCapabilityTest,ProvideNucleusSystemThemeE2ETest) self-register throughComposeUiTest.1.
window.v2clone must follow an upstream renameWindowGeometryProviders.desktop.ktrenamesAlignedToScreen→AlignedToScreenAvailableBoundsandCenteredOnScreen→CenteredInScreenBounds(core #3291). The latter becomes avalplus afun CenteredInScreenBounds(offset: DpOffset)that centers onscreen.bounds, ignoring insets. No deprecated aliases upstream. Our member-for-member clone indecorated-window-tao/src/main/kotlin/dev/nucleusframework/window/tao/v2/WindowProviders.ktstill has the old names. Public API frozen by BCV → keep the old names as@Deprecatedaliases, add the new ones,./gradlew apiDump. The other v2 files (Screen,WindowGeometry,WindowState,DialogState) are unchanged upstream.2.
PlatformContext.taskDispatchersis a new abstract memberNucleus does not break: every Tao context extends
PlatformContext.Empty()throughTaoPlatformContextBase(TaoKeepScreenOn.kt:61), and the EDT-guard wrapper inTaoSceneBundle.ktusesPlatformContext by this. ButEmptymapsIOtoDispatchers.Default, while the AWTComposeSceneMediatormaps it toDispatchers.IO. OverridetaskDispatchersinTaoPlatformContextBasefor parity.RootNodeOwnerexposes it asOwner.taskDispatchers. Also new with defaults:mediaScope: UiMediaScope(experimental, gated byComposeUiFlags.isMediaQueryIntegrationEnabled) andhapticFeedbacknow defaulting to a no-op.3. Skia graphics and text helpers moved to the new artifact
org.jetbrains.compose.ui:ui-skikoasComposeCanvas,nativeCanvas,toComposeImageBitmap,asSkiaBitmap,toAwtImage,SkiaGraphicsContext,SkiaBackedCanvas,SkikoGraphicsLayer, and on the text sideSkikoParagraph,FontLoader,PlatformFont,AwtFontInterop,JetBrainsRuntimeFontFamilies,ReflectionUtil, and the desktopFont(resource = …)/Font(file = …)factories all leftui-graphics-desktop/ui-text-desktopforui-skiko(src/nonAndroidMain,src/desktopMain). Same packages, same JVM class names (SkiaBackedCanvas_skikoKt,SkiaImageAsset_skikoKt,DesktopImageConverters_desktopKt,DesktopFont_desktopKt,PlatformFont_skikoKt), pulled at compile scope byui-desktopandfoundation-desktop→ source and binary compatible. Nucleus call sites:scene/TaoSceneBundle.kt(3),scene/TaoComposeSceneHostLinux.kt(2),nucleus-application/.../contextmenu/ContextMenuFlyout.kt(2), pluscompose-demo,tao-demo,benchmark-demo. Nothing to change in code, but one more jar for ProGuard / uber-jar / GraalVM metadata to see, andui-skiko-desktopis what now carries the Skiko native runtime dependency (see 10).4. Trackpad Pan events are now classified by Compose
ComposeSceneInputHandler.skiko.kttagsPanStart/PanMove/PanEndwithPointerClassification.Pan+isGestureStart/isGestureEnd(new fields onPointerInputEvent;ComposeScene.sendPointerEventis unchanged). Effects in common code, unconditional:PointerInputEventProcessor: a Pan event is treated as a hover stream regardless of pressed state (isHover = true).HitPathTracker: no new hit tests during a pan ("prevent entering new items while scrolling");isInis still recomputed for existing hit paths; pointer ids are pruned immediately on release during a pan.Gated by
ComposeUiFlags.isTrackpadPanHoverFixEnabled, defaultfalse(TODO CMP-10707 "restore to the AOSP value"): the synthesis ofEnter/Exitfor a Pan event whenisIn != hasEntered, i.e. hover Exit for items that scroll out from under a stationary cursor. So today's release changes the hit-path bookkeeping but not yet the Enter/Exit synthesis on desktop.This is exactly the bookkeeping
TaoSceneScrollRouterdoes by hand since #654 (deliver the deferredPanEndto the node that received thePanMoves). Needs headful re-testing: hover state during trackpad scroll, deferredPanEnd, NSPanel popups; then tryisTrackpadPanHoverFixEnabled = truesince it is the behaviour we want. The-Dnucleus.tao.trackpadPanEvents=falsepath (everything asScroll) is untouched.5. Legacy render-node layers removed, picture snapshot cache added
ComposeUiFlags.useLegacyRenderNodeLayersandLegacyRenderNodeLayer.skiko.ktare gone (core #3213). NewComposeUiFlags.useSnapshotCache(defaulttrue) feeds Skiko'sRenderNodeContext(measureDrawBounds, snapshotCache): everyGraphicsLayerrecords an immutableSkPicturereplayed until its content changes (skiko #1022). Nucleus references none of these flags. Memory grows with the number of layers; the known regression in the release is Web-only (CMP-10732), but watch windows with many popup layers.Other new
ComposeUiFlags(Jetpack side):isMinimalistLocalsEnabled(false, letsLocalXfall back toLocalOwnerinstead of being eagerly provided),isVectorDrawCacheSharingEnabled(true, documented as a no-op for now),isVelocityTrackerMinSampleSizeFixEnabled(true);isClearNestedScrollCoroutineScopeFixEnabledremoved.6. No more snapshot apply between layout and draw
BaseComposeScene.renderdroppedSnapshot.sendApplyNotifications()between the layout and draw phases. State written during layout is no longer visible to draw in the same frame. Nucleus already pumpsSnapshot.sendApplyNotifications()itself around frames (TaoMainDispatcher,TaoApplicationCompose,TaoAccessibility), so only state written during the layout pass is affected.DockLayoutStatelives in snapshot state →DockLayoutMonkeyHeadfulCasesis the safety net.7. Window insets plumbing
excludeWindowInsetsremoved fromPopup.skiko.kt/Dialog.skiko.kt,RootWindowInsetsProviderModifierElementremoved fromRootNodeOwner, foundation-layout'sWindowInsetsPadding.skiko.ktnow readsLocalPlatformWindowInsetsdirectly.PlatformContext.windowInsets(overridden by our three scene hosts) is still whatCompositionLocals.skiko.kt:106provides. Neutral, but layers that relied onusePlatformInsetsexclusion render differently.8. Friend-package accessors and implemented interfaces survive
androidx.compose.ui.draganddrop.TaoTransferableAccess→AwtDragAndDropTransferable.toAwtTransferable()still atDragAndDrop.desktop.kt:122-131.androidx.compose.ui.scene.TaoComposeSceneContextAccess→LocalComposeSceneContextstill a top-levelinternal valinComposeSceneContext.skiko.kt:33.CanvasLayersComposeScene,PlatformLayersComposeScene,ComposeScene,ComposeSceneLayer,ComposeSceneContext,ComposeSceneDragAndDropNode,PlatformDragAndDropManager,PlatformScreenReader,PlatformClipboard: files byte-identical between the two tags.WindowInfoinTaoWindowInfo/StandalonePopupWindowInfo,TextToolbarinTaoTextToolbar,UriHandlerinTaoLinuxUriHandler, anonymousViewConfiguration,ComposeSceneContext,PlatformContext) only gained explicitpublicmodifiers upstream (explicit API mode). No new abstract members besidesPlatformContext.taskDispatchers.SaveableStateRegistry(ourRelocatingSaveableStateRegistry),MonotonicFrameClock(TaoFrameClock),Applier(NoOpApplier),ModifierNodeElement, theDragAndDropSourceModifierNode(onStartTransfer)factory andDragAndDropStartTransferScope(TransferDrag),Modifier.dragAndDropTarget(DockTransferTarget,TabStrip), and the old context-menu API we hook (TextContextMenu/LocalTextContextMenu/ContextMenuRepresentation/ContextMenuAreainNativeTextContextMenu,NativeContextMenuRepresentation):ComposeFoundationFlags.isNewContextMenuEnabledstaysfalseon skiko in 1.13, so that path is still the one used. ThehapticFeedbackdefault swap is neutral,DefaultHapticFeedbackwas already empty.9. Skiko 0.152.0-alpha02, what touches Nucleus
FontStyle,FontWeight,FontWidth,FontMetricsbecamevalue classes (skiko #1209). Binary break for any jar compiled against 0.150 that calls them (FontMgr.matchFamilyStyle-ooATI1c,Font.getMetrics-l-OL_C8, …). Nucleus imports none of the four. Jewel 0.41.0 jars (jewel-foundation,jewel-ui,jewel-int-ui-standalone,jewel-markdown-*) reference none oforg/jetbrains/skia/FontStyle|FontWeight|FontWidth|FontMetrics|FontMgr, nor any Compose scene/PlatformContext/ComposeUiFlags internals → binary-safe with respect to this change, even though built on Compose 1.12.0.org.jetbrains.skiko.context.*(allContextHandlers) deleted; AWT redrawers reshaped (OnScreenRedrawer,FrameHost,AbstractOpenGLRedrawer,Redrawer.syncBounds→syncBoundsFromPlatformComponent,onLayerComponentResized;SkiaLayergainsfillsWindow). Nucleus mentionsSkiaLayer/ContextHandlerin comments only. Nothing to do.skiko-skottieis separate). Nucleus does not use them, butplugin-build/plugin/src/main/resources/nucleus/graalvm/library-metadata/skia-skiko.jsonandplatform-metadata/macos-reachability-metadata.jsonstill declareorg.jetbrains.skia.skottie.Logger,skottie.LogLeveland a 15-floatFontMetrics.<init>that the 0.152 JNI no longer constructs (interop.ccreturns afloat[]). Dead entries to remove.DirectContext(ptr, managed):Surface.recordingContextnow returns a borrowed context (managed = false);Canvas.recordingContextis new. If any Nucleus code closes a context obtained from a surface, review it.ColorFilter.makeBlendnow returnsColorFilter?;Path.updateBoundsCache()is a hidden deprecated no-op. Nucleus calls neither.Rect.fromInteropPointer,SurfaceProps.packToIntArray,LibraryLoadernow public@InternalSkikoApi(wereinternal).libskiko-macos-arm64.dylib21.44 MB → 21.43 MB); the dead-code stripping in #1228 is offset by m152.10. Packaging: natives now arrive as one fat jar
Verified from metadata:
desktop-jvm-macos-arm64POM →desktop 1.12.0+skiko-awt-runtime-macos-arm64 0.150.1.desktop-jvm-macos-arm64POM →desktop 1.13.0-alpha01only.ui-skiko-desktop→skiko-awt-runtime-all 0.152.0-alpha02(compile scope).skiko-awtitself only publishes version constraints on the per-OS runtimes, no dependency.skiko-awt-runtime-allexists on Central at exactly one version,0.152.0-alpha02.libskiko-linux-{x64,arm64}.so,libskiko-macos-{x64,arm64}.dylib,skiko-windows-{x64,arm64}.dll,icudtl.dat, plus.sha256files. Same entry names as the per-OS jars.Consequences for Nucleus:
AbstractJPackageTask.kt:966,isSkikoForCurrentOS) only recognises jars namedskiko-awt-runtime-<os>-<arch>. With 1.13 it recognises nothing, so the 55 MB jar is copied whole into the distributable (all six natives) and the pre-extraction that sets-Dskiko.library.pathnever runs (Skiko falls back to extracting into~/.skikoat first start). Port the newskikoUtils.ktfilter: jar nameskiko-*containing-awt-runtime, keep only entries containing-<os>-<arch>plusicudtl.daton Windows.exclude(group = "org.jetbrains.skiko", module = "skiko-awt-runtime-all")inexamples/jewel-demo,jewel-tabs-demo,scheduler-demomust go: on 1.13 it removes the only source of natives and the app dies at Skiko load. (Jewel's own POMs declare no Skiko runtime dependency; the exclusion was defensive.)include("libskiko-<os>-<arch>.dylib"),.so,.dll+icudtl.dat) keeps working, entry names did not change, but the uber jar grows by ~55 MB unless the other-OS entries are excluded when it is built.compose.desktop.currentOs(still resolves todesktop-jvm-<target>); its newcompose { dependencyCompatibility { exclude("group[:module]") } }and the runtime-libraries check now also flag Skiko version mismatches. Jewel 0.41 on a 1.13 build will trigger it.JvmApplicationBuildType:aot { mode = AotMode.AotPrebuild | AppCdsPrebuild | AppCdsAuto }, training run with-XX:AOTCacheOutput=$APPDIR/app.aotand-Dcompose.aot.training-run=true, runtime-XX:AOTCache=$APPDIR/app.aot,createDistributablesplit intocreateDistributableImpl+createAotArchive,stripNativeCommandsdisabled when generating the JRE CDS archive (generateJreCdsArchive). Same archive name as ouraotCache/aotTraining(). No break (fork), butgenerateJreCdsArchive(aclasses.jsafor the jlinked runtime) is worth adopting.--app-imageis now always the app dir (<name>.appon macOS,<name>elsewhere) and installers are built from the app image on every OS, not only macOS.AbstractJLinkTask.generateJreCdsArchive(jlink --generate-cds-archive, refused whenstripNativeCommandsis on),AbstractCheckNativeDistributionRuntime.aotModes(per-mode minimum JDK check), and anexecutePackagedApphelper that replaces the hand-rolled app-dir lookup inAbstractRunDistributableTask.Skia Graphite: what it actually is
Zero references in the Compose 1.13.0-alpha01 tree. Graphite exists only as a Skiko module
skiko-graphite(org.jetbrains.skia.gpu.graphite:GraphiteContext,Recorder,Recording,InsertRecordingInfo,SurfaceFactory,BackendTexture,BackendSemaphore,VulkanTypes,GraphiteImageProvider.cc), published as Apple klibs since 0.151.0 and asskiko-graphite-awt+ per-OS runtime jars (macOS / Linux / Windows, x64 + arm64) since 0.152.0 (skiko #1245, #1261 "Add Graphite Vulkan", #1277). Backends: Metal and Vulkan. No Dawn, no D3D12.Compose does not need to know about it:
GraphicsLayergoes through Skiko'sRenderNode, which recordsSkPictures and replays them onto whateverSkCanvasthe host provides. The backend decision lives entirely in the host surface, i.e. in ourMetalSceneRenderer/GlSceneRenderer(DirectContext.makeMetal/makeGLWithInterface+Surface.makeFromBackendRenderTarget). Going Graphite would meanGraphiteContext+Recorder+SurfaceFactoryon macOS, and a new Vulkan backend on Linux/Windows where we are EGL / ANGLE today; it would also break the Ganesh-based zero-copyTextureViewimport and the ExternalTexture roadmap (#338). Nothing to do for 1.13. Worth tracking because Ganesh is end-of-life upstream.What we gain
For free:
theme.cctrieslibdbus-1.so.3beforelibdbus-1.so(skiko #1276) → fixesSystemTheme.UNKNOWNon distros without the dev symlink, whichProvideNucleusSystemThemereads throughcurrentSystemTheme.RenderNodepicture snapshot cache → cheaper static subtrees (dock bands, tab strips, palettes).Ctrl+F/Ctrl+Breversed in text fields, fixed in foundationKeyMapping.skiko.kt(skikoMain, so it applies to Tao).RenderNode.alphaapplied at draw (skiko #1227), null-safeColorFilter(core #3319), Skia m152.With a little code:
Image.adoptTextureFrom(context, texture, origin, colorType, alphaType)→ declare premul / opaque properly inTextureView.Canvas.drawAnnotation,Canvas.recordingContext.PlatformContext.mediaScope(experimental): Tao can feedpointerPrecision,keyboardKind, window size.taskDispatcherswith a realDispatchers.IO.ComposeUiFlags.isTrackpadPanHoverFixEnabled = trueonce validated: upstream hover Exit during trackpad scroll, which is what Tao: distinguish trackpad panning from wheel scrolling in Compose #654 wanted.aotCacheapproach;generateJreCdsArchiveis portable.Proposed order (spike branch;
v1.13.0-alpha02+dev4828is already tagged upstream)composeandmaterial3to1.13.0-alpha01,hotReloadto1.3.0-alpha01(onlytao-demoapplies it), andnavigation3(onlycompose-demo, currently1.1.1built on Compose 1.10.0) to1.2.0-beta01withlifecycle-viewmodel-navigation3 2.11.0.registerSkikoComposeImplementation()before any scene is created (TaoSceneBundle/DecoratedWindowbootstrap) and inTaoSceneTestHarness.skiko-awt-runtime-allexclusions fromjewel-demo,jewel-tabs-demo,scheduler-demo.tao/v2/WindowProviders.ktwith@Deprecatedaliases,apiDump.taskDispatchersinTaoPlatformContextBase(Default/IO).skikoUtils.ktnative filter into ourAbstractJPackageTask.kt; check the uber jar size for the GraalVM path.FontMetricsentries from the plugin's GraalVM metadata.TaoSceneScrollRouter), NSPanel popups,DockLayoutMonkeyHeadfulCases,TextureViewMonkeyHeadfulCases; then evaluateisTrackpadPanHoverFixEnabled.Image.adoptTextureFrom(alphaType)inTextureView, feedmediaScopefrom Tao, exclude Jewel from the plugin's compatibility check until a 1.13 Jewel exists.Not affected (checked)
ResourceFont/FileFontconstructors now@InternalComposeUiApi;PlatformTextInputSession& co only gained explicitpublic;InteropContainer.scheduleUpdate(holder)new default method;Wrapper.skiko.ktno longer passesuriHandler(nowRootNodeOwner.uriHandlerlazily);ComposeSceneMediator.boundsOnScreenPx()/drawContentInto(),ComposeDesktopEntryPoint.captureContentToImage()and the SkikometalSynchronousLiveResize/direct3DSynchronousLiveResizeproperties are AWTSkiaLayeronly. No Nucleus native code includes Skia headers.graalvm-runtime's@TargetClasssubstitutions target JDK classes only (java.awt.SplashScreen,sun.awt.X11.XToolkit,sun.awt.Win32FontManager,java.awt.Font,sun.awt.FcFontManager). The shipped ProGuard rule-keep class org.jetbrains.skiko.** { *; }is unaffected. Nucleus does not useFontLoader/PlatformFont/Typeface(skia)/FontRasterizationSettingsdirectly.Foundation's scrolling was refactored (
TrackpadScrollingLogic/MouseWheelScrollingLogicgeneralised intoTrackpad1DScrollingLogic/Trackpad2DScrollingLogicoverScrollValueAdapters inNonTouchScrollingLogic.kt); the 45° axis lock moved fromatan2(|y|, |x|) >= π/4inScrollable.kttoabs(y) >= abs(x)inOneDimensionalScrollValueAdapter.toScrollValue, which is the same rule, so diagonal trackpad pans reachingModifier.scrollablebehave as before.ComposeFoundationFlags:isBasicTextFieldSizeOptimizationEnabledflipped totrue, a set of new lazy cache-window flags,isClearNestedScrollCoroutineScopeFixEnabledremoved.SaveableStateHolderImplinternals changed (SnapshotStateSetkeys, directperformSave()), API identical. Transitive versions unchanged:androidx.collection1.5.0,lifecycle-runtime-compose2.9.6,lifecycle-viewmodel2.11.0,savedstate-compose1.4.0;runtime-retain1.12.0 → 1.13.0-alpha02.swing-tao-demouses noComposePanel/SwingPanel, so the Swing double-buffering change (#3336) touches nothing.