Skip to content

refactor(ui): extract LlmSuggestionCoordinator from StelekitViewModel - #378

Merged
tstapler merged 4 commits into
mainfrom
refactor/stelekit-viewmodel-llm-suggestion-coordinator
Oct 1, 2026
Merged

tstapler merged 4 commits into
mainfrom
refactor/stelekit-viewmodel-llm-suggestion-coordinator

Conversation

@tstapler

@tstapler tstapler commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Phase 2 of 5 from project_plans/stelekit-viewmodel-decomposition/plan.md (PR docs(plan): StelekitViewModel.kt decomposition plan #373): extracts the 8-function LLM Suggestion Workflow group (llmSuggestions, observeLlmSuggestions, proposeLlmSuggestion, dismissLlmSuggestionReview, rejectLlmSuggestion, acceptLlmSuggestion, openLlmProviderSettings, dismissLlmProviderSettings) out of StelekitViewModel.kt into a new LlmSuggestionCoordinator, mirroring Phase 1's SectionManagementCoordinator (PR refactor(ui): extract SectionManagementCoordinator from StelekitViewModel #375) — shares _uiState: MutableStateFlow<AppState> directly (the 2 AppState fields it owns are read by GraphDialogLayer.kt), reuses the ViewModel's own scope.
  • StelekitViewModel.kt: 2,667 → 2,619 lines. New file is 138 lines.
  • All 7 call-site-facing members (llmSuggestions val + 6 funs) keep one-line forwarders — verified by grep against every Compose UI and test call site (GraphDialogLayer.kt, GraphContentTagVoiceSetup.kt, screens/PageView.kt, StelekitViewModelLlmSettingsTest.kt, StelekitViewModelLlmSuggestionTest.kt). observeLlmSuggestions is private/init-only, so it's called directly on the coordinator from init {} with no VM-level forwarder, matching Phase 1's loadSectionManifest precedent.

Test plan

  • ./gradlew :kmp:compileKotlinJvm — BUILD SUCCESSFUL.
  • Targeted tests: StelekitViewModelLlmSettingsTest (jvmTest) — 1/1 passed; StelekitViewModelLlmSuggestionTest (businessTest) — 6/6 passed.
  • Full ./gradlew :kmp:jvmTest (5,072 tests): 5 pre-existing failures (JournalViewFanoutBenchmarkTest, CapturePopupWindowTest, CapturePopupWindowUxTest×2, BlockItemGestureTest) — same count as PR refactor(ui): extract SectionManagementCoordinator from StelekitViewModel #375's baseline, confirmed via content-grep to have zero relation to LLM suggestion code.

Phase 2 of 5 from project_plans/stelekit-viewmodel-decomposition/plan.md:
moves the LLM approval-gated edit workflow (llmSuggestions,
observeLlmSuggestions, proposeLlmSuggestion, dismissLlmSuggestionReview,
rejectLlmSuggestion, acceptLlmSuggestion, openLlmProviderSettings,
dismissLlmProviderSettings) out of StelekitViewModel into a new
LlmSuggestionCoordinator, mirroring Phase 1's SectionManagementCoordinator
pattern (shares _uiState directly, reuses the ViewModel's scope).
StelekitViewModel keeps one-line forwarders for every call site.
@tstapler
tstapler marked this pull request as ready for review October 1, 2026 05:27
Copilot AI balanced review requested due to automatic review settings October 1, 2026 05:27

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.

Copilot review overview

🟢 Approval recommended

The extraction preserves existing behavior and lifecycle handling; the remaining documentation correction is non-blocking.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Extracts the LLM suggestion workflow from StelekitViewModel into a dedicated coordinator while preserving its public API and shared lifecycle.

Changes:

  • Adds LlmSuggestionCoordinator for suggestion review, acceptance, rejection, and provider settings.
  • Retains ViewModel forwarding methods and initializes observation through the coordinator.
File Description
StelekitViewModel.kt Delegates LLM workflow operations to the coordinator.
LlmSuggestionCoordinator.kt Implements the extracted workflow and state orchestration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +61 to +63
* parallel to `StelekitViewModel.observeSyncState`'s `syncState.collect` — does NOT
* auto-dismiss when the inbox becomes empty via accept/reject (those explicitly set
* visibility, same "do NOT auto-dismiss" rule as journal-merge review).
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

JVM Load Benchmark (Desktop)

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

Metric This PR Baseline Delta
Phase 1 TTI ↓ 1ms 1ms 0 (0%)
Phase 2 background ↓ 0ms 0ms 0 (0%)
Phase 3 index ↓ 1ms 1ms 0 (0%)
Total ↓ 2ms 2ms 0 (0%)
Write p95 (baseline) ↓ 16ms 19ms -3ms (-16%) ✅
Write p95 (under load) ↓ n/a n/a
Jank factor ↓ n/a 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) `37.4%` byte[]_[k] `7.4%` java.lang.String_[k] `7.2%` int[]_[k] `6.7%` java.util.LinkedHashMap$Entry_[k] `3.8%` java.lang.StringBuilder_[k]
Top CPU hotspots (this PR) `96.5%` /usr/lib/x86_64-linux-gnu/libc.so.6 `1.4%` /tmp/sqlite-3.51.3.0-18cd4715-8427-4742-a479-2d534ce09ee6-libsqlitejdbc.so `0.5%` __libc_pwrite `0.2%` fsync `0.2%` SR_handler

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Android Load Benchmark

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

Comparing afda3095 (this PR) vs 052545d5 (baseline)
Device: API 30 x86_64 emulator — 530 pages loaded

Graph Load

Metric This PR Baseline Delta
Phase 1 TTI ↓ 32ms 34ms -2ms (-6%) ✅
Phase 3 index ↓ 2908ms 4414ms -1506ms (-34%) ✅

Interactive Write Latency (during Phase 3)

Metric This PR Baseline Delta
Write p95 (baseline) ↓ 4ms 9ms -5ms (-56%) ✅
Write p95 (during phase 3) ↓ 11ms 18ms -7ms (-39%) ✅
Jank factor ↓ 2.75x 2x +0.75x (+38%) ⚠️
Concurrent writes ↑ 12 23 -11ms (-48%) ⚠️

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 +0ms (+18%) ⚠️
IPC overhead ratio ↓ 6x 5x +1x (+20%) ⚠️
↓ lower is better · ↑ higher is better

tstapler added a commit that referenced this pull request Oct 1, 2026
## Summary

Phase 3 of [the StelekitViewModel decomposition
plan](https://github.com/tstapler/stelekit/blob/main/project_plans/stelekit-viewmodel-decomposition/plan.md)
— extracts the git-sync status machinery and the git
setup/conflict/journal-merge dialog group into a new
`GitSyncCoordinator` collaborator, following the
`SectionManagementCoordinator` (#375) and `LlmSuggestionCoordinator`
(#378) pattern.

- New `GitSyncCoordinator` owns the `syncState`/`gitLastSyncAt` derived
`StateFlow`s (built from the active `GitSyncService`) and the 16 moved
functions (`triggerSync`, `triggerFetchOnly`, `setGitConfig`, git setup
wizard open/dismiss variants, conflict-resolution/journal-merge-review
dialog methods, git-detection/browser-only-sync banner dismissal).
- `StelekitViewModel` keeps one-line forwarders for every public method
plus the two public `StateFlow` properties (`syncState`,
`gitLastSyncAt`), both read directly by Compose call sites
(`GraphContentActiveShell`, `GraphDialogLayer`,
`GraphContentLeftSidebar`) and by dedicated tests — verified by grep
before removal.
- Pure structural move, no behavior change.

## Verification

- `./gradlew :kmp:compileKotlinJvm` — BUILD SUCCESSFUL
- `./gradlew :kmp:jvmTest --tests StelekitViewModelSyncStateTest --tests
StelekitViewModelSyncStateIntegrationTest` — all 4 tests PASSED
- `scripts/jvm-display-check.sh -- ./gradlew :kmp:jvmTest` (full suite)
— 5072 tests completed, 5 failed, 39 skipped; all 5 failures are the
known pre-existing flakes (`JournalViewFanoutBenchmarkTest`,
`CapturePopupWindowTest`, `CapturePopupWindowUxTest`,
`BlockItemGestureTest.shiftClick_extendsSelection_notEditMode`),
unrelated to this change
tstapler added a commit that referenced this pull request Oct 1, 2026
… from breaking CI (#381)

## Why

`downloads.sourceforge.net` had intermittent Cloudflare 522 ("origin
unreachable") outages on 2026-10-01 that:
- Failed the Bazel Android build/test jobs on PRs #370, #373, #378, #379
- Caused `v0.89.0`'s release safety gate to exhaust its 3 retries,
skipping every downstream build/publish job — the release shipped with
**zero assets**

Root cause: `MODULE.bazel`'s `unzip_src`/`zip_src` `http_archive` rules
had a single hardcoded SourceForge URL each, with no fallback.

## Fix

Both tarballs (InfoZip `unzip60.tar.gz` / `zip30.tar.gz`) are now hosted
as assets on a dedicated, non-app [GitHub
release](https://github.com/tstapler/stelekit/releases/tag/ci-vendored-deps-infozip-v1)
in this repo — verified `sha256`-identical to the hashes already pinned
in `MODULE.bazel` (confirmed against the original SourceForge files
before the outage, and cross-checked against MacPorts/OSUOSL mirrors).

`MODULE.bazel`'s `urls` lists now try, in order: our own GitHub release
→ MacPorts/OSUOSL mirrors → SourceForge (demoted to last, kept only as a
final fallback). `http_archive` tries each URL until one succeeds, so
this requires no other code changes.

## Verification

- `bazel build --repository_cache=<empty> @unzip_src//:unzip
@zip_src//:zip` after deleting the previously-extracted external repos —
forces a true cold fetch, confirms the new URL list resolves and the
sha256 check passes.
- `bazel build //:stelekit_android_toolchain_impl` — the consumer of
these two targets — builds clean.
…wmodel-llm-suggestion-coordinator

# Conflicts:
#	kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/StelekitViewModel.kt
…wmodel-llm-suggestion-coordinator

# Conflicts:
#	kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/StelekitViewModel.kt
@tstapler
tstapler merged commit e442e18 into main Oct 1, 2026
23 of 24 checks passed
@tstapler
tstapler deleted the refactor/stelekit-viewmodel-llm-suggestion-coordinator branch October 1, 2026 09:05
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