Use Category with ListItem rows, plain leading icons, and trailing
chevrons instead of expressive hub chips on the detail screen.
Co-authored-by: denis0001-dev <denis0001.dev@ya.ru>
Merge server, proxy, and trusted certificates under a single Connection
settings entry in the notifications category, and remove the custom proxies
section title on the proxy screen.
Co-authored-by: denis0001-dev <denis0001.dev@ya.ru>
Trusted TLS certs are opened from Server and network, not the Proxy
screen. The certificate trust dialog can be dismissed without trusting.
Co-authored-by: denis0001-dev <denis0001.dev@ya.ru>
Show certificate issues in proxy checks, prompt to trust with persistent or
session-only pins, and add trusted-certificate management. Isolate health
checks from the active route and remove the App settings category label.
Co-authored-by: denis0001-dev <denis0001.dev@ya.ru>
* Fix Cloud Agent setup so Linux builds can sign and launch.
The debug keystore must use alias key0, matching the Android signing config. The install script also provisions JDK 21, Android SDK 37.2, and the libraries Compose Desktop needs to open a window.
Co-authored-by: denis0001-dev <denis0001.dev@ya.ru>
* Run Cloud Agent install from the app repo in a multi-repo workspace.
The setup script lives under the app checkout, and the debug keystore alias on main still has to be rewritten to key0 before Gradle signs the APK.
Co-authored-by: denis0001-dev <denis0001.dev@ya.ru>
* Call the Cloud Agent setup script directly from environment.json.
Install stays a single command. Keystore alias, JDK, Android SDK, and desktop libraries stay in cloud-setup.sh.
Co-authored-by: denis0001-dev <denis0001.dev@ya.ru>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Skip Kotlin Native link and unsigned IPA packaging in release builds; publish
Android, desktop, and Linux artifacts only.
Co-authored-by: Cursor <cursoragent@cursor.com>
Set default versionName to 1.2.0 with versionCode 120999 so Android stays
above 1.2.0-beta builds. Teach set-version to derive stable semver codes
as digits*1000+999 while keeping prerelease strip behavior.
Pass gitCommitSha and gitHubRepository from CI Gradle properties and local
git remote, generate them in AppBuildInfo, and resolve repo-relative legal
markdown links to GitHub blob URLs. Extend About with commit and license
links; keep EN/RU strings in sync.
Tar JVM build outputs for artifact upload, extract before packaging verify, derive iphoneos SDK_NAME from xcrun, and skip appimagetool permission checks on CI.
Co-authored-by: Cursor <cursoragent@cursor.com>
Build the KMP framework with plain Gradle logs before xcodebuild, skip the Xcode Gradle script phase, and include gitignored build outputs in jvm-compile-outputs artifacts.
Co-authored-by: Cursor <cursoragent@cursor.com>
Stamp AppUserModel IDs on installer shortcuts to match the desktop app, embed proper ICOs in launchers, derive beta setup names from versionName, and sync/copy helpers for the Windows VM workflow.
Co-authored-by: Cursor <cursoragent@cursor.com>
Use in-window popups with window-space anchors clamped to the chat pane, measure before reveal, and limit pointer blocking to the active message.
Co-authored-by: Cursor <cursoragent@cursor.com>
- drop the tag-push trigger: caches saved by tag-scoped runs are visible
to that tag only (GitHub docs, 'Restrictions for accessing a cache'),
so every tag build started cold and warmed nobody
- dispatch instead, preferably from the default branch; the tag does not
need to exist, publish creates it at the built commit; warn when
dispatching from a non-default branch
- merge check-build into resolve-version (one runner + one checkout
instead of two)
- merge record-build into publish: save the build-success marker at the
start of publish
- fix softprops input: overwrite_existing does not exist upstream
(silently ignored); the valid input is overwrite_files
ci(release): dump jpackage out/err logs when macOS packaging fails
ci(windows): align rust cache save key with restore key (arch + v5)
The save action wrote windows-rust-Windows-v3-<lock> with no arch while
restore fanned out on windows-rust-Windows-<arch>-v4-<lock> — so every
saved entry was unreachable by design. It also made the two matrix legs
race to reserve the same key: ARM won, x64 got 'another job may be
creating this cache'. Save now uses the exact restore key (arch + v5);
restore falls back only to same-arch v5 prefixes to avoid pulling the
stale archless v3 entries into the wrong build.
ci: add temporary cache-inspect workflow
ci(cache): one-shot push trigger so the temp inspector can run from a branch
ci(cache): fix field name in inspector (api uses key)
ci(cache): inspect via exact original path lists, per-OS matrix
ci(cache): save only tools/android/build-jar gradle scopes
Packaging scopes' build trees (1.3-2 GB each, 6 scopes) were ~90% installer
outputs and jlink runtime duplicates that actions/cache negation patterns
are unable to exclude (actions/toolkit#713). Saving ~7 GB per run kept the
repo at the 10 GB cache cap and LRU-evicted build-jar/android/windows-rust
entries within minutes of being saved. Packaging scopes are now
restore-only: they fall back to the build-jar scope via restore-keys, and
their outputs regenerate from the JAR artifact in a couple of minutes.
Also remove the temporary cache-inspect workflow (purpose served).
ci(cache): unify gradle-build path lists so restore-keys cross-hit
Restores differ per path list: actions/cache computes a version stamp from
the path list and only matches restore-keys via a version stamp (hash of
the path list): entries with different path lists are invisible to each
other's restore-keys, even with a shared key prefix. The build-jar scope
kept compose/tmp while the generic list negated it, so the
'-v2-build-jar-' fallback never matched for packaging scopes (measured in
run 39349881796: 'Cache not found' listing the fallback prefix). Drop the
ineffective '!*/compose/tmp' negation (no-op at save time anyway per
actions/toolkit#713) so all non-android scopes share one version stamp;
packaging restores now warm from the build-jar entry.
ci: enable Gradle configuration cache in CI; temporary --profile on Linux packaging
ci: revert configuration cache (script-closure tasks not serializable); keep --profile diagnostic
ci: drop temporary --profile diagnostic (profile captured; deb/rpm identified as the quiet ~45s/~23s hogs)
ci: build the macOS x64 DMG on an Intel runner (macos-26-intel)
macos-latest resolves to an arm64 image, so the 'Intel' matrix job ran on
Apple Silicon and jpackage shipped arm64 binaries under the -intel.dmg
name (v1.2.0-beta18): Intel Macs refuse it with 'this program isn't
supported on this mac'. jpackage cannot cross-build, and the Gradle
daemon JVM criteria (gradle-daemon-jvm.properties) provision the daemon
for the host arch, so every arch decision followed the runner. Pin each
matrix entry to its native runner.
proguard: keep JNA entire API from stripping (native GetStaticMethodID lookups)
jnidispatch resolves com.sun.jna.Native#dispose by name from native code
during initIDs; ProGuard stripped it (not a Java-native method, no Java
references), so every Windows package died at launch with
'Failed to launch JVM':
UnsatisfiedLinkError: Can't obtain static method dispose
from class com.sun.jna.Native
at com.sun.jna.platform.win32.Shell32.<clinit>
Linux/macOS never load Shell32, so only Windows builds were affected
(both x64 and arm64). Also keep classes extending/implementing JNA
(Libraries, Callbacks, Structures are loaded reflectively at runtime).
ci: speed up release builds; desktop/chat and theme updates
Share JVM compile outputs from Build JAR, fix macOS Playwright caching,
cache DMG export, and enable configuration cache on CI. Includes desktop
chrome, attachment preview, system theme, and related shared fixes.
Co-authored-by: Cursor <cursoragent@cursor.com>
ci: drop configuration cache on release (Compose ProGuard incompatible)
Co-authored-by: Cursor <cursoragent@cursor.com>
ci: skip JVM recompile on macOS/Windows when Build JAR artifact is restored
Gradle has no cross-OS execution history for restored classes; gate
compileKotlinJvm, desktop compileKotlin, and generateAppBuildInfo when
FROMCHAT_PREBUILT_JVM is set and outputs are present.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Title bar: hover animation on window buttons; restore icon flipped so the
lower tile sits left of the upper one; 2-pane search morph, mode-switch
continuity and NavTransitionMuting to stop one-frame garbled chat switches
- Linux: WM_CLASS "FromChat" (main class FromChat.kt), skiko/virgl workaround
shipped at build time via compileSkikoVirglPatch (GraphicsApi_jvmKt shadow in
desktop.jar), MESA_GL_VERSION_OVERRIDE=3.0 (+vblank_mode=3) baked into the
.desktop Exec on ARM64, Mac tray icon fix
- Attachment drag-drop: reject non-file payloads (desktop shortcuts) with a
bridge-scoped AttachmentDropRejection — only the hovered element tints with
"This isn't a file", the element rattles once, stays scaled back ~1s and then
blurs release, tint and scale fade together; strict isFile validation so
nothing is ever sent under name "file"
- Attachments: rounded tile icon, failed-image tile UI, cancel-X progress state
fixes, DecryptedFile/ImageCache hardening (no more cache_write_failed)
- Mac: hide image capture button on desktop
- Skill: build-linux-vm fast iteration loop learned from the VM session
On any DE hosting StatusNotifierItem (GNOME 46+/55 with the AppIndicator
extension, KDE, etc.) the shell now hosts both the icon and the menu:
we export org.kde.StatusNotifierItem + com.canonical.dbusmenu over
dbus-java (pure Kotlin) and register with StatusNotifierItemWatcher.
GNOME renders the popup itself from our DbusMenu tree
(Connected | sep | Show | About FromChat | sep | Quit); label changes
go out as ItemsPropertiesUpdated. AWT/XEmbed stays as fallback for
X11-only shells; Windows/macOS untouched.
dbus-java quirks resolved in pure Kotlin (details in tray-sni/DESIGN.md):
properties are served by a hand-rolled org.freedesktop.DBus.Properties
impl with explicit-signature Variants (DBusBoundProperty cannot express
a(iiay) from Kotlin), and outer return structs (GetLayout/AboutToShowGroup)
implement DBusSerializable to dodge the erased-field export XML bug.
Verified on Ubuntu ARM64 VM: registers on the real session bus; icon in
the GNOME panel; native menu opens; Show/About/Quit callbacks reach the
app on the EDT; hardware-accelerated rendering unchanged (0 fallbacks,
0 shader errors).
- DesktopTrayMenu: apply the screen density to the tray menu's own Window,
anchor from the TrayIcon event instead of stale MouseInfo, dp-based gap
- DesktopScale: usable accessors before composition (awtScale/contentDensity/sizeRatio)
- Main: color tray icon pre-scaled to 48x48 with isImageAutoSize, skip
persisting the first post-restore geometry pass and maximized windows
Diagnosed from inside the Ubuntu 26.04 VM via prlctl: the app's own log at
~/.local/share/FromChat/fromchat/logs/current.log contains
at java.desktop/java.awt.SystemTray.getSystemTray(Unknown Source)
so getSystemTray() throws where isSupported() had returned true. GNOME has no
StatusNotifierItem host for AWT's libappindicator bridge to register with, and
the deb's libayatana-appindicator3-1 dependency is present — so the dependency
was the wrong lever.
DesktopTrayHost called getSystemTray() unguarded while the two lines around it
were already runCatching, so the throw escaped the DisposableEffect as an
uncaught exception: the tray icon never appeared and nothing was clickable. It
is now guarded and logged (TrayMenu "System tray unavailable: <class>: <msg>"),
so the cause lands in current.log instead of as a stack trace with no context.
This makes the failure graceful; it does not make AWT trays work on GNOME.
Doing that needs a real StatusNotifierItem over D-Bus rather than AWT.
Flip the tray context menu below the cursor when there is no room above
On GNOME the tray icon sits in the top bar, so the menu could only ever be
placed by clamping: the old code always asked for anchor.y - height, and when
that fell outside the working area coerceIn pinned it to usable.y regardless
of where the click was. That is what made the menu look offset from the click.
Placement now tries up-and-left first and flips to below (and to the right when
there is no room on the left) instead of clamping, with a small 4px gap from the
cursor.
Reported on Ubuntu 26.04 (GNOME, Parallels) and Linux x64: the app renders
at 1:1 and looks tiny on a 2K screen — it "completely ignores scaling".
Compose derives LocalDensity from the AWT device configuration, and on a HiDPI
or fractional-scaled desktop Java frequently still reports 96 DPI. GDK exports
the real scale as GDK_SCALE (integer) times GDK_DPI_SCALE (fractional), and
sun.java2d.uiScale is the AWT property that picks it up, so it is now passed
through to the packaged launcher (and to `run`) on Linux.
Only applied when the computed scale is above 1:1, so unscaled desktops keep
Java's own detection and nothing changes for them.
The ARM64 installer still died with "installer has no payload for this CPU
(x86_64)". Two installers, two architectures, and each needs tooling compiled
for its own target.
fromchat-pack embeds the app-image payload, and payload.rs selects it with
std::env::consts::ARCH — the stub's own compile-time architecture, not the
machine's. The stub is a Windows x64 binary, so on ARM64 it runs emulated,
reports x86_64, and looks for an x86_64 payload that the ARM-only bundle never
embeds. The ARM asset was therefore structurally incapable of installing.
So the ARM64 leg now builds its own aarch64-pc-windows-msvc tooling on the ARM
runner (cargo build --release --workspace with no --target builds for the
host) instead of downloading the x64 artifact, which is now x64-only.
ensureWindowsRustBinaries is existence-based and rebuilds when binaries are
absent, so this leg is self-healing if the explicit build step ever misfires.
The Rust cache key was also arch-blind — windows-rust-${{ runner.os }}-v3-... —
so an x64 target/ directory could be restored onto the ARM runner and make the
"binaries ready" check pass with the wrong architecture. It now includes
${{ runner.arch }} and is bumped to v4, so the two legs cannot share a cache.
This also reverts the PROCESSOR_ARCHITEW6432 sniffing added in 35492fc: with
natively compiled stubs it is unnecessary, and it would have been actively
wrong — an x64 installer running emulated on ARM64 should install the x64
payload, not be redirected to the arm64 one.
Turn the Windows tooling job into a 2-entry matrix
The ARM64 fix had grown into duplicated, arch-conditional steps inside
windows-package: rust-toolchain, rust cache, a binary verification loop and a
build step, all gated on matrix.arch. That is per-architecture data, not control
flow, so it belongs in a matrix.
windows-common (x64-only) becomes windows-tools, a 2-entry matrix over
{x64 -> windows-latest -> x86_64-pc-windows-msvc} and
{arm64 -> windows-11-arm -> aarch64-pc-windows-msvc}. Each leg builds the
installer tooling natively — cargo has no --target, so it builds for the host —
and uploads it as windows-rust-tools-<arch>. windows-package then downloads
that artifact unconditionally again, and its two setup-java-gradle calls
collapse into one driven by a matrix key.
The ARM leg still needs its own step for the packaging JDK
(ensure-windows-arm64-jdk.cmd); that one is genuinely arch-specific rather than
matrix data.
This keeps the invariant that matters: fromchat-pack selects the embedded
payload with std::env::consts::ARCH, so each architecture's installer must be
compiled for its own target or it reports the wrong CPU and finds no payload.
beta11 shipped a subtly broken package. jpackage on a modern dpkg emits
**zstd** control tars (control.tar.zst), which the injection did not handle:
its `when` fell through to plain `tar cf`, so the rebuilt member was an
uncompressed tar still named control.tar.zst. dpkg tolerated it, but it is a
malformed package.
Verified against the actual beta11 artifact: the control member started with
`2e 2f 00 00` ("./", i.e. an uncompressed tar) and after this fix starts with
`28 b5 2f fd`, the zstd magic. data.tar.zst is byte-identical and the member
set is unchanged, because only the control member is ever rewritten.
Also fixed a second bug visible in the shipped control file: jpackage emitted
no Depends line, so replacing one *and* prepending one produced two. Every
Depends line is now stripped before a single one is prepended.
The injection self-checks the rebuilt member's framing and the control tar's
non-emptiness, and a failure still only warns and leaves the deb untouched.
AWT's Linux tray is not built into the JDK: it dlopens libappindicator, and
without it SystemTray.isSupported() is false. The app gates the whole tray on
that call (Main.kt:409) and silently renders nothing when it is false — which
is why the tray on KDE Plasma 6 had no working clicks and no explanation in the
log.
jpackage cannot be told about dependencies through Compose's `linux { }` DSL,
so the generated .deb shipped a control file with no Depends at all and pulled
nothing in. packageReleaseLinux now injects
`libayatana-appindicator3-1 | libappindicator3-1, libdbus-1-3` into that
control file (Ayatana is the maintained package; the legacy name stays as an
alternative for older distros).
The `ar` container is rewritten in Kotlin rather than shelled out to, because
BSD ar (macOS) injects a `__.SYMDEF SORTED` member while GNU ar (Linux, where
this runs) does not, and rebuilding a package with the wrong dialect corrupts
it. Member order is preserved and headers are reused verbatim, with only the
size field refreshed for the replaced control member. Everything is wrapped so
a failure logs a warning and leaves the deb exactly as jpackage built it,
rather than failing the release.
Verified against a synthetic GNU-style .deb: members read back in order
(debian-binary, control.tar.gz, data.tar.gz), the control file gains the
Depends line and keeps Package/Architecture, and the data payload round-trips
intact. BSD `ar t` also reads the result.
Main.kt now logs SystemTray.isSupported() once per run, so the next report
says outright whether the tray was available instead of us inferring it.
Still not covered: the .rpm gets no dependency injection (no jpackage option
and no easy rpm rewrite), and the AppImage still relies on the host having
libappindicator.
On KDE Plasma 6 (StatusNotifierItem host) the tray icon rendered as a solid
white square and clicks did nothing.
The square is explained by the asset: app_icon.png is a white glyph on
transparency, and all 3277 of its fully transparent pixels carry white
(255,255,255) RGB. Plasma hands the icon AWT supplies to the StatusNotifier
host, which does not honour the alpha channel — so every transparent pixel
painted white and the whole 22px tile became a white square.
The Linux tray now uses the coloured app icon instead (saturated
purple/pink/blue gradient), which survives a lost alpha channel, and skips
stripTrayMatteBackground for it — that helper exists to erase a white matte
background behind the macOS template glyph, and applied to the coloured icon
it would punch holes in its white glyph. macOS and Windows keep the template
glyph and the matte stripping exactly as before.
macOS: the app died before the first frame with "MacOS does not support
OPENGL rendering API" from SkiaLayer's constructor. Compose's platform DSL
(windows { } / macOS { }) has no jvmArgs of its own, so the unqualified
jvmArgs(...) in `windows { }` resolved to JvmApplication.jvmArgs — the one
application-level list the plugin writes into the launcher config of *every*
platform it packages. The published macOS bundle therefore shipped
-Dskiko.renderApi=OPENGL and -Dsun.java2d.d3d=true. GPU args now come from
hostGpuJvmArgs(), added per host: OPENGL on Linux and Windows x64, ANGLE on
Windows ARM64, nothing on macOS (Skiko defaults to Metal there). Verified by
repackaging locally: the macOS FromChat.cfg no longer carries those flags and
the packaged app starts, connects and renders.
Windows ARM64: the installer aborted with "installer has no payload for this
CPU (x86_64)". payload.rs picked the payload with std::env::consts::ARCH,
which is the *installer's* compile-time arch — and the stub is built once on
windows-latest (x64). On Windows ARM64 it runs emulated, reports x86_64 and
looks for an x64 payload the ARM-only bundle never embeds. host_arch() now
prefers PROCESSOR_ARCHITEW6432, which carries the machine's real architecture
for WOW64/emulated processes. payload_zstd() is only called by the runtime
binaries, never by the packer, so this cannot affect what gets packed.
Notifications: a Russian UI produced notifications titled "Main chat" (the
English default) because getString() outside a composition resolves against
the default resource. The chats screen already resolves the title the way the
UI renders it, so it now publishes that through LocalizedChatTitles and the
sink reuses it — same string the user sees, no second resolution path.
Compose Desktop's AbstractProguardTask builds ProGuard's -libraryjars by
walking <javaHome>/jmods and nothing else. Adoptium stopped shipping the
jmods/ directory in JDK 25 archives (verified: 0 jmods entries in both the
linux/x64 and macOS/aarch64 Temurin 25.0.4.1 tarballs, vs 71 for Temurin
17), so on the runners the desktop packaging JDK resolves to
/usr/lib/jvm/temurin-25-jdk-amd64 and ProGuard gets no JDK whatsoever.
Every class then fails to resolve java.lang.Object — 1.3M
"can't find superclass or interface java.lang.Object" warnings, followed by
"Please correct the above warnings first".
This is why the Java 25 toolchain bump broke every release while the
v1.2.0-beta release on the toolchain 17 build kept working, and why a local
build with the JetBrains Runtime (which does ship jmods) succeeded while CI
did not. ProGuard's version was never the cause; 7.10.0 was still kept
because it is what understands this project's Kotlin 2.4 metadata.
Install Zulu 25 (still ships jmods) in the desktop-proguard job and export
it as FROMCHAT_PACKAGING_JDK; setup-java runs before Temurin 17 so JAVA_HOME
stays on 17 for Gradle and the Android build. resolvePackagingJdkHome() now
warns loudly when the JDK it picks has no jmods/, instead of leaving it to
ProGuard's wall of warnings.
Use setup-java's `path` output for FROMCHAT_PACKAGING_JDK
setup-java@v5 names the installed JDK's location `path`, not `java-path`
(the v4 name), so the beta4 run exported an empty value, fell back to the
jmods-less /usr/lib/jvm/temurin-25-jdk-amd64 and failed with the same
"can't find superclass or interface java.lang.Object" wall of warnings.
The Gradle-side guard added in b0159eb correctly named the offending JDK
in the beta4 log, which is what pointed at the empty export.
Give the Linux packaging jobs a jmods-capable JDK too
beta5 proved the ProGuard fix: Build JAR passed in 9m35s, and Windows
(x64 + ARM) and macOS (arm64 + Intel) all packaged successfully. Only the
two Linux jobs failed, at :app:desktop:createRuntimeImage — jlink exits 1
because jlink defaults its --module-path to <javaHome>/jmods, the same
directory ProGuard needs. So the jmods gap bites twice: ProGuard degrades
into a wall of warnings, jlink just dies.
macOS/Windows runners happen to resolve a JDK 25 that still ships jmods;
ubuntu-latest's /usr/lib/jvm only has the jmods-less Temurin 25. Point the
Linux jobs at Zulu 25 via the setup action's desktop-jdk input, and stop
the packaging step's `export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"` from
clobbering it with Temurin 17 (hence ${VAR:-$JAVA_HOME}).
Also widen the Gradle guard to name jlink, since that is what actually
fails on a jmods-less JDK during packaging.
Fix two Windows ARM64 failures: JDK-8371740 and a needless JDK download
Neither is related to the jmods work; both were visible only on the
windows-11-arm runner.
1. Gradle hung at build shutdown. The runner's ambient JAVA_HOME is Temurin
21.0.12 aarch64, and JDK-8371740 — a LinkedTransferQueue race fixed only
in JDK 26 — makes Gradle on Windows ARM64 hang whenever it runs on JDK
21+, failing the build after the installer was already written with
"Timeout waiting for concurrent jobs to complete". Gradle's own repo and
linroid/Ketch both carry workarounds for this. Pin the Gradle JVM to 17,
which is what the x64 job already does; Adoptium ships no Windows aarch64
17, hence the new `java-distribution` input and zulu there. Packaging is
unaffected: resolvePackagingJdkHome() rejects 17 as too old and falls back
to the Java 25 toolchain JDK for jpackage.
2. "Ensure ARM64 packaging JDK" stalled for minutes and looked hung. The
script only scanned C:\Program Files\Eclipse Adoptium, but hosted ARM64
runners ship their ARM64 Temurin 21 under the hostedtoolcache, so the scan
missed it and fell through to downloading a ~180 MB JDK via
Invoke-WebRequest + Expand-Archive. Re-running the job showed it still
installing 3m24s later. Accept JAVA_HOME when it reports os.arch=aarch64 —
the script's own comments already call Java 21 good enough as the Gradle
daemon JVM.
Stop the Gradle build cache from being invalidated on every release
Two independent defects, both found while a release sat at 8.83 GB of the
10 GB per-repo cache limit with entries being evicted.
1. set-version rewrote build.gradle.kts with sed on every run, and that file
is hashed into the Gradle build-cache key — so each release produced a
brand-new key and orphaned the previous one. The repo inventory showed the
damage: windows-package-x64, macos-x64 and windows-package-arm64 each had
two ~1.1 GB entries differing only in that hash. build.gradle.kts now reads
-PversionName/-PversionCode (injected as ORG_GRADLE_PROJECT_* by
set-version) and keeps its previous values as defaults, so local builds are
unchanged. Types stay String/Integer for the `as Int`/`as String` consumers
in app/android and app/shared. Side benefit: hash-release-build-inputs also
hashes build.gradle.kts, so the "inputs unchanged" marker no longer churns
per version either.
2. Every desktop scope archived the Android build dirs (never built there)
plus the final installers, the exported ProGuard jars, the jpackage runtime
image and the ProGuard temp output — all regenerated for each version, and
together the bulk of a ~1.7 GB entry. Those paths are now excluded, which
is what kept the repo over the limit and into eviction (GitHub evicts by
oldest last-accessed once the limit is hit).
Restore and save path lists stay in sync: actions/cache silently skips the save
when they differ.
Cross-tag reuse is NOT addressed here and cannot be: per the docs, a run for one
tag cannot read another tag's cache, and the automatic retry against the default
branch is a no-op for us because main has no app/desktop module to warm it.
Stage each packaging host's own Skiko/JavaFX natives
The beta7 desktop builds cannot start. Both failures the user reported
(macOS arm64 and Linux arm64) are the same bug:
ERROR: [Desktop] uncaught on main Failed to loade library
.../libskiko-macos-arm64.dylib
Caused by: java.lang.UnsatisfiedLinkError: Can't load library
ProGuard runs once, on ubuntu-latest, and its output is shipped to every
packaging job. But the Compose desktop runtime classpath is host-specific:
that single ProGuard run froze in skiko-awt-runtime-linux-x64 and
javafx-*-27-linux, and nothing for any other target. Inspecting the published
macOS arm64 DMG confirmed it — 140 jars, the entire ProGuard set, with
skiko-awt-runtime-linux-x64 as the only Skiko runtime and no
libskiko-*-arm64 anywhere in the bundle, plus no JavaFX natives either. The
macOS x64 build is broken the same way; Linux x64 is the only target that
happened to match, and the Windows installers shipped Linux natives too.
stageCiPrebuiltProguard now also stages the packaging host's own natives
(skiko-awt-runtime-<os>-<arch>, javafx-*-<os>-<arch>) from its resolved
runtimeClasspath. Skiko and JavaFX each pick their library by os/arch, so the
foreign Linux copies stay inert and cost only some bundle size. The task
fails loudly if a host resolves no natives at all.
Verified locally end-to-end with a prebuilt-ProGuard packaging run:
Contents/app/libskiko-macos-arm64.dylib (21.3 MB) is now present, which is
the exact path Skiko failed to load before.
Keep ProGuard's output cached in the one scope that runs it
Follow-up to 0e7b181, which excluded compose/tmp from every desktop scope
to shrink the cache entries. That was too blunt for the build-jar scope:
compose/tmp/main-release/proguard holds proguardReleaseJars' *output*, so
without those jars Gradle can never consider the task up-to-date and
re-runs its ~8 minutes on every re-run of a tag.
Split the non-android path list in two. build-jar keeps compose/tmp; the
packaging scopes drop it, because they never run ProGuard — they stage the
Build JAR artifact's jars instead, so their copy is a redundant 122 MB.
The Compose plugin defaults to ProGuard 7.8.0, which cannot process the Java 25
class files produced by jvmToolchain(25): it fails to load the JDK itself and
aborts with thousands of "can't find referenced class java.lang.Class" /
"the configuration refers to the unknown class 'java.lang.String'" warnings,
ending in "Please correct the above warnings first". Every release build has
been failing there since the toolchain bump.
Pin ProGuard 7.10.0 (7.9.0 added Java 25 support; 7.10.0 is the first release
that also understands the Kotlin 2.4 metadata this project compiles against).
Also ignore JOGL's JavaFX bridge. JetBrains Runtime ships jogl.all.jmod in its
jmods directory, which ProGuard loads as a library jar; its classes extend
program-side JavaFX classes, so JBR-based (macOS) builds fail with
"7 instances of library classes depending on program classes". Temurin JDKs
ship no such jmod, so that part is macOS/JBR-only.
Verified locally: :app:desktop:exportReleaseProguardForCi succeeds and exports
app/desktop/build/ci/proguard/.
jvmToolchain(25) (f89619a) compiles classes to class file 69, but
resolvePackagingJdkHome() accepted any JDK that shipped jpackage. On Windows
ARM64 the candidate list hardcoded jdk-21-arm64 first and
ensure-windows-arm64-jdk.cmd provisioned Temurin 21; on macOS the first
candidate was Adoptium 17. jpackage then bundled a Java 17/21 runtime and the
installed app died at launch with UnsupportedClassVersionError on the hidden
launcher's stderr (invisible: GUI subsystem, no error dialogs).
CI hit this too: setup-java-gradle installs Temurin 17 and the workflows
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME", which the resolver returned
verbatim — so the x64 installer built by desktop-windows-x64.yml was broken
on launch.
- build.gradle.kts: version-gate FROMCHAT_PACKAGING_JDK / JAVA_HOME /
candidate JDKs to >= desktopJvmTarget (25) with a warning, then fall back to
the Gradle-provisioned toolchain JDK (recursive .gradle/jdks scan).
- ensure-windows-arm64-jdk.cmd: prefer an installed jdk-25* or the
Gradle-provisioned Java 25 toolchain JDK; Temurin 21 remains only the
last-resort daemon JVM.
Ported from c53b54b, minus ensure-windows-x64-jdk.cmd and diag-launch.ps1:
this branch has no packageWindowsX64 path to feed, and diag-launch.ps1 is
machine-specific and unreferenced.
Verified on macOS: createDistributable now bundles JAVA_VERSION 25.0.4.1,
matching class file 69, where a previous build had bundled 17.0.19.
The tray right-click popup took roughly 500ms to appear, then flashed at the
top-left of the display at the wrong size before settling; on HiDPI Windows it
was also pinned to the bottom-right corner of the screen rather than the click.
- Show the popup transiently (Window.opacity = 0) while Compose measures it,
then size, position and reveal it. This removes the open delay without the
off-screen parking trick, which Windows clamps back to (0,0) and paints,
causing the flash at the wrong size.
- Derive the anchor from MouseInfo rather than the TrayIcon event and normalise
it into the graphics configuration's coordinate space. On HiDPI Windows the
event carries raw physical pixels, which landed outside every screen and
pinned the menu to the bottom-right corner.
- Clamp to the monitor's working area (bounds minus taskbar insets) so the menu
is never hidden behind the taskbar.
- Pick the monitor that actually contains the click instead of the one the
unpositioned window happens to sit on.