Skip to content

feat: improve on-device coach quality and orbiting shell FAB - #102

Merged
YKDBontekoe merged 6 commits into
mainfrom
cursor/coach-fab-improvements-8833
Jul 13, 2026
Merged

YKDBontekoe merged 6 commits into
mainfrom
cursor/coach-fab-improvements-8833

Conversation

@YKDBontekoe

@YKDBontekoe YKDBontekoe commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Addresses two coach UX issues: weak/truncated on-device model replies and awkward shell FAB placement/interaction.

On-device coach quality

  • Higher output budgets: 384 tokens (full tier) / 192 tokens (constrained), up from 256/128
  • Auto-continuation: when a reply looks cut off by the token budget, the isolate sends a short follow-up to finish the thought
  • Tier-aware prompts: constrained devices get a direct-answer system prompt (no tool catalog) and skip the agentic tool loop
  • Structured output format: SIGNALS / ANSWER / ACTION scaffolding helps small models stay complete
  • Smarter truncation: question block is always preserved; context sections drop from lowest priority when over budget
  • Context on tool follow-ups: coach context is no longer cleared between tool steps

Shell FAB

  • Orbit menu: tab destinations and settings fan out in an upper-right arc instead of a vertical stack, with staggered scale/fade animation
  • Coach tab positioning: FAB sits above the input bar via bottomInset
  • Input clearance: chat text field is inset on the left so typing doesn't sit under the FAB

Validation

  • flutter analyze — clean
  • flutter test — 257 passing
  • flutter build web — succeeds
  • Visual demo: coach FAB orbit + positioning

coach-fab-orbit-demo.mp4

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Updated coach chat prompt formatting with tier-aware behavior and improved response streaming, including automatic continuation for likely-incomplete outputs.
    • Enhanced the floating navigation control with arc-style orbit rendering and improved spacing around the coach chat input.
  • Bug Fixes

    • Ensured coach prompts respect on-device character limits while preserving the athlete question block.
  • Tests

    • Added/expanded tests for prompt truncation, truncated-response detection, and tool-loop skipping in constrained tiers.

Raise local model output budgets and add continuation when responses
truncate mid-sentence. Use tier-aware prompts, structured output format,
priority-aware context truncation, and skip agentic tool loops on
constrained devices.

Refactor shell FAB to orbit menu items in an upper-right arc, lift it
above the coach input bar, and inset the chat text field from the left.

Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cursor[bot], you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 767fe962-44a2-4c49-a831-ec527c449fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 0ffefd6 and f4e2866.

📒 Files selected for processing (2)
  • CODEMAP.md
  • lib/features/coach_chat/providers/coach_chat_provider.g.dart
📝 Walkthrough

Walkthrough

Coach chat now uses tier-aware prompt and response handling, including continuation for truncated Gemma output and conditional agentic tool loops. Floating navigation and coach input receive inset-aware layout updates, with supporting tests and refreshed code-map metadata.

Changes

Coach inference and UI

Layer / File(s) Summary
Coach-tab floating navigation spacing
lib/app/shell_page.dart, lib/core/theme/layout.dart, lib/features/coach_chat/presentation/..., lib/shared/widgets/kynos_floating_nav.dart, CODEMAP.md
Adds coach-specific insets and replaces the expanded vertical navigation menu with orbit-based positioning and animation.
Prompt shaping and response truncation
lib/domain/utils/coach_context_formatter.dart, lib/domain/utils/coach_prompt_truncator.dart, lib/domain/utils/gemma_inference_limits.dart, test/domain/utils/*
Preserves question blocks during prompt truncation, removes whole context sections at limits, raises output budgets, and tests truncation heuristics.
Tier-aware Gemma runtime flow
lib/infrastructure/ai/gemma/*, lib/infrastructure/ai/isolate_ai_coach_repository.dart, lib/shared/providers/gemma_tier_provider.dart
Passes inference tiers and output budgets through request construction, applies tier-specific instructions, and streams continuations for likely truncated responses.
Tier-aware agentic tool loop
lib/features/coach_chat/providers/*, test/features/coach_chat/*, CODEMAP.md
Enables or disables tool loops from backend and device tier, updates step limits and final-attempt handling, and refreshes generated code-map metadata.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CoachChat
  participant IsolateAiCoachRepository
  participant AiIsolateEntrypoint
  participant GemmaRuntime
  CoachChat->>IsolateAiCoachRepository: submit tier-aware coach prompt
  IsolateAiCoachRepository->>AiIsolateEntrypoint: send AiChatRequest with output budget
  AiIsolateEntrypoint->>GemmaRuntime: stream response
  GemmaRuntime-->>AiIsolateEntrypoint: response chunks
  AiIsolateEntrypoint-->>CoachChat: forward response chunks
  AiIsolateEntrypoint->>GemmaRuntime: request continuation when response appears truncated
Loading

Possibly related PRs

Suggested labels: released

Poem

I’m a rabbit with prompts in my den,
Whole questions come round again.
Gemma streams bright,
Tools loop just right,
While floating menus arc in a glen.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, conventional, and accurately summarizes the main changes to coach quality and the orbiting shell FAB.
Description check ✅ Passed The summary and validation sections are filled out, but the checklist and agent notes sections from the template are not completed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

cursoragent and others added 2 commits July 13, 2026 17:07
Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
@YKDBontekoe
YKDBontekoe marked this pull request as ready for review July 13, 2026 17:15

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
test/features/coach_chat/coach_chat_agentic_tool_test.dart (1)

40-42: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test for the constrained-tier tool-loop-disabled path.

All tests override gemmaInferenceTierProvider to GemmaInferenceTier.full, which enables the agentic tool loop. The PR objective explicitly adds "skipped agentic tool loops on constrained devices," but no test verifies that GemmaInferenceTier.constrained disables the loop. A model TOOL_CALL response on a constrained device should degrade to a direct answer without executing tools.

🧪 Suggested constrained-tier test
test('skips tool loop on constrained tier and answers directly', () async {
  final fakeAi = _ScriptedAgenticAiCoachRepository(
    scriptedTurns: [
      'TOOL_CALL: {"name":"get_recent_runs","arguments":{"limit":2}}',
    ],
  );
  final prefs = await SharedPreferences.getInstance();
  final container = ProviderContainer(
    overrides: [
      sharedPreferencesProvider.overrideWithValue(prefs),
      healthRepositoryProvider.overrideWithValue(_FakeHealthRepository()),
      chatAiCoachRepositoryProvider.overrideWithValue(fakeAi),
      gemmaInferenceTierProvider.overrideWith(
        (ref) async => GemmaInferenceTier.constrained,
      ),
    ],
  );
  await container
      .read(coachConversationsProvider.notifier)
      .ensureActiveConversation();
  await container.read(coachChatProvider.future);
  addTearDown(container.dispose);

  await container
      .read(coachChatProvider.notifier)
      .sendMessage('How is my training?');

  final assistant = container.read(coachChatProvider).value!.last;
  // Tool loop is disabled on constrained tier — no tool steps should execute.
  expect(assistant.toolSteps, isNull);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/features/coach_chat/coach_chat_agentic_tool_test.dart` around lines 40 -
42, Add a test in the coach chat agentic tool tests that overrides
gemmaInferenceTierProvider to GemmaInferenceTier.constrained, supplies a
scripted TOOL_CALL response, sends a user message, and verifies the assistant
returns directly without tool steps or tool execution. Keep the existing
full-tier tests unchanged and dispose the ProviderContainer after the test.
test/domain/utils/coach_prompt_truncator_test.dart (1)

1-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add edge-case tests for uncovered branches.

The test file covers the happy paths but misses several branches in truncateCoachPrompt and coachResponseLooksTruncated:

  • truncateCoachPrompt when the question marker is absent (falls back to raw substring).
  • truncateCoachPrompt when the question block itself exceeds maxChars (returns truncated question).
  • coachResponseLooksTruncated with (Unicode ellipsis) — the only reachable check on line 43 of the truncator.

These are simple additions that would lock in the fallback behavior.

🧪 Suggested additional tests
test('truncates to maxChars when question marker is absent', () {
  final prompt = 'x' * 3000;
  final truncated = truncateCoachPrompt(prompt);
  expect(truncated.length, lessThanOrEqualTo(GemmaInferenceLimits.maxPromptCharacters));
  expect(truncated, isNot(contains('Person')));
});

test('truncates question block itself when it exceeds maxChars', () {
  final longQuestion = 'Person’s question: ' + 'x' * 3000;
  final truncated = truncateCoachPrompt(longQuestion);
  expect(truncated.length, lessThanOrEqualTo(GemmaInferenceLimits.maxPromptCharacters));
});

test('returns false for Unicode ellipsis ending', () {
  expect(
    coachResponseLooksTruncated(
      'Your readiness is low because ' * 30 + '…',
      maxOutputTokens: 256,
    ),
    isFalse,
  );
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/domain/utils/coach_prompt_truncator_test.dart` around lines 1 - 46, Add
edge-case tests in the existing truncateCoachPrompt and
coachResponseLooksTruncated groups: verify prompts without the question marker
use raw max-length truncation, oversized question blocks are truncated to
GemmaInferenceLimits.maxPromptCharacters, and a response ending with the Unicode
ellipsis (… ) is not classified as truncated. Preserve the existing happy-path
tests.
lib/shared/providers/gemma_tier_provider.dart (1)

1-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider generating this provider with @riverpod for convention consistency.

As per coding guidelines, "generate providers with @riverpod plus build_runner" and "use AsyncNotifierProvider for async state." This hand-written FutureProvider works correctly but deviates from the project's provider generation convention. For a simple read-only probe this may be acceptable, but if consistency matters, wrapping it as an @riverpod-generated AsyncNotifierProvider would align with the rest of the codebase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/shared/providers/gemma_tier_provider.dart` around lines 1 - 8, Replace
the hand-written gemmaInferenceTierProvider FutureProvider with an
`@riverpod-generated` provider using the project’s AsyncNotifier convention, while
preserving GemmaRuntimeTier.resolve() as the async value source. Add the
required generator annotation/base implementation and ensure generated provider
output is produced through build_runner.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/features/coach_chat/providers/coach_chat_provider.dart`:
- Around line 246-252: The cached gemmaInferenceTierProvider can make
enableToolLoop use a stale tier; invalidate it before reading at the inference
call site in lib/features/coach_chat/providers/coach_chat_provider.dart lines
246-252, then await the refreshed value before computing enableToolLoop. No
direct change is required in lib/shared/providers/gemma_tier_provider.dart lines
1-8; its existing FutureProvider behavior supports re-resolution after
invalidation.

---

Nitpick comments:
In `@lib/shared/providers/gemma_tier_provider.dart`:
- Around line 1-8: Replace the hand-written gemmaInferenceTierProvider
FutureProvider with an `@riverpod-generated` provider using the project’s
AsyncNotifier convention, while preserving GemmaRuntimeTier.resolve() as the
async value source. Add the required generator annotation/base implementation
and ensure generated provider output is produced through build_runner.

In `@test/domain/utils/coach_prompt_truncator_test.dart`:
- Around line 1-46: Add edge-case tests in the existing truncateCoachPrompt and
coachResponseLooksTruncated groups: verify prompts without the question marker
use raw max-length truncation, oversized question blocks are truncated to
GemmaInferenceLimits.maxPromptCharacters, and a response ending with the Unicode
ellipsis (… ) is not classified as truncated. Preserve the existing happy-path
tests.

In `@test/features/coach_chat/coach_chat_agentic_tool_test.dart`:
- Around line 40-42: Add a test in the coach chat agentic tool tests that
overrides gemmaInferenceTierProvider to GemmaInferenceTier.constrained, supplies
a scripted TOOL_CALL response, sends a user message, and verifies the assistant
returns directly without tool steps or tool execution. Keep the existing
full-tier tests unchanged and dispose the ProviderContainer after the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 39eda044-423c-4a04-8fc0-a55e10505a48

📥 Commits

Reviewing files that changed from the base of the PR and between 472850e and b7ed962.

📒 Files selected for processing (19)
  • CODEMAP.md
  • lib/app/shell_page.dart
  • lib/core/theme/layout.dart
  • lib/domain/utils/coach_context_formatter.dart
  • lib/domain/utils/coach_prompt_truncator.dart
  • lib/domain/utils/gemma_inference_limits.dart
  • lib/features/coach_chat/presentation/pages/coach_chat_page.dart
  • lib/features/coach_chat/presentation/widgets/chat_input_bar.dart
  • lib/features/coach_chat/providers/coach_chat_provider.dart
  • lib/features/coach_chat/providers/coach_chat_provider.g.dart
  • lib/infrastructure/ai/gemma/ai_isolate_entrypoint.dart
  • lib/infrastructure/ai/gemma/ai_isolate_messages.dart
  • lib/infrastructure/ai/gemma/coach_prompt_builder.dart
  • lib/infrastructure/ai/gemma/gemma_inference_session.dart
  • lib/infrastructure/ai/isolate_ai_coach_repository.dart
  • lib/shared/providers/gemma_tier_provider.dart
  • lib/shared/widgets/kynos_floating_nav.dart
  • test/domain/utils/coach_prompt_truncator_test.dart
  • test/features/coach_chat/coach_chat_agentic_tool_test.dart

Comment thread lib/features/coach_chat/providers/coach_chat_provider.dart Outdated
cursoragent and others added 2 commits July 13, 2026 17:30
Invalidate gemmaInferenceTierProvider on each coach message so thermal
throttling cannot leave the agentic loop enabled on a constrained tier.
Add a constrained-tier regression test.

Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
test/features/coach_chat/coach_chat_agentic_tool_test.dart (1)

40-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Constrained-tier test logic is correct; container setup duplicates buildContainer.

The new constrained test correctly validates the tier-gated tool-loop path: with GemmaInferenceTier.constrained, enableToolLoop is false, maxSteps becomes 0, the TOOL_CALL is stripped (leaving empty content), and the resulting StateError produces hasError: true with a single captured user message. All assertions align with the provider logic in coach_chat_provider.dart.

However, lines 115–130 duplicate the entire buildContainer setup with only the tier value differing. Consider parameterizing buildContainer to accept an optional tier (defaulting to GemmaInferenceTier.full) to eliminate the duplication and keep future override changes in one place.

♻️ Proposed refactor: parameterize buildContainer
 Future<ProviderContainer> buildContainer(
   _ScriptedAgenticAiCoachRepository fakeAi, {
-  GemmaInferenceTier tier = GemmaInferenceTier.full,
+  GemmaInferenceTier tier = GemmaInferenceTier.full,
 }) async {
   final prefs = await SharedPreferences.getInstance();
   final container = ProviderContainer(
     overrides: [
       sharedPreferencesProvider.overrideWithValue(prefs),
       healthRepositoryProvider.overrideWithValue(_FakeHealthRepository()),
       chatAiCoachRepositoryProvider.overrideWithValue(fakeAi),
       gemmaInferenceTierProvider.overrideWith(
-        (ref) async => GemmaInferenceTier.full,
+        (ref) async => tier,
       ),
     ],
   );

Then the constrained test becomes:

-    final prefs = await SharedPreferences.getInstance();
-    final container = ProviderContainer(
-      overrides: [
-        sharedPreferencesProvider.overrideWithValue(prefs),
-        healthRepositoryProvider.overrideWithValue(_FakeHealthRepository()),
-        chatAiCoachRepositoryProvider.overrideWithValue(fakeAi),
-        gemmaInferenceTierProvider.overrideWith(
-          (ref) async => GemmaInferenceTier.constrained,
-        ),
-      ],
-    );
-    addTearDown(container.dispose);
-    await container
-        .read(coachConversationsProvider.notifier)
-        .ensureActiveConversation();
-    await container.read(coachChatProvider.future);
+    final container = await buildContainer(
+      fakeAi,
+      tier: GemmaInferenceTier.constrained,
+    );
+    addTearDown(container.dispose);

Also applies to: 109-142

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/features/coach_chat/coach_chat_agentic_tool_test.dart` around lines 40 -
42, Parameterize the existing buildContainer helper to accept an optional
GemmaInferenceTier argument defaulting to GemmaInferenceTier.full, while keeping
its shared provider overrides and setup unchanged. Update the constrained-tier
test to pass GemmaInferenceTier.constrained instead of duplicating the container
construction, and preserve the existing full-tier callers through the default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/features/coach_chat/coach_chat_agentic_tool_test.dart`:
- Around line 40-42: Parameterize the existing buildContainer helper to accept
an optional GemmaInferenceTier argument defaulting to GemmaInferenceTier.full,
while keeping its shared provider overrides and setup unchanged. Update the
constrained-tier test to pass GemmaInferenceTier.constrained instead of
duplicating the container construction, and preserve the existing full-tier
callers through the default.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d7cf752d-b9c4-4dca-ba28-8847b1a3d25d

📥 Commits

Reviewing files that changed from the base of the PR and between b7ed962 and 0ffefd6.

📒 Files selected for processing (2)
  • lib/features/coach_chat/providers/coach_chat_provider.dart
  • test/features/coach_chat/coach_chat_agentic_tool_test.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/features/coach_chat/providers/coach_chat_provider.dart

Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
@YKDBontekoe
YKDBontekoe merged commit f9d4c87 into main Jul 13, 2026
13 checks passed
@YKDBontekoe
YKDBontekoe deleted the cursor/coach-fab-improvements-8833 branch July 13, 2026 17:48
kynos-release-bot Bot pushed a commit that referenced this pull request Jul 13, 2026
# [1.22.0](v1.21.0...v1.22.0) (2026-07-13)

### Features

* improve on-device coach quality and orbiting shell FAB ([#102](#102)) ([f9d4c87](f9d4c87))
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