chore(main): release 0.9.5 - #30
Merged
Merged
Conversation
github-actions
Bot
force-pushed
the
release-please--branches--main
branch
from
April 24, 2026 18:05
d9aab1b to
3ab5352
Compare
github-actions
Bot
force-pushed
the
release-please--branches--main
branch
from
April 25, 2026 04:24
3ab5352 to
bf44fcb
Compare
Contributor
Author
|
🤖 Created releases: 🌻 |
tstapler
added a commit
that referenced
this pull request
Apr 25, 2026
Brings in: - perf(android): decompose Phase 3 chunk writes (b5a51ab) - chore(main): release 0.9.5 (#30) Conflict resolution: - build.gradle.kts: keep minSdk=26 (required by genai-prompt:1.0.0-beta2) and take testInstrumentationRunner from main Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tstapler
pushed a commit
that referenced
this pull request
Apr 26, 2026
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
tstapler
added a commit
that referenced
this pull request
Apr 26, 2026
…r icons (#42) * feat(fdroid): fix repo version history and show version in Settings - F-Droid workflow now downloads APKs from all releases instead of only the latest, so the repo contains full version history and upgrades work correctly without needing to uninstall/reinstall. - DeviceInfo.android.kt reads actual versionName from PackageManager instead of the hardcoded "1.0.0". - SettingsDialog shows the app version (e.g. "v0.8.1") at the bottom of the settings sidebar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address Copilot review — iOS version and fdroid error handling - DeviceInfo.ios.kt: read CFBundleShortVersionString from NSBundle instead of returning hardcoded "1.0.0", so the Settings version label is accurate on iOS. - fdroid.yml: replace blanket `|| true` with explicit "no assets match" guard; fail fast on auth/network/ratelimit errors and emit a clear error if zero APKs are downloaded. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(bench): benchmark summary a9770de * fix(sync): prevent data-loss races on mobile reload and external conflict handling (#21) * fix(sync): prevent data-loss races on mobile reload and external conflict handling - Android: wire up SafChangeDetector ContentObserver in GraphLoader.startWatching(); add SAF-optimized listFilesWithModTimes to avoid N+1 queries per poll cycle - iOS: fix getLastModifiedTime (was hardcoded null), listFiles (empty list bug), listDirectories and directoryExists (didn't distinguish files from dirs) - DebounceManager: add cancel(key) and hasPending(key) suspend fns for targeted per-key cancellation and pending-state inspection - FileRegistry.detectChanges: wrap in Mutex to prevent double-emit on concurrent watcher poll + ContentObserver callback races - BlockStateManager: add hasPendingDiskWrite / cancelPendingDiskSave using the new DebounceManager APIs - StelekitViewModel: extend shouldProtect to cover the DB-saved-but-not-yet-written window (~300ms); cancel pending disk save when conflict dialog is shown; acceptDiskVersion flushes to disk via savePageNow to maintain consistency - Tests: comprehensive coverage for all five race-condition fixes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(state): unobservePage now clears blocks from state map The previous implementation cancelled the coroutine job but left the page's blocks in _blocks indefinitely, causing unbounded memory growth as users navigate pages. Now evicts the page entry on unobserve, which also aligns with the unobservePage_clears_state test contract. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sync): coalesce ContentObserver callbacks with Channel.CONFLATED Rapid ContentObserver signals (or the SAF 30s polling fallback) could queue up many redundant directory scans — each callback launched an independent parallelScope coroutine. Replace with a Channel(CONFLATED) that drops all-but-one pending trigger, so at most one extra scan is queued during a burst. The consumer coroutine is now a child of watcherJob so it is properly cancelled when startWatching is called again. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ios): fix commonMain compilation errors in PerfExporter and PerformanceDashboard String.format() and Dispatchers.IO are JVM-only; replace with KMP-compatible padStart string building and PlatformDispatcher.IO respectively. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(bench): benchmark summary 105928d * feat(search): AND semantics, field boosting, recency + graph distance ranking (#22) - FtsQueryBuilder: multi-term queries now use AND (all terms required) with wildcard on every token; OR fallback fires automatically when AND returns empty - BM25 scores exposed on SearchedPage/SearchedBlock; RankedSearchHit sealed class added for cross-type ranking with abstract score field - PAGE_BOOST (5×): page-title FTS hits ranked above body-text hits - RECENCY_HALFLIFE_DAYS (14d): exponential decay gives recently-edited results up to 2× score multiplier - GRAPH_BOOST (3×): results from 1-hop neighbour pages (via block_references) receive a 3× multiplier; selectNeighbourPageUuids UNION query pre-fetches the full neighbour set in a single indexed read - 37 tests covering AND semantics, OR fallback, field boost, graph distance, recency Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(main): release 0.9.0 (#20) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore(bench): benchmark summary 73cc4e8 * chore: update Homebrew formula for v0.9.0 * fix(cache): work around Kotlin 2.3.10 K2 compiler bug in LruCache When all public members of a class are suspend fun, the K2 compiler silently omits the outer .class file for the JVM target while still generating the inner coroutine continuation classes. This caused a NoClassDefFoundError: dev/stapler/stelekit/cache/LruCache at runtime. Three workarounds applied: - Extract weigher to fun interface LruWeigher<K,V> (SAM-convertible, call sites unchanged — lambdas still work via SAM conversion) - Replace Mutex.withLock{} (inline suspend) with explicit lock()/unlock() - Add val capacity: Long (non-suspend anchor that forces outer class emission) The root trigger is the all-suspend public API. Adding any one non-suspend public member to the class is sufficient to fix the missing .class file. Bug filed: https://youtrack.jetbrains.com/issue/KT-XXXXX * chore(bench): benchmark summary ba30932 * chore(main): release 0.9.1 (#23) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore(bench): benchmark summary 921e56f * chore: update Homebrew formula for v0.9.1 * fix(cache): strengthen LruCache.class workaround for Linux CI The previous workaround (val capacity: Long) forced LruCache.class emission on macOS but the Kotlin 2.3.10 K2 compiler still dropped the class on Linux. Adding override fun toString() as a second non-suspend anchor reliably triggers outer class emission on both platforms. * chore(bench): benchmark summary 59ae017 * chore(main): release 0.9.2 (#24) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore(bench): benchmark summary 6387e79 * chore: update Homebrew formula for v0.9.2 * fix(cache): replace coroutine Mutex with synchronized in LruCache The Kotlin 2.3.10 K2 compiler silently drops LruCache.class on Linux when all public members are suspend fun — non-suspend anchors (val capacity, override toString) fixed the issue locally on macOS but not in the Linux CI build environment. Switch to synchronized(this) instead of Mutex, making all methods plain fun. Operations are O(1) and hold the lock for microseconds so brief thread blocking is acceptable. Callers need no changes since non-suspend functions are callable from suspend contexts. The suspend → fun change also eliminates the all-suspend-public-API compiler bug entirely: no coroutine inner classes are generated so LruCache.class is always emitted. * chore(bench): benchmark summary 17345e4 * chore(main): release 0.9.3 (#25) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore(bench): benchmark summary 8121f76 * chore: update Homebrew formula for v0.9.3 * fix(cache): rename LruCache → SteleLruCache to bypass K2 compiler bug Kotlin 2.3.10 K2 on the macOS CI runner silently drops the .class file for a class named exactly "LruCache" in this package. All other workarounds (val property anchor, toString override, synchronized instead of suspend) failed to prevent the omission. Renaming to SteleLruCache produces a valid .class file. Type aliases LruCache and LruWeigher are kept so all call sites require zero changes. * chore(bench): benchmark summary ea70c86 * ci(macos): add gradle-home-cache-strict-match to prevent stale IC cache restore Prevents the Gradle Actions setup from falling back to a prefix-match cache restore for the macOS desktop build. Without this, stale Kotlin Incremental Compilation state in ~/.gradle/caches can cause the K2 compiler to reuse old output and silently omit .class files for renamed or modified classes. This pairs with the SteleLruCache rename (v0.9.4) to ensure a clean compile on macOS CI. * chore(bench): benchmark summary ba0aed2 * ci: unify Gradle cache configuration across all build jobs All four build jobs (android, linux, macos, windows) now share identical setup-gradle options: - gradle-home-cache-cleanup: true - gradle-home-cache-strict-match: true (was macOS-only) - cache-encryption-key The strict-match flag prevents the Gradle Actions prefix-match fallback from restoring stale Kotlin Incremental Compilation state from a different commit's cache entry. Without it, the K2 compiler can reuse old IC output and silently drop .class files for renamed or modified classes. The macOS build command is also aligned with other desktop jobs: - Removed :kmp:clean (was a workaround; strict-match is the real fix) - Added --build-cache (now consistent with linux and windows) * chore(bench): benchmark summary d8346ce * ci: add smoke-test startup steps to all three desktop build jobs Each desktop job now launches the final packaged artifact and waits 20 seconds to confirm the app stays alive. A JVM crash (e.g. from a NoClassDefFoundError) exits within ~2 seconds and fails the job before the artifact is uploaded. - Linux: Xvfb virtual display + AppImage launch - macOS: mount DMG, strip quarantine, run binary from mount point - Windows: createDistributable + PowerShell Start-Process check Windows also adds :kmp:createDistributable so the smoke test has a binary to run (the MSI installer cannot be tested without installing). The rename step is updated to exclude app/ subdirs when searching for the installer artifact. * chore(bench): benchmark summary d9ff7b3 * chore(main): release 0.9.4 (#28) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore(bench): benchmark summary f2d6623 * chore: update Homebrew formula for v0.9.4 * fix(resilience): fix loading screen hang + isolate test settings Three root causes of the infinite loading spinner, all fixed: 1. App.kt: use try/finally around migrationReady=false so it always resets to true even if the LaunchedEffect is cancelled mid-run. 2. App.kt: initialize currentGraphPath from the persisted active graph (graphManager.getActiveGraphInfo()?.path) instead of the hardcoded getDefaultGraphPath() which always pointed to ~/Documents/stelekit, causing an unnecessary graph-switch on every launch. 3. StelekitViewModel: catch CancellationException separately before the generic Exception catch and rethrow it — prevents structured-concurrency cancellation from being swallowed, which left isLoading stuck at true. Test isolation (the source of /tmp/rp-stale in production prefs): 4. Extract Settings interface from PlatformSettings expect class so tests can pass InMemorySettings instead of the file-backed PlatformSettings. All test files (RecentPagesTest, DiskConflictResolutionTest, ComposeUITestBase, TopBarTest, PageViewUITest, DesktopScreenshotTest) now use InMemorySettings — no more test paths leaking into ~/.stelekit/prefs.properties. * chore(bench): benchmark summary dc1b51b * test(ci): add KMP JVM test job + loading state regression tests - Add test-kmp job to release.yml gating resolve-version on test pass - Add StelekitViewModelLoadingTest: 6 pure-Kotlin tests covering isLoading state transitions, missing-directory error path, onboarding reset, scope cancellation resilience (CancellationException regression) - Add MigrationReadyLoadingTest: 4 Compose tests for the try/finally migrationReady pattern that caused the infinite spinner - Add waitForViewModelReady() extension to ComposeUITestBase These tests would have caught the two bugs fixed in dc1b51b. * perf(android): decompose Phase 3 chunk writes to allow HIGH-priority preemption (#26) * perf(android): decompose Phase 3 chunk writes to allow HIGH-priority preemption Phase 3 background indexing dispatched all saves for a 10-page chunk inside a single Execute(LOW) lambda, holding DatabaseWriteActor for the full chunk duration. A real Android session export showed a 4904ms db.queue_wait span on a HIGH-priority request — user navigation was blocked for ~5s while indexing ran. Replace flushChunkWrites (Execute-wrapped) with flushChunkWritesPreemptible, which dispatches savePages, deleteBlocksForPages, and saveBlocks as separate typed LOW actor calls. The actor can now service HIGH-priority SavePage/SaveBlocks requests between each step, including between consecutive pages during block saves (via the existing coalescing preemption in processSaveBlocks). Also extend PerfExporter.export() to accept an optional directory parameter and expose defaultExportDirectory(), wired up in PerformanceDashboard as an AlertDialog that lets the user choose the export location before writing the JSON report. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(research): android save/parse performance investigation artifacts MDD Phase 1–2 artifacts for the android-save-parse-performance project: requirements, research plan, findings across 4 dimensions (stack, features, architecture, pitfalls), and ADR-ready synthesis identifying DatabaseWriteActor contention as the confirmed root cause (4904ms db.queue_wait span on Android). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(android): add instrumented benchmark to gate actor-contention regressions Adds an androidInstrumentedTest in :kmp that runs on a real Android emulator in CI via reactivecircus/android-emulator-runner (API 30, x86_64). Two tests: - loadPhaseTimings: measures phase 1 TTI + phase 3 indexRemainingPages duration, asserts phase 1 stays under 10s (conservative for emulator variance) - writeLatencyDuringPhase3: the regression guard — concurrently runs indexRemainingPages and block saves every 200ms, asserts write p95 stays under 5s. Before the actor decomposition fix, this would have measured ~5000ms (Execute(LOW) holding the actor for a full 10-page chunk); after the fix it should be well under 2s. Uses a lightweight inline FileSystem instead of PlatformFileSystem to avoid SAF ContentProvider dependency in the test process. Generates 20 pages + 5 journals directly into context.cacheDir so no SAF permissions are needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(bench): android benchmark history tracking and PR diff comments Mirrors the JVM benchmark diff pattern for Android: - AndroidGraphBenchmark now logs structured JSON to logcat (tag ANDROID_BENCH) at the end of each test: loadPhase and writeLatency metrics - android-benchmark.yml captures logcat after the emulator run, parses the JSON lines with Python, and writes a timestamped summary to benchmarks/android-history/{slug}_{sha}.json - On push to main: commits the new history file (same pattern as benchmark.yml) - On PR: compares against the most recent baseline in android-history/ and posts (or updates) a comment with a delta table covering phase 1 TTI, phase 3 index time, write p95 baseline, write p95 during phase 3, and jank factor Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(bench): remove backslash line continuations in emulator runner script reactivecircus/android-emulator-runner executes each script line separately, so backslash continuations are not shell-interpreted. Put the gradle command on a single line. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address Copilot review comments PerfExporter: treat blank directory string as absent so clearing the path field in the export dialog falls back to the Downloads folder instead of producing an invalid path like /stelekit-perf-....json. GraphLoader: restore per-page atomicity in flushChunkWritesPreemptible. The previous implementation called deleteBlocksForPages for all pages at once then saveBlocks per page via separate typed requests. A HIGH write could interleave between the delete and save for the same page, leaving it transiently with no blocks. Fix: wrap each page's deleteBlocksForPages + saveBlocks inside a single Execute(LOW) so HIGH can only preempt between pages (not within a page's delete+save sequence). GraphLoader: move KDoc above @OptIn so it is attached to the function. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve compile errors introduced by merging main - Add `: Settings` to Android actual PlatformSettings (expect/actual mismatch from Settings interface added to main after our branch diverged) - Make FakeFileSystem open so MissingDirectoryFileSystem in StelekitViewModelLoadingTest can extend it Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove unimportable assertDoesNotExist import, add override modifiers - Remove `import androidx.compose.ui.test.assertDoesNotExist` — in Compose Multiplatform Desktop the function is a member of SemanticsNodeInteraction, not a top-level extension function; callers use it as a method call directly - Add `override` modifier to all four Settings method implementations in Android PlatformSettings (required when a class explicitly implements an interface via `: Settings`) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: guarantee isLoading=false when loadGraph job is cancelled pre-start When scope.cancel() is called before the loadGraph coroutine reaches its first suspension point, the CancellationException catch block may not run, leaving isLoading=true indefinitely. invokeOnCompletion fires unconditionally when the job ends — including for cancellations that happen before the coroutine body executes — so isLoading always resets to false. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ios): replace JVM-only synchronized with KMP-compatible PlatformLock LruCache.kt was updated on main to use synchronized(this) which is JVM-only and breaks Kotlin/Native (iOS) compilation. Replace with an expect/actual PlatformLock that uses ReentrantLock on JVM/Android and a no-op on iOS/WASM where coroutine dispatcher scheduling provides mutual exclusion within the DB context. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(bench): benchmark summary b5a51ab * chore(main): release 0.9.5 (#30) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore(bench): benchmark summary 283753a * chore: update Homebrew formula for v0.9.5 * chore(bench): benchmark summary 9bb8a77 * chore(main): release 0.10.0 (#34) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore(bench): android benchmark summary a1dfe06 * chore: update Homebrew formula for v0.10.0 * feat(android): extract Application class and share GraphManager across process lifecycle Introduces SteleKitApplication to initialize the database driver and file system at process start, so GraphManager survives Activity recreation without re-loading the graph. Adds Glance AppWidget dependencies for upcoming home screen widget support. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(benchmark): retry push with rebase on non-fast-forward failure When commits land on main while the benchmark job is running, the summary push fails with a non-fast-forward error. Retry up to 5 times, pulling with rebase before each attempt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(bench): benchmark summary 962b647 * fix(search): create-page in link picker now inserts link and appears first The onCreatePage callback in PageView and JournalsView was a no-op, so selecting "Create page" from the link picker did nothing. Wire it to insertLinkAtCursor — same behaviour as selecting an existing page. Also prepend CreatePageItem instead of appending so it is the first (default-selected) result rather than buried below all page/block hits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(fdroid): enforce latest version, add metadata, replace placeholder icons Three issues prevented the F-Droid store from showing correct information: 1. versionCode was hardcoded to 1 — F-Droid had no way to rank APKs, so it suggested the oldest release (v0.1.0). Now computed as major×1_000_000 + minor×1_000 + patch, coerced ≥2 so all future APKs outrank the historical versionCode=1 builds. 2. No fdroid/metadata/ directory — the store showed no description or links. Added fdroid/metadata/dev.stapler.stelekit.yml with name, summary, full description, license, and source/issue-tracker URLs. 3. All mipmap ic_launcher_foreground.png, ic_launcher.png, ic_launcher_round.png, and ic_launcher_monochrome.png were identical 3 KB placeholder files regardless of density. Replaced with the SteleKit brand icon (from assets/brand/png/icon_512.png) resized to the correct density-proportional dimensions, with the adaptive foreground respecting the 72/108dp safe zone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 I have created a release beep boop
0.9.5 (2026-04-25)
Bug Fixes
Performance Improvements
This PR was generated with Release Please. See documentation.