Skip to content

fix(ci): resolve CI failures, skip warm-start ANALYZE, add init timing logs#200

Open
tstapler wants to merge 5 commits into
mainfrom
fix/ci-failures
Open

fix(ci): resolve CI failures, skip warm-start ANALYZE, add init timing logs#200
tstapler wants to merge 5 commits into
mainfrom
fix/ci-failures

Conversation

@tstapler

@tstapler tstapler commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • fix(ci): resolve pre-existing CI failures on main
  • fix(wasm): move ktoml out of commonMain to fix wasm-opt failure
  • fix(sections): add encodeSectionManifestToml expect/actual for SectionManifestWriter
  • perf(db): skip ANALYZE blocks/pages on warm starts when sqlite_stat1 already has stats — saves ~100ms per startup on Android by checking sqlite_stat1 before running ANALYZE instead of running it unconditionally every launch
  • debug(init): add elapsed-time log lines to switchGraph init sequence so the on-screen overlay shows exactly where init time is spent

Test plan

  • Business tests pass: bazel test //kmp:business_tests
  • JVM tests pass (pre-existing failures baseline: 54): bazel test //kmp:jvm_tests
  • On Android: warm-start overlay shows init[Xms]: createRepositorySet done where X is measurably lower than before
  • Fresh-install second startup still shows correct query plans (ANALYZE runs when sqlite_stat1 has no entry for blocks/pages)

🤖 Generated with Claude Code

tstapler and others added 5 commits July 1, 2026 22:04
- Detekt: fix MultipleEmitters in DeviceSetupWizard (wrap in Column),
  ComposableParamOrder in SectionBadge (modifier before onClick),
  ComplexCondition in SettingsDialog (extract to local val)
- Bazel Android: add ktoml dep missing from androidMain BUILD.bazel
- Wasm/JS: move js() calls to top-level functions (Kotlin/Wasm restriction),
  replace onLeft with Either.Left pattern match to avoid missing import
- SQLDelight: sync generated sources (idx_pages_section_id in create())

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ktoml 0.7.1 generates WASM bytecode that fails binaryen wasm-opt
validation during the production bundle optimization step. Move ktoml
to jvmCommonMain + iosMain and introduce an expect/actual split:
- jvmCommonMain/iosMain: actual uses ktoml for real TOML parsing
- wasmJsMain: actual returns null (SectionManifestParser falls back
  to empty SectionManifest — no file system in the browser anyway)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nManifestWriter

SectionManifestWriter.kt (commonMain) referenced sectionToml directly,
which was moved to jvmCommonMain in the ktoml WASM fix. Apply the same
expect/actual split: JVM+iOS actuals delegate to ktoml, WASM stub throws
(caught by the surrounding Either error handler).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tats

ANALYZE blocks + ANALYZE pages ran unconditionally every startup (~50ms
each on Android). Gate them on sqlite_stat1 having no entry for the table
— the only case that needs explicit ANALYZE is the second startup after a
fresh install (when the analyze_blocks migration ran on an empty table and
wrote no stats). Warm starts now run 2× cheap SELECT instead.
PRAGMA optimize continues to run unconditionally to handle stale stats
for all other tables.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Logs ms elapsed at each major step (preFlightJob, createRepositorySet,
UuidMigration, content migrations) so the overlay shows where init time
is actually spent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 2, 2026 16:07
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

JVM Load Benchmark (Desktop)

Synthetic in-memory benchmark measuring load performance for the desktop (JVM) app.
Comparing 4d702e96 (this PR) vs 929ffc81 (baseline)
Graph config: xlarge — 230 pages

Metric This PR Baseline Delta
Phase 1 TTI ↓ 1ms 0ms +1ms ⚠️
Phase 2 background ↓ 0ms 0ms 0 (0%)
Phase 3 index ↓ 1ms 1ms 0 (0%)
Total ↓ 2ms 1ms +1ms (+100%) ⚠️
Write p95 (baseline) ↓ 32ms 31ms +1ms (+3%) ⚠️
Write p95 (under load) ↓ 1ms n/a
Jank factor ↓ 0.03x n/a
↓ lower is better
Flamegraphs (this PR) **Allocation** — object allocation pressure (JDBC/SQLite churn)

Alloc flamegraph not available

CPU — method-level hotspots by on-CPU time

CPU flamegraph not available

Top allocation hotspots (this PR) `38%` byte[]_[k] `9.3%` java.lang.String_[k] `6.3%` java.util.LinkedHashMap$Entry_[k] `5.5%` int[]_[k] `3.3%` java.lang.Object[]_[k]
Top CPU hotspots (this PR) `94.3%` /usr/lib/x86_64-linux-gnu/libc.so.6 `2.2%` /tmp/sqlite-3.51.3.0-f61332fc-1f2d-41b6-bd6a-5f069b9dfd98-libsqlitejdbc.so `0.9%` __libc_pwrite `0.4%` fsync `0.3%` SR_handler

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses CI failures and platform build issues (notably Kotlin/Wasm), introduces platform-specific TOML parsing/encoding for section manifests, and improves startup performance/observability by skipping redundant SQLite ANALYZE work on warm starts and adding init timing logs.

Changes:

  • Move section manifest TOML parsing/encoding behind expect/actual so ktoml is only used on JVM/iOS, with WASM stubs.
  • Reduce warm-start startup work by skipping ANALYZE blocks/pages when sqlite_stat1 already contains stats; add init elapsed-time logs in GraphManager.
  • Fix Kotlin/Wasm js() interop placement and apply minor UI/compose refactors.

Reviewed changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
kmp/src/wasmJsMain/kotlin/dev/stapler/stelekit/sync/WasmSectionSyncService.kt Moves js() helpers to top-level for Kotlin/Wasm compatibility; replaces Arrow onLeft usage with explicit Either.Left check.
kmp/src/wasmJsMain/kotlin/dev/stapler/stelekit/sections/SectionManifestTomlDecoder.js.kt WASM actuals: decoding returns null (parser falls back), encoding throws unsupported op.
kmp/src/jvmCommonMain/kotlin/dev/stapler/stelekit/sections/SectionManifestTomlDecoder.jvm.kt JVM actuals using ktoml for decoding/encoding section manifest TOML.
kmp/src/iosMain/kotlin/dev/stapler/stelekit/sections/SectionManifestTomlDecoder.ios.kt iOS actuals using ktoml for decoding/encoding section manifest TOML.
kmp/src/generated/sqldelight/dev/stapler/stelekit/db/SteleDatabaseQueries.kt Generated SQLDelight changes (formatting/arg count expression).
kmp/src/generated/sqldelight/dev/stapler/stelekit/db/kmp/SteleDatabaseImpl.kt Generated schema creation adds idx_pages_section_id.
kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/onboarding/DeviceSetupWizard.kt Wraps mode selection/custom mode UI in Column containers.
kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/settings/SettingsDialog.kt Refactors null-checks for Sections settings rendering; uses !! after consolidated condition.
kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/SectionBadge.kt Reorders parameters to put modifier before onClick.
kmp/src/commonMain/kotlin/dev/stapler/stelekit/sections/SectionManifestWriter.kt Routes TOML encoding through encodeSectionManifestToml expect/actual.
kmp/src/commonMain/kotlin/dev/stapler/stelekit/sections/SectionManifestParser.kt Routes TOML decoding through decodeSectionManifestToml expect/actual with WASM fallback to empty manifest.
kmp/src/commonMain/kotlin/dev/stapler/stelekit/db/MigrationRunner.kt Skips ANALYZE when stats already exist; adds hasStats helper.
kmp/src/commonMain/kotlin/dev/stapler/stelekit/db/GraphManager.kt Adds elapsed-time log lines across switchGraph init sequence.
kmp/src/androidMain/kotlin/BUILD.bazel Adds ktoml dependency for Android Bazel build.
kmp/build.gradle.kts Moves ktoml dependency out of commonMain into jvmCommonMain and iosMain.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +831 to 834
// ponytail: only ANALYZE when stats are absent; PRAGMA optimize handles stale stats
if (!hasStats(driver, "blocks")) driver.execute(null, "ANALYZE blocks", 0).await()
if (!hasStats(driver, "pages")) driver.execute(null, "ANALYZE pages", 0).await()
driver.execute(null, "PRAGMA optimize", 0).await()
Comment on lines +839 to +844
driver.executeQuery(
identifier = null,
sql = "SELECT count(*) FROM sqlite_stat1 WHERE tbl = '$table'",
mapper = { cursor -> cursor.next(); QueryResult.Value((cursor.getLong(0) ?: 0L) > 0L) },
parameters = 0
).await()
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Android Load Benchmark

Instrumented benchmark on an API 30 x86_64 emulator — 500-page synthetic graph.

Comparing 4d702e96 (this PR) vs 929ffc81 (baseline)
Device: API 30 x86_64 emulator — 530 pages loaded

Graph Load

Metric This PR Baseline Delta
Phase 1 TTI ↓ 44ms 43ms +1ms (+2%) ⚠️
Phase 3 index ↓ 4101ms 3730ms +371ms (+10%) ⚠️

Interactive Write Latency (during Phase 3)

Metric This PR Baseline Delta
Write p95 (baseline) ↓ 8ms 7ms +1ms (+14%) ⚠️
Write p95 (during phase 3) ↓ 15ms 13ms +2ms (+15%) ⚠️
Jank factor ↓ 1.88x 1.86x +0.02x (+1%) ⚠️
Concurrent writes ↑ 20 18 +2ms (+11%) ✅

SAF I/O Overhead (ContentProvider vs direct File read)

Measures Binder IPC cost added by ContentResolver per readFile() call.
Real SAF via ExternalStorageProvider will be higher on device; this is a lower bound.

Metric This PR Baseline Delta
Direct read / file ↓ 0.0ms 0.0ms 0 (0%)
Provider read / file ↓ 0.2ms 0.2ms 0 (0%)
IPC overhead ratio ↓ 6x 6x 0 (0%)
↓ lower is better · ↑ higher is better

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants