Commit graph

85 commits

Author SHA1 Message Date
Adam Brown
1c22fdb926 Prepared for release: v3.11.0
Some checks are pending
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
Build CI / iOS UI tests (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
2026-09-24 23:47:29 -07:00
Wavesonics
61b3b21ab9
Prepared for release: v3.10.1
Some checks are pending
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
Build CI / iOS UI tests (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
2026-09-23 07:53:53 -07:00
Adam Brown
6394005f60 Prepared for release: v3.10.0
Some checks are pending
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
Build CI / iOS UI tests (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
2026-09-20 10:16:01 -07:00
Adam Brown
1bc1e37460
Fix the Microsoft Store build, and stamp builds with their distribution channel (#938)
* Switch the desktop Ktor engine to OkHttp

`java.net.http.HttpClient`'s constructor opens an NIO `Selector`, whose
Windows wakeup pipe is an AF_UNIX socket. The MSIX sandbox rejects the
connect with EINVAL, so every Microsoft Store build hard-crashes on the
first network call, before any request is sent (JDK-8312215, open since
Java 17 with no fix in sight). OkHttp uses blocking socket IO and is
unaffected.

Ships unconditionally rather than gated on the distribution channel:
OkHttp works everywhere, and a second engine would be a second code path
to test forever. Android already uses it.

Includes the unchecked-IO mapping from `fix/unchecked-io-network-errors`,
so a client that cannot be built at all surfaces as a network error
rather than taking down the app.

* Add an Export Logs button to the desktop About screen

Opening the log directory hands the shell a path that does not exist under
an MSIX container: the app's writes to %LOCALAPPDATA% are redirected into
the package's LocalCache, but reads fall through, so the app sees the
directory and Explorer does not. Snap confinement will do the same on
Linux. Exporting a zip to a location the user picks works on every
distribution vehicle, which makes it the one support instruction that
never needs a per-channel caveat.

Also walk up to the nearest existing ancestor before handing a directory
to the shell, so the open button degrades instead of erroring.

* Bake the distribution channel into the build

Per-vehicle rules (self-update, store payment policy, "get the app" links,
sandbox-aware paths) were tracked by hand. `-Pchannel=<token>` now resolves
to a `DistributionChannel` constant, defaulting to DEV, and an unknown
token fails the build rather than quietly shipping a store binary as DEV.
Every release pipeline passes its own channel; the existing F-Droid build
flags map to FDROID without their call sites changing.

Flat enum rather than capability flags: every channel-conditional branch is
a greppable `when` on the type. Flags can emerge later from actual
duplication.

The startup banner and all three crash dumps now carry the channel. That
line pays for itself immediately: this investigation started from a crash
log that did not say which build produced it.

* Un-redirect container paths before handing them to the shell

MSIX filesystem redirection is asymmetric: the app's writes under
%LOCALAPPDATA% land inside the package container, but reads fall through,
so File.exists() is true from inside the container and the app is
satisfied. Explorer runs outside it and correctly reports nothing at the
literal path, which is the "location is not available" dialog the
open-logs button produced on the Store build.

Rewrite the path into the container for the Microsoft Store channel, both
where it is shown and where it is handed to the shell. The package family
name is hardcoded: Windows derives its hash suffix from the publisher ID,
so it is not in AppxManifest.xml and cannot be computed from it, and it
only changes if the Store identity does.

`Windows.Storage.ApplicationData.Current.LocalCacheFolder` is the native
answer but means a WinRT dependency on a KMP desktop target for one path
lookup. Worth revisiting only if more packaged-app APIs are needed.

* Cover the About log section with a render test, document the channel

* Suppress TooGenericExceptionCaught on the unchecked IO catch
2026-09-14 15:38:15 -07:00
Wavesonics
59d1537950
Prepared for release: v3.9.8
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
2026-09-12 00:54:08 -07:00
Wavesonics
72304dfa87 Prepared for release: v3.9.7 2026-09-04 20:08:54 -07:00
Adam Brown
53ecfb4780
Publish iOS under the shared com.darkrockstudios.apps.hammer app ID (#935)
iOS and macOS were shipping as two separate App Store listings under two bundle
IDs. The supported model is Universal Purchase: one app record, one bundle ID,
one platform entry per OS. The binaries stay separate — each platform is still
built and uploaded independently with its own version stream and screenshots.

Points the iOS target and all four fastlane iOS identifiers at
com.darkrockstudios.apps.hammer, adds the for_platform :ios block the Appfile
was missing, and drops the hyphen from the iOS listing name, which only existed
because the unhyphenated name was taken by our own macOS record.

Stops skipping screenshot upload on the iOS lane. That was safe while iOS had
its own record and screenshots carried forward between versions; the iOS
platform newly added to the macOS record starts empty, and App Store Connect
refuses to submit a version with no iPhone screenshots.

Adds a shared concurrency group to the two App Store publish workflows. Both now
target the same App Store Connect record, and a full release tag starts them in
parallel. This is mutual exclusion rather than a needs: edge — neither waits on
the other's result, and a targeted single-platform tag skips the other job
before it reaches the gate.
2026-09-04 19:23:28 -07:00
Wavesonics
e833d57f8d Prepared for release: v3.9.6
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
2026-09-03 20:25:54 -07:00
Wavesonics
9fd796c342
Prepared for release: v3.9.5 2026-09-01 20:51:00 -07:00
Adam Brown
aa51c311dc Prepared for release: v3.9.4
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
2026-08-22 17:48:02 -07:00
Adam Brown
92c0aaa3da Prepared for release: v3.9.3
Some checks are pending
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
Build CI / iOS UI tests (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
2026-08-22 10:00:40 -07:00
Adam Brown
d1ba0c6456 Prepared for release: v3.9.2
Some checks are pending
Build CI / iOS UI tests (push) Waiting to run
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
2026-08-21 23:50:57 -07:00
Adam Brown
d424a346ed Prepared for release: v3.9.1
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
2026-08-18 00:02:23 -07:00
Adam Brown
d39819fbe6 Prepared for release: v3.9.0
Some checks are pending
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
Build CI / iOS UI tests (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
2026-08-16 22:38:10 -07:00
Adam Brown
9cde66bef6
Fix AltGr shortcuts closing the desktop app (#859) (#907)
Windows reports AltGr as Ctrl+Alt, so typing @ on a Turkish-Q or German
QWERTZ layout matched the loose Ctrl+Q quit test and closed the app.

Window shortcut matching moves into WindowShortcuts.kt, built on the
exact-modifier matchesShortcut helper, which also tightens Ctrl+W and
Ctrl+Shift+F.

* Move save-all off Ctrl+Alt+S to Ctrl+Shift+S

Ctrl+Alt+S is itself an AltGr chord on Windows, so it fired and ate the
character on layouts that map AltGr+S, such as Polish (s with acute).
Rebound on desktop, Android, and iOS, and Android's global search now
matches its modifiers exactly like the rest.

No shortcut uses Alt now, which is what keeps AltGr keystrokes reaching
the editor.

* Keep Koin hint classes out of test detection
2026-08-16 00:00:10 -07:00
Adam Brown
e25d80c051 Prepared for release: v3.8.2
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
2026-08-11 01:05:31 -07:00
Adam Brown
51f79a8631 Prepared for release: v3.8.1
Some checks are pending
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
Build CI / iOS UI tests (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
2026-08-08 14:34:38 -07:00
Adam Brown
1c856dc32b
Fix the F3 sync and Ctrl+Alt+S shortcuts, and give them to Android and iOS (#865)
Some checks are pending
Build CI / build (push) Waiting to run
Build CI / static-analysis (push) Waiting to run
Build CI / android-instrumented-tests (push) Waiting to run
Build CI / iOS compile & test (push) Waiting to run
Build CI / iOS UI tests (push) Waiting to run
PublishInternal / publish-google-play (push) Waiting to run
* Handle F3 and Ctrl+Alt+S on the project window

Both shortcuts hung off a Modifier.onPreviewKeyEvent in ProjectRootUi, so
they only fired when focus sat inside the project subtree and did nothing
from the home screen. Move them to the window onKeyEvent that already owns
Esc, Ctrl+W, Ctrl+Q and Ctrl+Shift+F.

F3 now goes through ProjectRoot.startProjectSync(), which opens the sync
modal only for server-linked projects, restoring the gate the old sync menu
item had.

* Match shortcut modifiers exactly and run them pre-focus

F3 and Ctrl+Alt+S were hand-rolled in the window's when-chain, which
dropped the exact-modifier matching onKeyShortcut enforced: Ctrl+F3 and
Ctrl+Alt+Shift+S both fired. Extract that predicate as
KeyEvent.matchesShortcut and use it for both.

Move the two to onPreviewKeyEvent as well. onKeyEvent only runs when
nothing on the focus path consumed the key, so a focused editor could
swallow them; the docs claimed otherwise. The other window shortcuts stay
on onKeyEvent so a focused component can still handle Esc first.

* Give Android and iOS the project shortcuts back

Moving F3 and Ctrl+Alt+S to the desktop window left the other platforms
with nothing. Add ProjectShortcutHost, which ProjectRootScaffold binds
while the project UI is composed, and drive it from each platform's own
key hook: Activity.dispatchKeyEvent on Android and UIKit key commands on
iOS. Both are focus independent, which the Compose modifiers were not.

The iOS container lives in Swift because keyCommands is an Objective-C
category member and Kotlin cannot override those.

* Bind the project shortcuts in one place

Desktop kept its own copy of the save-all action while Android and iOS went
through ProjectShortcutHost. Move the binding down to ProjectRootUi, the one
composable all three platforms render, so each host only detects keys and
calls the host object.

That also lets the window drop the snackbar state and coroutine scope it had
hoisted purely to run the action.
2026-08-07 00:45:59 -07:00
Adam Brown
c1fac8ff1e Prepared for release: v3.8.0 2026-08-04 21:47:41 -07:00
Adam Brown
22520baca3
Fix flaky iOS scene-editor save UI test (#786)
tapUntilGone gated the save tap solely on isHittable. Under CI load the
top-bar save button's accessibility frame can never settle to hittable
within the timeout, so the loop waited out the whole budget without ever
tapping and failed with 'Element still present after 20.0s'.

Keep the proven isHittable hit-tested fast path and add a geometry
fallback: once the button's frame has settled (fully below the status
bar, ruling out the transient dead-pixel position, and stable across two
samples) tap it via tapCenter. This guarantees a tap eventually fires
even when isHittable never flips true, while still avoiding the
dead-pixel/scroll-away hazard the isHittable gate originally fixed.
2026-07-28 22:38:18 -07:00
Adam Brown
fdeab5b992 Prepared for release: v3.7.2 2026-07-27 13:45:42 -07:00
Adam Brown
3375d81b55 Prepared for release: v3.7.1 2026-07-23 14:36:28 -07:00
Adam Brown
8742202de5 Prepared for release: v3.7.0 2026-07-21 09:10:11 -07:00
Adam Brown
fab1da62de Revert "Prepared for release: v3.7.0"
This reverts commit 9bfc9ec8e1.
2026-07-21 09:01:45 -07:00
Adam Brown
9bfc9ec8e1 Prepared for release: v3.7.0 2026-07-21 00:56:45 -07:00
Adam Brown
8bc1322c82
Retry the focus tap in the iOS UI test type() helper (#757)
A dropped coordinate tap left the field unfocused, so the software
keyboard never rose and the test failed on the single 10s wait.
Re-focus until the keyboard appears, matching typeIntoEditor.
2026-07-20 10:29:31 -07:00
Adam Brown
57f4f2a9fe
Harden flaky iOS scene-editor UI test with tap-retry helpers (#741)
* Harden flaky iOS scene-editor UI test with tap-retry helpers

SceneEditorWorkflowUITests.testEditSceneTextThenSave intermittently timed
out (most visibly waiting 20s for the save affordance to disappear). The
UI is Compose Multiplatform, which renders to a single surface, so
XCUITest taps are best-effort coordinate taps onto that surface. An
individual tap can be silently dropped (mid-relayout, under simulator
load, or while the software keyboard geometry is shifting). A lone tap
followed by one long wait is the classic XCUITest flake: a single dropped
tap costs the whole timeout and fails the test.

Add a small retry primitive and route the flake-prone taps through it:

- tapUntil(element, until:) re-taps on a short sub-timeout until the app
  demonstrably reacts, converging instead of eating the full timeout on
  one dropped tap.
- tap(_:expecting:) drives each scene-creation step by the element its
  tap surfaces (add menu, then the create-item dialog).
- tapUntilGone(_:) replaces the lone save tap + long wait; it also covers
  a late IME keystroke re-dirtying the buffer right after a save.
- openProjectCard re-taps the card until the project root's nav rail
  renders (same dropped-tap resilience), keeping its existing
  name/firstMatch card selection.

Validated by running the full iosUITests scheme on freshly-erased
simulators across 6 consecutive runs (all four UI test classes green
every run).

* Fix save-tap flake: only tap the save button when it's settled/hittable

CI surfaced the actual flake this test was meant to harden: the save tap
timed out because the button's accessibility frame is briefly stale right
after the edit that surfaces it — reported up under the status bar (a dead
pixel) until the top-bar layout settles. Blindly re-tapping that stale
coordinate never saved and could even scroll the app away from the editor,
so the re-tap loop ran the full timeout and failed.

Make tapUntilGone tap only when the button reports itself hittable
(settled), using a hit-tested tap() that re-resolves its real position;
otherwise wait for it to settle. Left untouched, the button stays hittable
even under load, so this converges cleanly instead of fighting a dead spot.

Scoped to the save helper; the navigation/card taps are unchanged.

Validated on a CI-sized simulator (iPhone 16e, 390x844) across 6 runs
under CPU load, all green.
2026-07-16 17:56:29 -07:00
Wavesonics
cfd3fefb5c Promote iOS scene editor UI test to full edit+save flow
The iOS SceneEditorWorkflowUITests was scoped to "scene opens" rather than
the Android-parity edit->save flow because the scene-editor-save affordance
never surfaced to XCUITest. Root cause: the iOS EditorTopBar save IconButton
was missing the SCENE_EDITOR_SAVE_TAG testTag that the Android/desktop
variants carry, so even once the edit dirtied the buffer and the button
rendered, XCUITest had no accessibility identifier to find.

- Tag the iOS EditorTopBar save button with SCENE_EDITOR_SAVE_TAG.
- Add a typeIntoEditor(_:into:until:) helper to HammerUITest that re-focuses
  and re-injects until the edit propagates, mirroring the Android
  typeIntoEditor retry loop (guards the enabled=hasReceivedInitialBuffer
  gate on the initial buffer load).
- Promote testCreateSceneOpensEditor to testEditSceneTextThenSave: type into
  the editor, assert save appears, tap it, assert it disappears — matching
  SceneEditorWorkflowTest.editSceneTextThenSave.

Verified on iPhone 16 (iOS 18.6) simulator: TEST SUCCEEDED.
2026-07-14 23:01:08 -07:00
Adam Brown
c5c42f2938 Prepared for release: v3.6.1 2026-07-12 23:30:39 -07:00
Adam Brown
501de2e597 Prepared for release: v3.6.0 2026-07-07 23:22:53 -07:00
Adam Brown
177e4bbe4f Prepared for release: v3.5.3 2026-06-30 15:16:45 -07:00
Adam Brown
43a836844d
Add iOS UI smoke tests (XCUITest) (#707)
* Add iOS UI smoke tests (XCUITest)

Adds an iOS UI smoke-test suite that drives the real app on a simulator via
XCUITest, the iOS analogue of the android/src/androidTest Compose UI tests.
The whole iOS UI is Compose Multiplatform, and CMP (1.8+) maps Compose testTags
to iOS accessibilityIdentifiers automatically, so the tests target the same
testTags the Android suite uses.

Workflows covered (all green on simulator):
- LaunchSmoke: app boots through the SwiftUI entry point, Koin + data migration
  run, project selection renders.
- ProjectWorkflow: create + open a project (exercises the create dialog text
  entry and navigation into the editor).
- SceneEditorWorkflow: create a scene and confirm it opens in the editor.
- NotesWorkflow: navigate to Notes and open the create-note screen.

Setup:
- ios/scripts/add_ui_test_target.rb idempotently creates the iosUITests
  UI-testing target + shared scheme via the xcodeproj gem (the folder previously
  had source files but no actual target).
- ios/scripts/disable_sim_hardware_keyboard.sh forces the software keyboard so
  XCUITest text entry lands (Compose fields need it).
- ios-ui-tests CI job runs the suite on a simulator and uploads the xcresult on
  failure.
- composeUi ProjectCreateDialog: tag the name field so the create flow is
  targetable (also benefits Android).

Known limitation: the app's custom rich-text editors (scene body, note body via
MarkdownEditField) do not report keyboard focus to XCUITest, so their text entry
can't be driven (only standard Compose text fields can). The scene and notes
tests therefore stop at "editor opens" / "creation screen opens"; the full
edit/create-with-body paths remain covered by the Android suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* iOS UI tests: type into note body via composetexteditor 2.3.0

composetexteditor 2.3.0 publishes text-editing accessibility semantics on its
editor, so XCUITest can now drive text entry into it. Bump the dependency and
promote the Notes UI test to the full Android-parity flow: create a note by
typing into the body, then assert its card appears.

The scene edit+save flow stays scoped to "scene opens" for now — text entry
works, but the scene editor's initial-buffer gating + dirty-driven save make
the save affordance unreliable to assert from an IME-driven edit; promoting it
is a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:39:04 -07:00
Adam Brown
57de1c6522 Prepared for release: v3.5.2 2026-06-29 22:15:55 -07:00
Adam Brown
46ce5042d3 Prepared for release: v3.5.1 2026-06-27 23:51:29 -07:00
Adam Brown
1a0a50bd76 Prepared for release: v3.5.0 2026-06-27 00:39:39 -07:00
Adam Brown
3b23dc6cf0 Prepared for release: v3.4.2 2026-06-23 10:10:05 -07:00
Adam Brown
77f1eb1884 Prepared for release: v3.4.1 2026-06-23 00:45:34 -07:00
Adam Brown
080c403619 Prepared for release: v3.4.0 2026-06-22 22:02:40 -07:00
Adam Brown
49ba08b55a Prepared for release: v3.3.1 2026-06-16 00:34:56 -07:00
Adam Brown
18bcc4652f Prepared for release: v3.3.0 2026-06-13 12:44:51 -07:00
Adam Brown
6fd3fd15ae
Prepared for release: v3.2.1 2026-06-10 19:41:32 -07:00
Adam Brown
93de0e7fd1
Prepared for release: v3.2.0 2026-06-08 00:08:51 -07:00
Wavesonics
97bc5a1b1a Prepared for release: v3.1.3 2026-06-04 21:56:12 -07:00
Adam Brown
35ecc7b750
Prepared for release: v3.1.2 2026-06-03 00:37:43 -07:00
Adam Brown
6c705d11bb
Prepared for release: v3.1.1 2026-06-02 00:29:07 -07:00
Adam Brown
28d0d2bdf1
Prepared for release: v3.1.0 2026-06-01 23:10:53 -07:00
Wavesonics
2517bb80c6 Add manifest-driven asset generation pipeline
Replace ad-hoc icon scripts with a single source-of-truth manifest
  (scripts/assets.yaml) and a Python generator that renders every app icon,
  store-listing graphic, MSIX tile, favicon, and the Play feature graphic from
  two SVG sources. Compositions (icon + "Hammer" wordmark in Kingthings
  Trypewriter) are defined once as percentages of canvas and reused across the
  Play feature graphic, MSIX wide/splash tiles, and the Snap featured banner.

  Adds the missing snap store icon, fixes the snap desktop icon to the correct
  256x256 hicolor size, and routes uploads-only assets to build/store-assets/
  (gitignored).

  See docs/ASSET-GENERATION.md for the manifest schema.
2026-05-22 19:26:49 -07:00
Wavesonics
246914d10f Cleanup plist 2026-05-20 23:27:12 -07:00
Wavesonics
331ff2336c Prep iOS Info.plist + profile reference for App Store submission
Declare ITSAppUsesNonExemptEncryption=false so App Store Connect stops
asking on every upload, drop the legacy armv7 UIRequiredDeviceCapabilities
entry, and align CFBundleShortVersionString with the shared codebase
version (3.0.3) in gradle/libs.versions.toml. pbxproj now references the
freshly-issued "Hammer AppStore iOS" provisioning profile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:16:31 -07:00
Wavesonics
496e05d934 clean up ios user files 2026-05-20 22:45:14 -07:00