Skip to content

fix(ui): show actual LLM error; skip API key dialog for on-device providers#202

Merged
TylerStaplerAtFanatics merged 3 commits into
mainfrom
fix/tag-suggestion-ux
Jul 3, 2026
Merged

fix(ui): show actual LLM error; skip API key dialog for on-device providers#202
TylerStaplerAtFanatics merged 3 commits into
mainfrom
fix/tag-suggestion-ux

Conversation

@tstapler

@tstapler tstapler commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Tag suggestion error message: TagChipRow and SuggestionBottomSheet showed a hardcoded "Could not reach LLM" regardless of the actual failure reason. Now surfaces state.llmError directly — users see "Model is still downloading — try again in a few minutes" instead of a generic network error message.
  • On-device API key dialog: Clicking an on-device provider row (e.g. Gemini Nano) in Settings → AI Providers was opening EditBuiltInProviderKeyDialog. On-device providers need no credentials. onEditProvider now checks registry.find(id)?.kind != LlmProviderKind.ON_DEVICE before opening the dialog.

Test plan

  • Trigger tag suggestion while Gemini Nano model is still downloading — verify bottom sheet shows "Model is still downloading — try again in a few minutes"
  • Trigger tag suggestion with a real network failure — verify "Network error" is shown
  • Navigate to Settings → AI Providers, tap the on-device (Gemini Nano) row — verify no dialog opens
  • Tap a remote provider (Anthropic/OpenAI) row — verify API key dialog still opens normally

🤖 Generated with Claude Code

…vice providers

- TagChipRow + SuggestionBottomSheet: replace hardcoded "Could not reach LLM"
  with state.llmError so users see the real reason (e.g. "Model is still
  downloading — try again in a few minutes" vs. a network failure)
- LlmProviderSettings.onEditProvider: skip EditBuiltInProviderKeyDialog when
  the tapped provider's kind is ON_DEVICE — on-device providers have no
  credentials to configure so opening the key dialog was a no-op that confused
  users into thinking credentials were required

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 04:04

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

Improves AI-related UI behavior by surfacing the actual LLM failure message to users (instead of a hardcoded generic error) and preventing the built-in API key dialog from opening for on-device LLM providers that don’t require credentials.

Changes:

  • Display llmError / state.llmError directly in tag suggestion UI surfaces (TagChipRow, SuggestionBottomSheet).
  • Update provider-edit behavior so ON_DEVICE providers don’t trigger the built-in API key dialog in Settings → AI Providers.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/tags/TagChipRow.kt Shows the actual LLM error string when present instead of a hardcoded message.
kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/tags/SuggestionBottomSheet.kt Surfaces state.llmError to present provider-specific failure reasons to users.
kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/settings/LlmProviderSettings.kt Prevents opening the built-in provider key dialog when the selected provider is on-device.

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

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

JVM Load Benchmark (Desktop)

Synthetic in-memory benchmark measuring load performance for the desktop (JVM) app.
Comparing 623c407d (this PR) vs 7f9ecbdb (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) ↓ 37ms 34ms +3ms (+9%) ⚠️
Write p95 (under load) ↓ n/a 0ms
Jank factor ↓ n/a 0x
↓ 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%` java.lang.String_[k] `6.9%` java.util.LinkedHashMap$Entry_[k] `6.7%` int[]_[k] `3.6%` java.lang.StringBuilder_[k]
Top CPU hotspots (this PR) `94.2%` /usr/lib/x86_64-linux-gnu/libc.so.6 `2.2%` clock_nanosleep `1.4%` /tmp/sqlite-3.51.3.0-edfc9df3-6b2e-4f54-b6db-d689ad7ec397-libsqlitejdbc.so `0.6%` __libc_pwrite `0.3%` fsync

…ditProvider

null != ON_DEVICE is true in Kotlin, so an unrecognized provider id would
silently open the API key dialog. == REMOTE is safe for null and future
LlmProviderKind variants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Android Load Benchmark

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

Comparing 623c407d (this PR) vs 7f9ecbdb (baseline)
Device: API 30 x86_64 emulator — 530 pages loaded

Graph Load

Metric This PR Baseline Delta
Phase 1 TTI ↓ 39ms 40ms -1ms (-2%) ✅
Phase 3 index ↓ 4063ms 3753ms +310ms (+8%) ⚠️

Interactive Write Latency (during Phase 3)

Metric This PR Baseline Delta
Write p95 (baseline) ↓ 9ms 8ms +1ms (+13%) ⚠️
Write p95 (during phase 3) ↓ 16ms 10ms +6ms (+60%) ⚠️
Jank factor ↓ 1.78x 1.25x +0.53x (+42%) ⚠️
Concurrent writes ↑ 19 19 0 (0%)

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

LlmProviderRow now accepts nullable onClick — passes null for ON_DEVICE
providers so the row is non-clickable and the ChevronRight/Edit icon is
hidden. Also fixes null-safe inversion in LlmProviderSettings.onEditProvider:
use == REMOTE allowlist instead of != ON_DEVICE denylist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@TylerStaplerAtFanatics TylerStaplerAtFanatics merged commit 49880fc into main Jul 3, 2026
18 checks passed
@tstapler tstapler deleted the fix/tag-suggestion-ux branch July 3, 2026 15:55
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.

3 participants