Introduce a SERVER platform (tag token `server`) and a third release
scope alongside All / Targeted. A server-only release produces a
`vX.Y.Z+server` tag, which matches none of the per-store publish jobs in
publish-release.yml, so no client app store upload runs while the server
distribution still builds and deploys out of band.
isPlatformReleaseTag now recognizes `+server`, so backout/revert clean it
up like any other release tag.
Introduces review-logic.js: DOM-free suggestion logic (segment
splitting, overlap guards, smart-spacing, applying accepted edits) plus
deterministic per-suggestion pen-ink strike styling, loadable both as a
browser script and a Node module. Unit-tested with Node's built-in
runner via a new :server:jsTest Gradle task, wired into CI as an
explicit gate with Node set up in the build job.
Restores the "store projects in public storage" feature, gated to F-Droid builds (the required MANAGE_EXTERNAL_STORAGE permission is disallowed on Google Play).
- Expose the build channel at runtime via BuildConfig.FDROID in the common module.
- Declare the storage permissions only in src/fdroid/AndroidManifest.xml, swapped in for F-Droid builds.
- Restore the storage-location toggle + file-access UI, gated on BuildConfig.FDROID; reconcile the toggle with the real location on open.
- Build the GitHub release APK as the F-Droid flavor.
- Extract the directory move into a tested FileSystem.moveDirectory() helper (fixes the same-path data-loss crash; runs off the UI thread).
- Read the fdroid flag consistently across settings.gradle.kts and module scripts.
- Document the F-Droid build flag in DEVELOPMENT.md.
ProjectLifecycleTest launches the real ProjectSelectActivity, creates a project,
waits for it to appear in the list, and confirms opening it launches
ProjectRootActivity (via ActivityMonitor). Cleans up in @After.
- Wire jetbrains-compose ui-test-junit4 into the androidTest source set.
- Tag the create-project affordance (CreateProjectButtonTestTag); "Create
Project" otherwise appears as three separate on-screen texts.
- Run the instrumented suite on an emulator in CI (android-emulator-runner) with
AVD snapshot caching.
- Convert HashTest from JUnit Jupiter to JUnit4 so the AndroidJUnit4 runner can
execute it on-device (it had "no runnable methods" otherwise); use assertEquals
since assert() is a no-op when assertions are disabled on a device. It now
verifies EntityHasher's golden vector on Android ART.
- Fix a scope-close crash: getSceneBufferDirectory used a non-recursive
createDirectory, so closing a project whose scenes/ dir is absent threw on the
teardown path and crashed the process. Use createDirectories (matching its
siblings) and order the test teardown so it doesn't delete the project mid-close.
The dry run served its purpose (verified the pipeline and surfaced the
two-cert signing bug, now fixed in publish-mac-app-store.yml). Drop the
dry-run workflow and its desktop_build_only lane; keep only the fix.
* ci: add Mac App Store dry-run lane and workflow
Adds a way to exercise the Mac App Store build/sign/package pipeline
without releasing anything to App Store Connect.
- fastlane: new `mac desktop_build_only` lane builds, signs, and verifies
the .pkg (via build-appstore.sh) but skips the App Store Connect build
number lookup and the upload — no API key needed.
- workflow: new "Dry Run — Mac App Store" (workflow_dispatch) mirrors the
real publish workflow's keychain + provisioning-profile setup, runs the
build-only lane, and saves the .pkg as an artifact instead of uploading.
Verified locally: lane builds + passes codesign/pkgutil checks.
* ci: temporarily trigger mac dry-run on push to its branch
workflow_dispatch requires the workflow to exist on the default branch
before it can be dispatched. Add a branch-scoped push trigger so the dry
run can be exercised now; remove before merging.
* ci: import Mac signing certs from two separate p12 files
macOS `security import` only ingests one identity from a combined
multi-key .p12 (which one wins is non-deterministic), so a single secret
holding both the Application and Installer certs left one of them missing
from the CI keychain — caught by the dry run, which failed with "Mac
Installer Distribution certificate not found".
Import the Application and Installer certs from their own single-identity
.p12 files instead. Adds a second secret, MAC_INSTALLER_CERT_P12_BASE64.
Applied to both the dry-run and the real publish-mac-app-store workflows.
* ci: drop temporary push trigger from mac dry-run workflow
The dry run was triggered via a branch-scoped push trigger because
workflow_dispatch only works once the workflow exists on the default
branch. Now that it's merging to develop, revert to workflow_dispatch
only.
Pass -u gh-releases-zsync to appimagetool so the Linux AppImage carries
update metadata and emits a .zsync file, and upload that file to the
release. Closes#478
buildDebug never touched the androidTest source set, so a stale instrumented
test could - and did - rot unnoticed: HashTest stopped compiling after `tags`
was added to EntityHasher.hashNote.
- Add a :android:assembleDebugAndroidTest step: a fast, emulator-free gate that
compiles + packages the androidTest source set on every PR.
- Fix the stale HashTest to match the current hashNote signature.
- Exclude the duplicate META-INF/LICENSE files the test deps ship so the
androidTest APK packages.
* Harden EntityHasher field-coverage test against nested and new-type drift
The descriptor-driven sensitivity test only inspected top-level fields, so a
new field on a nested DTO (ProjectTheme, WordCountGoal, Image) - or a whole new
synced type - could be added without the hasher, and no test failed.
- Recurse into owned nested @Serializable DTOs so e.g. theme.tertiary is a
tracked field path, not an invisible sub-field of `theme`.
- Cover ProjectData / ProjectDataHasher, which had no structural guard at all.
- Assert every ApiProjectEntity.Type has a sensitivity spec, so a new entity
subtype can't ship without one (reflection-free; server has no kotlin-reflect).
* Run server unit tests explicitly in CI
The server unit tests (including EntityHashSensitivityTest) previously ran only
as a side-effect of koverXmlReport gathering coverage - a kover config change
would silently stop running them. Add an explicit :server:test gate.
We've run into many problems where everything publishes fine, except 1 store. This provides a way to unblock that one store without pushing to the rest.
- Grant contents: write to the publish-fdroid-tag caller job so the
reusable workflow's git push tag actually has the token scope it
needs (reusable workflows can't elevate beyond the caller).
- Add a track input to publish-google-play (internal/alpha/beta/
production), default internal for manual dispatch so a stray
workflow_dispatch click can't ship develop straight to Production.
publish-release.yml explicitly passes track: production.
- Pass release_tag through env vars in publish-snap and
publish-fdroid-tag instead of interpolating into shell, closing
the workflow_dispatch command-injection vector.
- Validate fdroid release_tag matches semver before letting it near
git tag / git push.
Lets us iterate on the Partner Center submission logic via
workflow_dispatch (with a dry_run mode that skips the final commit
and cleans up the draft) instead of cutting a release every time.
Release pipeline keeps the publish-google-play gate by calling the
reusable workflow with secrets: inherit.
Mirror the Mac setup with an :ios platform block exposing ios_testflight
and ios_release lanes, plus a publish-ios-app-store job in the release
workflow. Both lanes reuse the existing App Store Connect API key (the
.p8 isn't platform-specific). The CI job installs the provisioning
profile to both Xcode 16's path and the legacy Fastlane location for
compatibility.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
POST /submissions clones the last published submission body, so the
applicationPackages array still pointed at Hammer-2.1.1.msix. We never
updated it, so every commit since 2.1.1 succeeded against an unchanged
package list and the Store stayed frozen. Also: fileUploadUrl wants a
ZIP, not a raw MSIX.
- Mark cloned packages PendingDelete, append a PendingUpload entry for
the new MSIX, PUT the body back before uploading.
- Upload a ZIP whose entry name matches the new fileName.
- Clear any stale pending submission first so retries can run.
- Poll /status after commit and throw on CommitFailed so failures stop
hiding in the run log.
Wire up two Mac lanes (desktop_testflight, desktop_release) alongside the
existing Android setup, and a publish-mac-app-store job in the release
workflow so each new release uploads a sandboxed .pkg to TestFlight on a
macos-latest runner. The lane respects BUILD_NUMBER from env (CI sets it
to github.run_number) and falls back to TestFlight + 1 locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverts the Nucleus packaging port and its follow-ups:
- 93c7973c Trying nucleus for packaging Snap stays bespoke
- 7c14bcd5 Broaden artifact staging to handle Nucleus sandboxed pipeline paths
- d24f6183 Add homepage and debMaintainer for electron-builder
- 8831e9c5 Update release workflows for Nucleus packaging
- 4af31163 Port :desktop packaging to Nucleus framework
Compose Desktop is back as the packaging backend (jpackage targets,
custom packageMsix task, bespoke buildDistSnap/AppImage/Flatpak via
registerLinuxDistributionTasks). The Nucleus runtime libraries —
nucleus.darkmode-detector and nucleus.decorated-window-jbr — are
kept for window decoration and dark-mode detection in code, along
with the JBR jvmToolchain vendor pin and the libs.versions.toml
'nucleus' version entry that they depend on.
The Nucleus-pipeline state is preserved on branch
'nucleus-build-pipeline' for future reference.
Spins up a real Jetty server and a real headless client in the same JVM,
runs the actual sync protocol over HTTP, and asserts against the shared
FakeFileSystem. Covers seven scenarios: smoke (handshake), client upload,
server download, independent edits, conflict resolved to server, conflict
resolved to client, and client delete.
- New :integrationTests Gradle module with the RoundTripTestBase harness,
HeadlessClient driver, and seven scenario tests.
- :server enables java-test-fixtures so EndToEndTest, E2eTestData, and
SqliteTestDatabase are shared with the new module instead of duplicated.
- CI: build.yml runs the suite on every PR/develop push; prepare-release.yml
adds an integration-tests job that gates all seven package jobs, so a
broken sync protocol blocks the release before any artifact is built.
Nucleus splits store formats (AppX, Flatpak, PKG) into a separate
sandboxed pipeline whose output path uses a `-sandboxed` suffix —
exact location not pinned down in docs. Switch every staging step
to a recursive find under `desktop/build/compose/binaries/` by file
extension instead of expecting a fixed `<format>/` subdir. Each
step now also fails loudly with a clear message if the expected
artifact isn't found.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nucleus packages via electron-builder, so every desktop-building job
now installs Node.js. Artifact paths move from
build/installers/main-release/<format>/ to
build/compose/binaries/main-release/<format>/.
prepare-release.yml:
- Consolidates the separate snap and flatpak jobs into the linux job,
since Nucleus produces all five Linux formats (.deb/.rpm/.AppImage/
.snap/.flatpak) from one set of tasks.
- Windows job now also builds Portable and AppX targets.
- Replaces per-artifact rename steps with a single staging step using
find/Get-ChildItem with wildcards — electron-builder's naming
pattern differs from jpackage and is more variable.
publish-release.yml:
- Microsoft Store job now downloads hammer.appx from the GitHub release
(mirroring the snap-publish pattern) instead of running the removed
packageMsix gradle task. Partner Center submission Powershell is
unchanged otherwise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>