130 Commits
Author SHA1 Message Date
Vitor Pamplona 8a183d1cbd refactor(ci): move the Winget bump out of CI onto a maintainer machine
Mirrors the Homebrew cask change. The old design stored a classic `public_repo`
PAT as WINGET_TOKEN and handed it to the third-party
vedantmgoyal9/winget-releaser action: a token with write access to every public
repo the owning account can reach, given to code we do not control, in a place
any push-access collaborator could read it from (a pushed branch containing a
workflow runs with repo secrets).

- CI (bump-winget.yml, now "Sync Winget Manifest Reference") uses GITHUB_TOKEN
  only: downloads the MSI, computes the sha256, reads the ProductCode from the
  MSI Property table via msitools, and opens an in-repo PR syncing
  desktopApp/packaging/winget/. Runs on ubuntu (1x billing) rather than Windows
  since msitools reads the Property table fine.
- scripts/bump-winget.sh does the upstream PR. It needs NO new token: it drives
  `gh`, which a maintainer already has authenticated, and it does not need
  wingetcreate (Windows-only) because the manifests are plain YAML — so it runs
  from macOS or Linux.

Add the three reference manifests under desktopApp/packaging/winget/, matching
the schema 1.12.0 shape used upstream. All three validate against Microsoft's
published JSON schemas.

Validation caught one real bug worth noting: an all-digit 64-char InstallerSha256
parses as a YAML *integer* and fails the schema's `string` type, so it is written
quoted. ProductCode is re-read every release because jpackage regenerates it per
build; it is the ARP key `winget upgrade` matches on.

Drops the last package-manager PAT from the secret inventory.
2026-07-29 10:30:21 -04:00
Vitor Pamplona db529444c4 refactor(ci): move the Homebrew cask bump out of CI onto a maintainer machine
`brew bump-cask-pr` forks homebrew-cask into the token owner's account, which
requires a CLASSIC PAT with the `repo` scope. That scope cannot be narrowed: it
grants write to every repository the owning account can reach, including this
one. As an Actions secret it would be usable by anyone with push access here,
because a pushed branch containing a workflow runs with repo secrets — a strict
escalation for a channel that ships one DMG a month.

Split the work so the token never becomes a CI secret:

- CI (bump-homebrew.yml, now "Sync Homebrew Cask Reference") does the
  error-prone bookkeeping with GITHUB_TOKEN only: downloads the DMG, asserts it
  is notarized + stapled, computes the sha256, and opens an in-repo PR syncing
  desktopApp/packaging/homebrew/amethyst-nostr.rb. This makes it a third sibling
  of the amy/geode formula-sync workflows rather than a special case.
- scripts/bump-homebrew-cask.sh does the upstream PR from a maintainer's shell,
  re-verifying sha256 and the notarization ticket against the live asset first,
  and refusing to submit if either fails. Verified against v1.13.1: the sha256
  matches and it correctly refuses on the missing notarization ticket.

Drop HOMEBREW_TOKEN from the secret inventory, and rewrite the two formula
TODO(bootstrap) notes that proposed reintroducing it so a future homebrew-core
submission follows the same local pattern.
2026-07-29 10:19:57 -04:00
Vitor Pamplona 8a8f58e7ad fix(release): actually notarize the macOS DMG, and verify it
The macOS leg ran only `packageReleaseDmg`, which SIGNS the DMG but does not
notarize it — notarization is a separate Compose task. The `notarization {}`
block in desktopApp/build.gradle.kts only supplies credentials; nothing invoked
it. So every release up to v1.13.1 shipped a signed but unnotarized DMG:
`xcrun stapler validate` reports no ticket on either the .app or the .dmg, and
Gatekeeper blocks it on first launch.

Append `:desktopApp:notarizeReleaseDmg` on the macOS leg. Compose 1.11.1's
AbstractNotarizationTask runs `notarytool submit --wait` and then `stapler
staple` in place, so the asset-collection step still finds the same file. Raise
that leg's inner timeout to 45m since the submit blocks on Apple.

Gate it on the cert AND all three notary secrets, so forks and credential-less
runs keep producing a plain unsigned DMG instead of failing.

Add a verify step that asserts the stapled ticket. The bug survived many
releases precisely because nothing ever checked the outcome.

Also update the reference cask to 1.13.1 and make it pass `brew audit --new
--cask` + `brew style` cleanly: add the missing conflicts_with (the tiling
window manager's cask installs the same Amethyst.app), add depends_on :macos,
fix stanza order, drop the unnecessary `verified:` (url and homepage share a
domain), and widen the zap to the state dirs the source actually uses.

Correct BUILDING.md's macOS state table, which listed a
com.vitorpamplona.amethyst.desktop.plist that does not exist and omitted
~/.amethyst. Note that Java's Preferences API writes to a SHARED
com.apple.java.util.prefs.plist, which is why the cask must not zap it.
2026-07-29 09:59:41 -04:00
Vitor Pamplona a302b82413 fix(ci): replace the unresolvable homebrew-bump-cask action with brew itself
bump-homebrew.yml pinned macauley/action-homebrew-bump-cask to
ad984534de44a9489a53aefd81eb77f87c70dc60, commented "v4.0.0". That SHA does not
exist — the action's only release is v1 (445c4239) from 2021. GitHub resolves
every `uses:` during "Set up job", so the job died before running a single step,
which no `if:` gate can avoid. It stayed invisible because the workflow had
never been triggered; fixing the trigger surfaced it immediately.

Call `brew bump-cask-pr` directly instead. It is the same command BUILDING.md
already documents for manual recovery, and it removes an unmaintained
third-party action that would hold a PAT with write access to homebrew-cask.

Split the workflow into check (ubuntu) + bump (macOS): cask commands are
macOS-only, but the bump is gated on a cask that does not exist yet, so the
10x-billed macOS job is only created once there is something to bump.
2026-07-29 09:42:20 -04:00
Vitor Pamplona 054dffd55d fix(ci): make the Homebrew/Winget bump workflows actually fire
All four bump workflows triggered on `release: types: [released]`, which never
fires here: create-release.yml publishes the release with GITHUB_TOKEN, and
GitHub suppresses workflow-triggering events for GITHUB_TOKEN actions. They had
zero runs across every release up to v1.13.1.

Switch them to `workflow_run` on "Create Release Assets" completion, filtered to
a successful tag push. That also removes a latent race: `released` fired while
the matrix legs were still uploading assets, whereas workflow_run fires after
all of them finish.

The workflow_run payload carries no draft/prerelease flags, so add a
resolve-release composite action that reads them back from the API and feeds
assert-stable-release, keeping the defense-in-depth guard intact instead of
inferring stability from the tag string alone.

Also gate the cask/winget bumps on the package existing upstream. Neither
`amethyst-nostr` nor `VitorPamplona.Amethyst` has been bootstrapped, and
bump-cask-pr/winget-releaser can only update an existing package — without the
gate, fixing the trigger would file a spurious [release-ops] issue on every
release.

Docs: correct the claims this uncovered — Homebrew/Winget are not shipping,
macOS is arm64-only (no Intel DMG), the release carries 31 assets (13 Android,
not 12), Maven Central publishes from a step inside deploy-android and lags
repo1 by tens of minutes, RELEASE_NOTES_ID is minor-releases-only, and note the
git-credential-manager hang that blocks the release push.
2026-07-29 09:37:24 -04:00
Claude e8739f5e83 ci: run geode tests in a dedicated isolated job
The first cut appended :geode:test to the build-desktop matrix command.
That was wrong twice over: geode is JVM-only, so it ran 3× across the
ubuntu/macos/windows matrix, and — because org.gradle.parallel=true —
its default suite's CPU-heavy throughput benchmarks (a 1M-event mirror
sync, WireReqFloor, NegentropyServerReconcile) ran concurrently with the
timing-sensitive quartz relay-client tests, flaking
NostrClientReqBypassingRelayLimitsTest.denseSecondBeyondCapIsSteppedPastWithoutStalling.

Move :geode:test into its own test-geode job (needs: lint, ubuntu, JVM
21) so it runs once and its benchmark load can't starve another module's
timing assertions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KCdJwdhGtmLZ12ViS56S3k
2026-07-24 19:37:09 +00:00
Claude 0a30231196 feat(geode): add a release & distribution pipeline
geode was runnable only via ./gradlew :geode:run and was absent from CI.
Give it the same release process as the amy CLI (it's the same kind of
application-plugin JVM module), plus the pieces a long-running server
daemon needs that a one-shot CLI does not.

- Main.kt: add terminal --version/-V and --help/-h flags so a packaged
  binary has a fast, exit-0 command (Homebrew test block, package smoke
  checks, Docker healthcheck).
- build.gradle.kts: jlinkRuntime + geodeImage (portable flat app-image
  with a bundled JRE, plus config.example.toml + geode.service under
  share/) + jpackageDeb/jpackageRpm, mirroring cli/. No Compose to
  exclude — geode depends only on :quartz.
- Dockerfile + .dockerignore: multi-stage image (gradle installDist ->
  temurin JRE), the primary channel for relay operators.
- packaging/: systemd unit, macOS hardened-runtime entitlements, and a
  reference Homebrew formula.
- scripts/asset-name.sh: geode_asset_name/collect_geode_assets under the
  canonical geode-<version>-<family>-<arch>.<ext> scheme.
- create-release.yml: build-geode matrix (tarball + deb/rpm + no-JRE jvm
  bundle, with a serve+NIP-11 smoke test of the jlink image) and a
  docker-geode job pushing ghcr.io/<owner>/geode:<version> (+ :latest).
- bump-homebrew-geode-formula.yml: auto-sync the reference formula on
  stable releases.
- build.yml: run :geode:test in CI (it ran in no workflow before).
- README.md + plans/2026-07-24-geode-release.md: operator docs + design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KCdJwdhGtmLZ12ViS56S3k
2026-07-24 17:56:50 +00:00
Claude b86bc5b798 ci: replace abandoned android-test-report-action with mikepenz/action-junit-report
asadmansr/android-test-report-action@v1.2.0 is a Docker action whose
Dockerfile is `FROM ubuntu:18.04` + `apt-get install python` (Python 2).
Ubuntu 18.04 (bionic) is end-of-life, so its apt archives are now
unreliable and Python 2 has no installation candidate — the image rebuild
runs on every CI invocation, takes ~8 minutes, and has started hard-failing
the test-and-build-android job (`E: Package 'python' has no installation
candidate`). The action was last released in 2020 and is unmaintained, and
it was referenced by a movable tag rather than a pinned commit.

Swap it for mikepenz/action-junit-report (actively maintained, Apache-2.0,
JS action — no Docker rebuild), pinned to the v6.4.2 commit SHA. Use
annotate_only so it needs no `checks: write` permission and keeps working on
pull requests from forks; fail_on_failure keeps the job red when a unit test
fails, matching the previous step's behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMtpSuyr8FBh2ZQ22BV4ZP
2026-07-22 16:25:23 +00:00
Claude 91765c56aa fix(ci): reclaim working-tree ownership after Crowdin Docker action
The crowdin/github-action runs inside a Docker container as root, so files
and directories it downloads are root-owned. When Crowdin creates a brand-new
locale folder (e.g. values-en-rGB/ under commons), the unprivileged runner
user in the peter-evans/create-pull-request step can't unlink files inside it,
so the branch checkout aborts with 'unable to unlink ... Permission denied'.

Add a chown step that reclaims ownership of the whole working tree after the
Crowdin action and before any git manipulation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179E3AfsnB1NP87YmtAMZNX
2026-07-17 23:39:44 +00:00
Claude 7f99cbc3d6 fix(ci): include commons translations in the Crowdin PR
The Crowdin action now downloads the commons module's Compose-resource
translations too, but the create-pull-request step's add-paths still only
covered amethyst/ and docs/. New untracked commons files created by the
Crowdin action (run as root in its container) were therefore left out of
the commit and shelved by git stash --include-untracked, which failed with
'Permission denied' when trying to remove the root-owned files.

Add commons/src/commonMain/composeResources/**/strings.xml to add-paths so
those translations are committed into the PR instead of being stashed.
2026-07-17 23:14:36 +00:00
Claude 7e78204336 feat: ship a Flatpak bundle of the desktop app on release CI
The linux-portable release leg now wraps the createReleaseDistributable
tree it already builds into a single-file Flatpak bundle and attaches it
to the GH Release as amethyst-desktop-<version>-linux-x64.flatpak.

Packaging fixes to the existing (previously unwired) Flathub manifest:
- add the missing 512x512 icon (copy of desktopApp icon.png) and install
  it under hicolor/512x512 instead of the never-present 256px path
- drop the openjdk module + sdk-extension: the jpackage tree bundles its
  own trimmed JRE, so /app/jre was pure bloat
- grant --socket=x11 instead of wayland/fallback-x11: Compose Desktop
  renders via AWT/skiko (X11-only on Linux, XWayland on Wayland), so
  fallback-x11 left the app socketless on Wayland sessions

CI wiring:
- install flatpak tooling + the freedesktop runtime/sdk (version greped
  from the manifest so the pin can't drift), retried like other fetches
- inject the AppStream <release> entry for the tagged version at build
  time (the checked-in metainfo deliberately carries none)
- flatpak-builder with --disable-rofiles-fuse (GH runners) and a
  --state-dir under desktopApp/build so the repo tree stays clean
- collect via the existing asset-name.sh contract (new flatpak ext)

Verified end-to-end locally: built the bundle from the manifest with a
stubbed jpackage tree, installed it, and ran the exported command inside
the sandbox (freedesktop 24.08, args forwarded through the wrapper).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYfNxhPZC3WRrn82Dm2Cb4
2026-07-14 14:06:37 +00:00
Claude 4a53dccaf9 ci: auto-sync amy Homebrew formula on release; fix bump failure-reporter perms
Add bump-homebrew-formula.yml: on a stable release it downloads the published
amy-<version>-jvm.tar.gz bundle, computes its sha256, and opens a PR syncing
cli/packaging/homebrew/amy.rb's url + sha256 — automating the manual step the
formula header calls out and keeping the reference formula ready for the
homebrew-core submission. The homebrew-core auto-bump is deferred (documented
TODO) because brew bump-formula-pr can only bump a formula already in the tap,
and amy has not been submitted to homebrew-core yet.

Also fix the "Report failure" step in bump-homebrew.yml and bump-winget.yml:
both declared `permissions: contents: read`, but github.rest.issues.create
needs issues:write, so the failure reporter itself 403'd and never filed an
alert. Add issues:write to both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGdEUziwC3Uc3XhB7DFQYt
2026-07-07 23:18:25 +00:00
dependabot[bot]andGitHub ebc4bee4d9 chore(actions): bump the actions group with 4 updates
Bumps the actions group with 4 updates: [actions/checkout](https://github.com/actions/checkout), [softprops/action-gh-release](https://github.com/softprops/action-gh-release), [actions/cache](https://github.com/actions/cache) and [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

Updates `softprops/action-gh-release` from 3.0.0 to 3.0.1
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/b4309332981a82ec1c5618f44dd2e27cc8bfbfda...718ea10b132b3b2eba29c1007bb80653f286566b)

Updates `actions/cache` from 5 to 6
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

Updates `peter-evans/create-pull-request` from 7 to 8
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: peter-evans/create-pull-request
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-01 00:45:34 +00:00
Claude 284b7b143e ci: build signed Accrescent APK set (.apks) on release
Accrescent only accepts a signed APK set of split APKs generated by
bundletool from an AAB — not the AAB itself and not a monolithic APK.
After signing the F-Droid AAB, run bundletool build-apks (--mode=default)
to emit dist/amethyst-fdroid-<tag>.apks, signed with the same release
keystore secret the other signing steps use. It is attached to the GitHub
Release via the existing dist/* glob.

Upload to Accrescent stays manual (drag the .apks into the developer
console): Accrescent has no publish API or CI CLI yet — both are on their
roadmap but unreleased. A build-time guard warns if the APK set exceeds
Accrescent's 128 MiB automated-check limit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rE4k6sUN39emsofSv1Y1M
2026-06-26 21:27:16 +00:00
Claude 48a88f136f ci: combine Crowdin translation and translator-seed PRs into one
Previously the Crowdin workflow ran two independent jobs that each opened
their own pull request: the crowdin/github-action sync PR ("New Crowdin
Translations") and the peter-evans seed PR ("Seed translator npub
placeholders").

Collapse them into a single job: the Crowdin action now only downloads
translations into the working tree (push_translations/create_pull_request
disabled), the seed script edits translators.json in the same checkout, and
one create-pull-request step opens a single combined PR on
l10n_crowdin_translations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kxm4Pq3rm4doVLqJ2LaXtr
2026-06-20 14:10:35 +00:00
Vitor PamplonaandGitHub c37562dc51 Merge pull request #3280 from vitorpamplona/claude/epic-hamilton-m4xors
Add translator credit automation via Crowdin sync
2026-06-19 13:21:34 -04:00
Vitor PamplonaandClaude Opus 4.8 61defaa2e9 fix(ci): resolve the exact Developer ID common name for Compose signing
v1.12.4 got the keychain right ("...in keychain [/Users/.../amethyst-signing
.keychain-db]") but createReleaseDistributable still failed with "Could not
find certificate". Different layer of the same problem:

Compose's MacSigner maps its identity to a cert by running `security
find-certificate -c <identity>`, prepending "Developer ID Application: " when
the identity doesn't already start with it. `codesign --sign` (used by the
signMacJarNatives task, which succeeds in the same job) instead matches a
SHA-1 hash OR any common-name substring. So a MAC_SIGN_IDENTITY secret that is
a fingerprint or a team-ID/partial name signs fine with codesign but, once
prefixed by Compose, is not a substring of the cert's common name -> zero
matches -> failure.

Reproduced locally against the real Developer ID cert:
  find-certificate -c "Developer ID Application: <TEAMID>"      -> 0 matches
  find-certificate -c "Developer ID Application: <full CN>"     -> 1 match

Fix: import-macos-cert now resolves the certificate's full "Developer ID
Application: NAME (TEAMID)" common name from the keychain (via find-identity)
and exposes it as an `identity` output. The desktop build feeds that to
Compose's signing.identity, falling back to the raw secret if resolution
fails. Independent of whatever form the secret takes. The amy CLI leg keeps
using bare codesign with the secret directly and is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:54:56 -04:00
Vitor PamplonaandClaude Opus 4.8 e82237840a fix(ci): point Compose macOS signing at the CI keychain explicitly
The desktop DMG release leg (build-desktop macos, packageReleaseDmg) has never
produced a signed artifact: createReleaseDistributable fails with "Could not
find certificate for '***' in keychain []". This is independent of the v1.12.3
notarization fix, which addressed the separate amy CLI leg.

Root cause: Compose's MacSignerImpl maps the signing identity to a certificate
by running `security find-certificate -a -c <identity>` with no keychain
argument. On the GitHub macOS runners that lookup does not resolve the cert that
import-macos-cert imported into a throwaway keychain and added only to the user
search list — even though bare `codesign --sign` (e.g. the signMacJarNatives
task, which succeeds in the same job) finds it fine. The "keychain []" in the
error is just the null settings.keychain being echoed.

Fix: export the throwaway keychain path from the import-macos-cert action and
feed it to Compose's `signing.keychain` via AMETHYST_MAC_SIGN_KEYCHAIN, so the
certificate lookup searches that keychain directly. Also set it as the default
keychain for good measure. No-op on local/PR builds (env unset -> Compose keeps
its previous default-search-list behavior).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:32:46 -04:00
Claude 6b30486667 feat: track a rolling "since last tag" translator list
Reworks docs/changelog/translators.json into two lists maintained by
scripts/translators.sh:

- mappings: a forever-growing Crowdin-username/id -> npub registry. --seed
  appends new contributors with a blank npub and never deletes or overwrites
  existing entries.
- sinceLastTag: a rolling snapshot of who has translated since the last v* tag,
  fully refreshed on every --seed run.

The contribution window now defaults to the most recent v* tag instead of a
fixed two months (falling back to two months ago when no tag is reachable). The
CI seed job fetches tags (fetch-depth: 0) so it can resolve that window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWQWVdWLAwBUBX2gJ55y6b
2026-06-19 15:52:27 +00:00
Claude 6c9ca11602 ci: seed translators.json from Crowdin on every sync
Adds a seed-translators job to the Crowdin workflow that runs
scripts/translators.sh --seed (past two months) and opens/updates a single
PR via peter-evans/create-pull-request whenever a new contributor appears, so
docs/changelog/translators.json stays current without manual upkeep. The
action is MIT and CI-only (not linked into any shipped artifact), and no-ops
when there is no diff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWQWVdWLAwBUBX2gJ55y6b
2026-06-19 15:46:33 +00:00
Vitor PamplonaandClaude Opus 4.8 ee58355e6e fix(ci): sign macOS Mach-O natives embedded in bundled jars before notarization
The v1.12.2 release was the first to actually codesign + notarize the macOS
artifacts (signing was wired after v1.12.1, which shipped unsigned). Both macOS
legs failed with "Notarization status: Invalid": Apple's notary service recurses
into the bundled jars and rejects the unsigned Mach-O natives inside them
(secp256k1, sqlite-bundled, jna, skiko, jkeychain, kdroidFilter mediaplayer) —
codesign on the .app and the CLI's loose-file loop never descend into jars.

Notary log confirmed the offending entries, e.g.
  sqlite-bundled-jvm.jar/natives/osx_arm64/libsqliteJni.dylib
    -> "not signed with a valid Developer ID certificate" / "no secure timestamp"

Add scripts/sign-macos-jar-natives.sh: a shared helper that signs every macOS
Mach-O inside the bundled jars with hardened runtime + a secure timestamp,
skipping Linux ELF via a `file` Mach-O gate and no-opping when no identity is
set (local/PR builds unchanged). Wire it into:
  - the CLI notarize step (runs before the loose-file signing loop)
  - a desktop signMacJarNatives Gradle task that signs the proguarded jars
    between proguardReleaseJars and createReleaseDistributable, so Compose
    seals already-signed code.

Validated locally on arm64: clean signed createReleaseDistributable produces an
.app that passes `codesign --verify --deep --strict`, with every nested native
carrying Developer ID + hardened runtime + secure timestamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:42:06 -04:00
Claude cb5498ae4c perf(cli): drop Compose UI render stack from the amy runtime image
amy is headless and compiles against zero Compose UI (the Compose deps are
`implementation` in :commons, so they never hit the CLI compile classpath),
but they still rode the runtime classpath into the shipped image — ~29 MB of
Compose desktop render stack, including skiko's native .dylibs that enlarged
the macOS notarization surface.

Exclude skiko + the org.jetbrains.compose UI groups (ui/foundation/material/
material3/animation) from :cli runtimeClasspath. Keep androidx.compose.runtime
(snapshot state + @Stable/@Immutable) — that IS CLI-safe and used by commons
models/state. This avoids the commons → commons/commons-ui module split: the
single-module, feature-cohesive design (commons/ARCHITECTURE.md §1/§3) is
preserved; only the runtime artifact is trimmed.

Result: amy image lib 77 MB -> 48 MB (-38%), and all 4 Compose/skiko notary
dylibs gone (only secp256k1/jna/sqlite natives remain — the ones actually
loaded). A create-release.yml assertion fails the build if the UI stack ever
leaks back.

Verified with the SDK hidden + an offline amy command battery (init/whoami/
--json/relay/marmot/login, plus a real 6-relay key-package round-trip): zero
NoClassDefFoundError/linkage errors; init derives a secp256k1 key cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 01:39:08 +00:00
Claude 3c422b1824 ci(cli): surface notary log on non-Accepted; record signing validation
macOS validation (Developer ID D77MCV9NZ7) confirmed the hardened-runtime
entitlements are correct and load-bearing: amy init derives a secp256k1 key
cleanly, and dropping disable-library-validation reproduces the runtime
dlopen Team-ID failure. The one unverified gap is whether Apple's notary
service accepts the unsigned Mach-O dylibs embedded inside lib/*.jar
(secp256k1/jna/sqlite/skiko), which it inspects recursively.

- create-release.yml: the notarize step now submits with --output-format json,
  and on any non-Accepted status dumps `notarytool log` (per-file issues) and
  fails — so the first real run names the offending files instead of failing
  opaquely. No speculative in-jar signing yet; gather the log first.
- BUILDING.md: record the validation result, the embedded-jar-native risk, the
  one-run way to decide it (workflow_dispatch dry_run with MAC_* secrets), and
  the staged fixes (sign-in-jar and/or strip the skiko/Compose leak). Note the
  desktop app shares the same jars and needs its own dry-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 00:49:26 +00:00
Claude d10e6c4d81 feat(cli): codesign + notarize the macOS amy tarball
Sign the macOS jlink image (amy-<version>-macos-arm64.tar.gz) so users who
download it directly clear Gatekeeper. Reuses the same Developer ID cert and
the six MAC_* secrets as the desktop DMG; no-op when they're absent.

- .github/actions/import-macos-cert: factor the throwaway-keychain cert import
  into a composite action; the desktop leg now uses it too (was inline).
- create-release.yml (build-cli macOS leg): import the cert, then codesign
  every Mach-O binary in the bundled JRE (executables get hardened-runtime
  entitlements, dylibs don't) and notarize via notarytool --wait. Runs before
  the collect step so the tarred image is signed. Job timeout 30->45 min for
  notarization headroom.
- cli/packaging/macos/amy.entitlements: hardened-runtime entitlements; the
  disable-library-validation key lets the JVM load the secp256k1 native dylib
  it extracts from a jar at runtime (would otherwise crash under notarization).
- BUILDING.md: document the tarball signing, the no-stapling/online-check
  caveat, and that the Homebrew-core jvm bundle is intentionally left unsigned.

Untested end-to-end (no macOS runner / Apple creds here) — validate with a
workflow_dispatch dry-run once the secrets are provisioned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 00:21:58 +00:00
Claude f24fc10212 feat(cli): publish no-JRE jar bundle + Homebrew-core formula for amy
Enable distributing the `amy` CLI via Homebrew-core (mainline formulae).
Homebrew-core builds in a network-sandboxed env, so a from-source Gradle
build can't resolve Maven deps there; the accepted pattern for JVM tools is a
pre-built no-JRE jar bundle + `depends_on "openjdk"`. installDist already
produces exactly that (bin/amy + lib/*.jar, no bundled runtime).

- create-release.yml: publish `amy-<version>-jvm.tar.gz` (the installDist tree)
  as a release asset on the linux leg. Pure JVM bytecode, so one
  platform-independent artifact serves every OS.
- packaging/homebrew/amy.rb: reference formula (depends_on openjdk, livecheck
  for BrewTestBot auto-bumps, `amy --help` smoke test). Not consumed by any
  build here — it's the artifact to submit to Homebrew/homebrew-core.
- BUILDING.md: homebrew-core submission runbook; note that the desktop app is
  already on mainline Homebrew (homebrew/cask); document name-collision and
  pre-built-jar review caveats.
- asset-name.sh: document the jvm bundle naming exception.

Verified locally: :cli:installDist builds with only a JDK (no Android SDK),
and the extracted bundle runs via JAVA_HOME (`amy --help` exits 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 00:14:09 +00:00
Claude c81dbb0127 feat(desktop): wire macOS Developer ID signing + notarization
Add gated code-signing + notarization for the macOS desktop DMG so it can
clear Gatekeeper and stay in Homebrew's main cask (unsigned casks are
rejected after 2026-09-01).

- desktopApp/build.gradle.kts: macOS signing{}/notarization{} blocks, gated
  on the AMETHYST_MAC_SIGN_IDENTITY env var. Absent => unsigned DMG, exactly
  as before, so local dev and PR CI are unaffected.
- create-release.yml: import a Developer ID cert into a throwaway keychain on
  the macOS leg and export the signing/notary env. Soft-gated on the
  MAC_CERTIFICATE_P12 secret — no secret => unsigned build.
- BUILDING.md: document the six MAC_* secrets, how to generate them, and flip
  the unsigned-cask fallback note to reflect the wiring is now in place
  (pending Apple credentials).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-17 23:06:07 +00:00
Vitor Pamplona 0572b0091c v1.12.0 2026-06-16 14:38:13 -04:00
nrobi144 eae1ed88ec fix(desktop,media): address PR #3175 review findings
Correctness fixes in GlobalMediaPlayer.kt
- snapshotFlow { hasMedia } collector for initial seek used `return@collect`
  which only exits the lambda; the collector kept running and each
  subsequent playVideo() call accumulated a live collector that would
  re-fire a stale seekTo() on the wrong media. Replaced with `Flow.first`
  which terminates the collection cleanly.
- playVideo()/playAudio() reset the public MediaPlaybackState to
  volume=100/isMuted=false on a new URL, but the kdroidFilter player
  retains its `volume` across openUri(); muting one track and starting a
  new one left the engine silent while the UI showed unmuted. Reset
  `player.volume = 1f` to match the public state.
- ensureVideoPlayer()/ensureAudioPlayer() called createVideoPlayerState()
  synchronously from the Compose getter; if native init throws (missing
  GStreamer on Linux, broken NativeLibraryLoader extraction) the whole
  window would crash. Wrapped in runCatching and changed
  activeVideoPlayerState to nullable. Consumers in DesktopVideoPlayer
  and GlobalFullscreenOverlay handle the null path by rendering the
  thumbnail / blank backdrop respectively; playVideo()/playAudio()
  surface "Video playback unavailable" through the existing
  errorReason -> PlaybackErrorMessage path.

Crash mitigation (kdroidFilter 0.10.0 UAF in MacVideoPlayerSurface)
- NowPlayingBar previously mounted a SECOND VideoPlayerSurface against
  the same VideoPlayerState while the feed card was already mounting
  one, doubling the draw rate against the shared frame bitmap and
  widening the UAF window in MacVideoPlayerSurface's RasterFromBitmap
  path. Mini-preview now renders the cached thumbnail (or the music
  icon fallback). 0.10.1 contains an upstream fix
  ("recover video playback after composition removal") but is not yet
  on Maven Central — single-surface mounting is the only mitigation
  we can ship today.

VideoThumbnailCache.kt
- Truncated-download cache poisoning: when an origin ignored the
  Range: header and returned HTTP 200 with the full body, we capped
  the copy at MAX_THUMB_BYTES and persisted the truncated file
  forever. Subsequent thumbnail attempts hit the broken cache file
  and re-failed JCodec/ffmpeg every time. Tag download results with
  whether the server actually returned 206; on 200, extract from the
  temp file and delete it (no persistent cache hit).
- Tor bypass: replaced the bare OkHttpClient with
  DesktopHttpClient.currentClient() so thumbnail fetches respect the
  user's Tor preference (fail-closed when Tor is expected but
  bootstrapping).
- ffmpeg version probe leaked the process on hang: now drains stdout
  to DISCARD and calls destroyForcibly() on timeout.
- Frame-extract ffmpeg subprocess could deadlock on a chatty stderr
  pipe: redirectError(DISCARD) so we never wait on stderr; a finally
  block destroys the process if anything leaked through the timeout.

CI workflow cleanup
- Removed vlc-setup download cache + pre-fetch steps from
  build.yml and smoke-test-desktop.yml. They were targeting an
  ir.mahozad.vlc-setup plugin we no longer apply, so they wasted
  ~minutes of CI time per leg and tied the build to videolan.org
  reachability for no reason.
- Trimmed create-release.yml's stale VLC-plugins justification on
  the linuxdeploy-vs-appimagetool comment.

.gitignore + missing per-OS ffmpeg READMEs
- The pre-PR rules blanket-ignored desktopApp/src/jvmMain/appResources/{linux,macos,windows}/
  so the LGPL FFmpeg drop-in slot READMEs created in 704f4f44e never
  reached the commit. Refined the ignore rules to keep stale vlc/ workspace
  trees out of git (still ignored) while explicitly tracking the
  ffmpeg/README.md drop-in slot under each OS. The READMEs document the
  recommended LGPL build source per OS for the bundled-FFmpeg packaging
  path.

Verified on macOS arm64:
  ./gradlew :desktopApp:compileKotlin   BUILD SUCCESSFUL
  ./gradlew :desktopApp:test            BUILD SUCCESSFUL
  ./gradlew :desktopApp:spotlessApply   clean

Refs PR #3175 review by @davotoula.
2026-06-11 17:46:12 +03:00
Claude ed37020fbf ci: run shared commonTest on iOS for :commons
The test-quartz-ios job only compiled :commons production code for iOS
(compileKotlinIos*), never the commonTest source set, so a test using a
JVM-only API compiled fine on JVM/Android and shipped green while silently
breaking compileTestKotlinIosSimulatorArm64.

Mirror the quartz step: run :commons:iosSimulatorArm64Test (compiles +
runs the shared commonTest suite on the simulator) and
:commons:compileTestKotlinIosArm64 (device-only compile drift). This keeps
commonTest KMP-clean going forward.
2026-06-09 20:20:29 +00:00
dependabot[bot]andGitHub 92989566cd chore(actions): bump the actions group with 2 updates
Bumps the actions group with 2 updates: [gradle/actions](https://github.com/gradle/actions) and [actions/cache](https://github.com/actions/cache).


Updates `gradle/actions` from 4 to 6
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v6)

Updates `actions/cache` from 4 to 5
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-01 01:34:23 +00:00
Claude cbd8cd1b87 ci: switch setup-java distribution from zulu to temurin
Zulu's CDN (cdn.azul.com) has been returning HTTP 520 for several
hours, breaking JDK 21 setup across multiple PRs (#3075, #3095) on
every Linux runner. actions/setup-java's internal retry budget isn't
enough to ride through it.

Temurin (Adoptium) publishes its JDKs as GitHub Releases assets, so
the download path uses GitHub's own CDN — completely independent
infrastructure. Java 21 is the same upstream OpenJDK build either way,
so Gradle, Compose Multiplatform, and jpackage behave identically.

Applied to all setup-java steps in build.yml, smoke-test-desktop.yml,
and create-release.yml.
2026-05-28 22:46:29 +00:00
Claude e02972e1be build(commons): enable iosArm64 + iosSimulatorArm64 targets
Phase 2 task 10 of the iOS plan — flip on iOS targets for :commons.
Gradle dep resolution is fully green for iOS; actual Kotlin/Native
compilation runs on the macOS CI job (the dev container in which this
was authored can't extract the K/N LLVM toolchain).

Dep reshuffle to match what's actually KMP-available:

- commonMain: kept project(":quartz"), Compose Multiplatform, coil-compose,
  androidx-collection, kotlinx-collections-immutable, kotlinx-serialization-json,
  compose components-resources, androidx-lifecycle-viewmodel,
  androidx-lifecycle-runtime-compose. These all publish iosArm64 +
  iosSimulatorArm64 variants per `.module` inspection.

- jvmAndroid (NEW location): project(":nestsClient") (JVM+Android-only),
  coil-okhttp (JVM-only), markdown-commonmark / markdown-ui /
  markdown-ui-material3 (the RenderMarkdown.kt consumer is already in
  jvmAndroid), and androidx-lifecycle-viewmodel-compose (AndroidX publishes
  android + jvmStubs + linuxx64Stubs variants — no iOS, so the viewModel()
  Composable helper stays JVM-bound until we either swap to the
  org.jetbrains.androidx.lifecycle variant or accept a platform-specific
  ViewModel access pattern on iOS).

- libs.versions.toml: adds androidx-lifecycle-viewmodel catalog entry.

- New intermediate source set iosMain → both iosArm64Main and
  iosSimulatorArm64Main depend on it (clean place for iOS-only actuals
  when KmpLock, WeakReference, etc. get their iOS implementations).

- .github/workflows/build.yml: test-quartz-ios job now also runs
  :commons:compileKotlinIosArm64 + :commons:compileKotlinIosSimulatorArm64.
2026-05-25 00:13:13 +00:00
Claude b27fc34786 refactor: migrate FeedDefinitionSerializer to kotlinx.serialization
The one Jackson holdout in commons/commonMain. Migrating it unblocks
the iOS purity gate for :commons (Phase 1 of the iOS plan).

- Rewrites FeedDefinitionSerializer with the kotlinx.serialization JSON
  tree API (JsonObject / JsonArray / JsonPrimitive). Wire format is
  byte-identical, so users' existing on-disk custom-feed definitions
  keep deserializing — covered by a new parsesLegacyJacksonOutput test
  that pins a hand-written Jackson-shaped JSON blob.
- Adds :commons:verifyKmpPurity (mirrors the one in :quartz) and wires
  it into the CI lint job alongside :quartz:verifyKmpPurity.
- Pulls in kotlinx-serialization-json as a commonMain dep; the
  serialization plugin was already applied on :commons.
2026-05-24 16:41:00 +00:00
Claude e0c3b18731 ci: add iOS test job for quartz + commonMain purity gate
Phase 1 of the iOS support plan (amethyst/plans/2026-05-24-ios-support.md).
Two independent guards so JVM-only imports can't silently appear in
quartz's iOS-bound source sets:

- :quartz:verifyKmpPurity (Linux, ~1s): scans commonMain + apple/native
  source sets for com.fasterxml.jackson / okhttp3 references and fails
  the build with a clear pointer to the offending file:line. Wired into
  the existing lint job so it runs on every PR.

- test-quartz-ios (macos-latest): runs :quartz:iosSimulatorArm64Test on
  the simulator (NIP-04, NIP-17, NIP-19, NIP-49 vectors + AES-GCM and
  chatroom-key tests already in quartz/src/iosTest) and additionally
  compileTestKotlinIosArm64 to catch device-variant compile drift.
2026-05-24 16:12:34 +00:00
nrobi144andClaude Opus 4.6 487dd3f2ac fix(desktop): disable method/marking/static ProGuard optimization
The smoke test discovered that ProGuard's method/marking/static pass
converts kmp-tor's AsyncFs.of() from instance to static. The JVM
then throws IncompatibleClassChangeError at launch because callers
still use invokevirtual.

Also updates smoke-test-desktop.yml trigger: runs on any PR touching
desktopApp/ (not just build config files).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 09:48:01 +03:00
nrobi144andClaude Opus 4.6 2010e41b25 fix(ci): allow dpkg post-install error, verify binary extracted
dpkg -i exits non-zero when the post-install script fails even with
--force-all. Allow the error with || true, then verify the binary
was actually extracted to /opt before proceeding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 07:59:33 +03:00
nrobi144andClaude Opus 4.6 cca9c2ff3a fix(ci): use dpkg --force-all to skip xdg-desktop-menu error on runner
jpackage's post-install script calls xdg-desktop-menu which fails on
GitHub Actions runners with "No writable system menu directory found".
Menu registration is irrelevant for smoke testing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 07:39:34 +03:00
nrobi144andClaude Opus 4.6 cf6541a1e1 test(desktop): add Compose UI smoke test + release .deb launch CI
Fixes #2819 — v1.08.0 .deb crashed on Ubuntu with ManagementFactory
error because the build had no jlink modules() declaration. Current
main already has the fix; this PR adds CI to prevent regressions.

- Add compose.desktop.uiTestJUnit4 dependency
- DesktopLaunchSmokeTest: renders LoginScreen, asserts title text
- build.yml: xvfb for Linux leg so UI test runs on every PR
- smoke-test-desktop.yml: builds release .deb, installs it, launches
  under xvfb, verifies process stays alive 10s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 07:09:16 +03:00
Vitor PamplonaandGitHub f901db699d Merge pull request #2940 from vitorpamplona/claude/fix-ci-build-desktop-2qHSV
Replace linuxdeploy with appimagetool for AppImage packaging
2026-05-16 15:08:44 -04:00
Claude 7ed93bcbc2 fix(desktop): package AppImage with appimagetool, not linuxdeploy
linuxdeploy auto-walks every binary in the staged AppDir with ldd to
bundle their shared-library deps. That fights jpackage's self-contained
output on two axes and aborts the createReleaseAppImage task before any
AppImage is produced:

  - The bundled JRE puts libjvm.so at usr/lib/runtime/lib/server/, while
    its sibling libs (libmanagement.so, libawt_xawt.so, libfontmanager.so
    and others in usr/lib/runtime/lib/) have RPATH=$ORIGIN. ldd cannot
    resolve libjvm.so from those libs without help, so linuxdeploy errors:
        ERROR: Could not find dependency: libjvm.so
        ERROR: Failed to deploy dependencies for existing files

  - The bundled libvlc plugins under usr/lib/app/resources/vlc/ are
    UPX-compressed by the vlc-setup plugin. patchelf cannot read their
    section headers, so linuxdeploy aborts:
        ERROR: Call to patchelf failed:
        patchelf: no section headers. The input file is probably a
        statically linked, self-decompressing binary

  - Even after working around libjvm.so via LD_LIBRARY_PATH, several VLC
    libs have RUNPATH pointing at the wrong directory, so ldd fails on
    libva.so.2 / libvlccore.so.9 next.

This is why v1.09.2 shipped .deb + .rpm from the linux leg but no
.AppImage and no linux .tar.gz from the linux-portable leg — the gradle
step failed on linuxdeploy, so the subsequent "Build portable archives"
step never ran.

Swap to appimagetool, which only embeds the AppDir as-is into a
SquashFS-backed, runtime-prepended AppImage and never touches the
contents. jpackage already bundles a complete, self-contained app —
we don't need linuxdeploy's dep-discovery. AppRun continues to handle
LD_LIBRARY_PATH at launch.

Verified locally on ubuntu-24.04: a fresh
:desktopApp:createReleaseAppImage now produces a valid 254 MB
Amethyst-1.09.2-x86_64.AppImage in ~30 s on top of an up-to-date
createReleaseDistributable.

Side changes:
- Workflow installs desktop-file-utils (appimagetool 1.9.0 calls
  desktop-file-validate on the .desktop entry).
- BUILDING.md updated with the new local-dev fetch command.
- .gitignore replaces the linuxdeploy paths with appimagetool's.
2026-05-16 19:04:07 +00:00
Claude 25a49d1050 ci: remove macos-13 x64 build legs
GitHub's macos-13 runner image is being retired. Drop the x64 macOS
legs from both the desktop and CLI release matrices; macos-14 arm64
continues to ship the macOS builds.
2026-05-16 17:18:51 +00:00
Claude 2e8bf0d45d build: remove unused :ammolite module
The :ammolite module contained no production Kotlin/Java sources (just
a manifest, build.gradle, and proguard stubs) and no module in the
codebase imports com.vitorpamplona.ammolite.*.

Removes:
- ammolite/ directory (5 files)
- :ammolite project include in settings.gradle
- implementation project(':ammolite') from :amethyst
- androidTestImplementation project(':ammolite') from :benchmark
- :ammolite:testDebugUnitTest from CI workflow and pre-push hook
- -keep class com.vitorpamplona.ammolite.** rules from
  :amethyst, :commons, and :desktopApp proguard files
- Stale references in CONTRIBUTING.md, CLAUDE.md, and the
  gradle-expert skill dependency-graph doc

Small build-graph win: one fewer module to configure, compile, lint,
and spotless-check on every build, and one fewer unit-test target in
both CI and the local pre-push hook.
2026-05-16 14:40:03 +00:00
Claude 3ff721b31a refactor: move packaging/appimage into desktopApp module
The AppImage build inputs (AppRun, .desktop entry, icon, and the
CI-fetched linuxdeploy binary) are consumed only by desktopApp's
createReleaseAppImage task. Co-locating them under
desktopApp/packaging/appimage/ removes the `../` path escape from the
build script and keeps all desktop packaging assets inside the module.

https://claude.ai/code/session_0137ULcfJkASmfmffFBdW8ac
2026-05-14 18:21:28 +00:00
davotoulaandClaude Opus 4.7 3ba2049566 docs(contributing): add CONTRIBUTING-WITH-AI.md companion guide
Companion to CONTRIBUTING.md targeting external PRs whose diff was
substantially authored by an AI coding assistant. Adds five gates the
existing doc does not cover:

  - Research before code (issue still valid, decentralisation fit)
  - Build and install both flavours (Play + F-Droid)
  - Performance and resource hygiene (main-thread, coroutines,
    LocalCache reuse, relay subscriptions, KMP source sets, lambda Log)
  - Regression test plan alongside feature test plan
  - Don't-touch list (signer/KeyStore, release pipeline, NIP direction)

Filename deliberately avoids AGENTS.md to keep internal dev-team
agents unaffected by auto-load conventions. CONTRIBUTING.md and the
PR template each gain a one-line pointer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 20:24:47 +02:00
Claude 8056eb899f docs(contributing): scope proof-of-testing to non-regular contributors 2026-05-11 14:23:37 +00:00
Claude 8ea7d1ee81 docs(pr-template): drop modules-touched and risk/rollback sections 2026-05-11 14:11:59 +00:00
Claude cfa6899c65 docs: cross-link CONTRIBUTING.md from README and add PR template 2026-05-11 13:42:22 +00:00
Claude d2294247a7 fix(desktop): pin vlcVersion to 3.0.20 so Linux vlcDownload finds an artifact
The Linux build path of the vlc-setup plugin pulls vlc-plugins-linux from
Maven Central (ir.mahozad:vlc-plugins-linux), which only ships 3.0.20 and
3.0.20-2 — there is no 3.0.21 artifact yet. With vlcVersion set to 3.0.21,
both the CI pre-fetch step and the in-Gradle vlcDownload task hit a 404.

Match VLC_VERSION in build.yml and document the lag so future bumps wait
for the Maven artifact to catch up.

https://claude.ai/code/session_01GrZLMi3sdp6frwREmQ9cUi
2026-05-06 23:06:52 +00:00
Claude 00194d44a4 ci(desktop): pre-fetch VLC + UPX with curl --retry to dodge videolan flakes
Replace the nick-fields/retry wrapper around the Gradle invocation with a
curl-based pre-fetch step. The vlc-setup plugin writes its downloads to
${gradleUserHomeDir}/vlcSetup/ with overwrite(false), so dropping the
archives there ahead of time turns vlcDownload / upxDownload into no-ops.

curl --retry-all-errors --retry-max-time 900 tolerates a sustained
get.videolan.org outage far better than the plugin's de.undercouch
Download (retries(4) + 5min readTimeout, which still hit
SocketTimeoutException on Windows).

The actions/cache step at ~/.gradle/vlcSetup still captures the result
for subsequent runs; the prefetch only does real work on cache miss.
The in-build retries(4) in desktopApp/build.gradle.kts stays as a third
line of defense.

URLs and on-disk paths are read straight from the plugin source
(ir.mahozad.vlc-setup 0.1.0); macOS skips UPX because UPX cannot compress
.dylib files.
2026-05-06 19:16:48 +00:00
Claude cb428be28e ci(desktop): wrap desktop build in retry to survive vlcDownload flakes
The Windows leg of the Test/Build workflow has been intermittently failing
with SocketTimeoutException inside :desktopApp:vlcDownload, even after the
in-build retry budget (retries(4) + 5min readTimeout in
desktopApp/build.gradle.kts) and the actions/cache of ~/.gradle/vlcSetup.
When get.videolan.org is unreachable for long enough to exhaust the inner
retries, the whole job dies on a cache miss.

Wrap the Test+Build step in nick-fields/retry@v4 (max_attempts: 2,
timeout_minutes: 45) so one outer retry can ride out a sustained
videolan.org outage. This mirrors the proven pattern already used in
create-release.yml for the release artifact build.
2026-05-06 18:57:01 +00:00