Skip to content

R2.1.0 - #62

Open
Devasy wants to merge 5 commits into
mainfrom
r2.1.0
Open

R2.1.0#62
Devasy wants to merge 5 commits into
mainfrom
r2.1.0

Conversation

@Devasy

@Devasy Devasy commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Release 2.1.0 Final PR
Add proper changelogs

Summary by CodeRabbit

  • New Features
    • Added a modern floating navigation bar with animated selection states, badges, accessibility support, and scroll-aware visibility.
    • Added consistent styled confirmation dialogs, SnackBars, and glassmorphic text fields.
    • Improved Android release builds with stronger optimization and obfuscation.
  • Bug Fixes
    • Improved Health Connect reliability with timeouts and graceful fallbacks.
    • Fixed readiness messaging when health metrics are incomplete.
    • Improved workout completion navigation and exercise reordering.
  • Tests
    • Expanded automated coverage across screens, services, storage, health data, AI features, and complete user flows.
  • Documentation
    • Updated repository links and added Android signing setup guidance.

Devasy and others added 2 commits July 23, 2026 15:58
* feat: add localized summaries, app metadata, and signing block information to F-Droid repository data

* chore: upgrade Android SDK 16→17, Java 11→17, Gradle/AGP/Kotlin toolchain

- compileSdk + targetSdk: 36 → 37 (Android 17 / API 37)
- Removed compileSdkExtension (not needed for base API 37)
- Java source/target compatibility: VERSION_11 → VERSION_17
- Kotlin jvmTarget: 11 → 17
- Gradle wrapper: 8.12 → 8.14.1
- AGP: 8.9.1 → 8.11.1
- Kotlin Gradle Plugin: 2.1.0 → 2.2.20
- Enable android.builtInKotlin=true + android.newDsl=true
- Remove explicit id(kotlin-android) plugin (now injected by Flutter)

* chore: update pubspec.lock (transitive dependency bumps)

* chore: update repo name and username references to RepForge and Devasy

* upadtes the build gradle kts file to match the review comment

* Adds pubspec yaml

---------

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
* feat: add localized summaries, app metadata, and signing block information to F-Droid repository data

* chore: upgrade Android SDK 16→17, Java 11→17, Gradle/AGP/Kotlin toolchain

- compileSdk + targetSdk: 36 → 37 (Android 17 / API 37)
- Removed compileSdkExtension (not needed for base API 37)
- Java source/target compatibility: VERSION_11 → VERSION_17
- Kotlin jvmTarget: 11 → 17
- Gradle wrapper: 8.12 → 8.14.1
- AGP: 8.9.1 → 8.11.1
- Kotlin Gradle Plugin: 2.1.0 → 2.2.20
- Enable android.builtInKotlin=true + android.newDsl=true
- Remove explicit id(kotlin-android) plugin (now injected by Flutter)

* chore: update pubspec.lock (transitive dependency bumps)

* chore: update repo name and username references to RepForge and Devasy

* upadtes the build gradle kts file to match the review comment

* Adds pubspec yaml

* Enhances the bottom nav bar

* fixes out bulging issue

* Updates the bottom navbar UI, and then adds build size reuction params

* Adds build script and upgrades the release workflow

* Adds tests

* updates acc to review comments

* Adds gitignore and updates codecov yaml

* updated comments according to review comments

---------

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
@Devasy Devasy self-assigned this Jul 23, 2026
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Devasy, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4017e854-9415-412b-ad03-58162ae5dfda

📥 Commits

Reviewing files that changed from the base of the PR and between 39fbb5e and 41513a9.

📒 Files selected for processing (73)
  • .github/workflows/release.yml
  • .gitignore
  • docs/superpowers/specs/2026-08-08-sqlite-migration-and-coach-sql-tool-design.md
  • fastlane/metadata/android/en-US/full_description.txt
  • fdroid/metadata/com.devasy.repforge.yml
  • scripts/patch_so.py
  • workout-logger/lib/data/exercise_database.dart
  • workout-logger/lib/genui/a2ui.dart
  • workout-logger/lib/genui/src/a2ui_node.dart
  • workout-logger/lib/genui/src/a2ui_panels.dart
  • workout-logger/lib/genui/src/a2ui_parser.dart
  • workout-logger/lib/genui/src/a2ui_prompt.dart
  • workout-logger/lib/genui/src/a2ui_props.dart
  • workout-logger/lib/genui/src/a2ui_registry.dart
  • workout-logger/lib/genui/src/a2ui_renderer.dart
  • workout-logger/lib/genui/src/a2ui_series.dart
  • workout-logger/lib/genui/src/a2ui_spec.dart
  • workout-logger/lib/genui/src/a2ui_theme.dart
  • workout-logger/lib/genui/src/components/data_list_group.dart
  • workout-logger/lib/genui/src/components/dynamic_chart.dart
  • workout-logger/lib/genui/src/components/filter_chips.dart
  • workout-logger/lib/genui/src/components/grid_container.dart
  • workout-logger/lib/genui/src/components/metric_gauge.dart
  • workout-logger/lib/genui/src/components/radar_chart.dart
  • workout-logger/lib/genui/src/components/scatter_plot.dart
  • workout-logger/lib/genui/src/components/stat_card.dart
  • workout-logger/lib/genui/src/default_registry.dart
  • workout-logger/lib/main.dart
  • workout-logger/lib/models/models.dart
  • workout-logger/lib/screens/ai_coach_screen.dart
  • workout-logger/lib/screens/widgets/exercise_input_section.dart
  • workout-logger/lib/screens/workout_flow_screen.dart
  • workout-logger/lib/services/ai/coach_tool_service.dart
  • workout-logger/lib/services/ai/gemini_ai_service.dart
  • workout-logger/lib/services/gemini_context_builder.dart
  • workout-logger/lib/services/interfaces/ai_service_interface.dart
  • workout-logger/lib/services/interfaces/ml_service_interface.dart
  • workout-logger/lib/services/managers/pr_manager.dart
  • workout-logger/lib/services/ml_service.dart
  • workout-logger/lib/services/settings_provider.dart
  • workout-logger/lib/services/workout_provider.dart
  • workout-logger/lib/theme/a2ui_app_theme.dart
  • workout-logger/pubspec.yaml
  • workout-logger/scripts/test_gemini_api.py
  • workout-logger/test/ai_coach_view_model_test.dart
  • workout-logger/test/coach_tool_service_test.dart
  • workout-logger/test/gemini_context_builder_test.dart
  • workout-logger/test/genui/a2ui_custom_registry_test.dart
  • workout-logger/test/genui/a2ui_parser_test.dart
  • workout-logger/test/genui/a2ui_prompt_test.dart
  • workout-logger/test/genui/a2ui_props_test.dart
  • workout-logger/test/genui/a2ui_purity_test.dart
  • workout-logger/test/genui/a2ui_registry_test.dart
  • workout-logger/test/genui/a2ui_renderer_test.dart
  • workout-logger/test/genui/a2ui_robustness_test.dart
  • workout-logger/test/genui/a2ui_series_test.dart
  • workout-logger/test/genui/a2ui_theme_test.dart
  • workout-logger/test/genui/components/data_list_group_test.dart
  • workout-logger/test/genui/components/dynamic_chart_test.dart
  • workout-logger/test/genui/components/filter_chips_test.dart
  • workout-logger/test/genui/components/metric_gauge_test.dart
  • workout-logger/test/genui/components/radar_chart_test.dart
  • workout-logger/test/genui/components/scatter_plot_test.dart
  • workout-logger/test/genui/components/stat_card_test.dart
  • workout-logger/test/new_features_test.dart
  • workout-logger/test/routine_optimizer_screen_test.dart
  • workout-logger/test/routine_optimizer_view_model_test.dart
  • workout-logger/test/screens/ai_coach_genui_test.dart
  • workout-logger/test/settings_provider_test.dart
  • workout-logger/test/test_utils/mock_ml_service.dart
  • workout-logger/test/test_utils/test_harness.dart
  • workout-logger/test/userflow_ai_coach_and_gemini_service_test.dart
  • workout-logger/test/userflow_services_and_ai_sweep_test.dart

Walkthrough

Changes

Release and platform configuration

Layer / File(s) Summary
CI and release workflow updates
.codecov.yml, .github/workflows/*, .gitignore, README.md, workout-logger/fastlane/...
CI triggers, action versions, caching, permissions, Codecov settings, signing validation, obfuscated builds, and repository references are updated.
Android toolchain and signing
workout-logger/android/*
Android now targets SDK 37 and Java/Kotlin 17. Gradle tooling, signing fallback properties, shrinking, and ProGuard rules are updated.
Project metadata and release tooling
workout-logger/pubspec.yaml, workout-logger/scripts/build_release.py
Flutter package constraints and versions are updated. A Python script builds an obfuscated ARM64 release APK with split debug information.

Application behavior and UI

Layer / File(s) Summary
Shared UI components and screen integration
workout-logger/lib/screens/widgets/floating_nav_bar.dart, rf_dialogs.dart, rf_widgets.dart, home_screen.dart, edit_workout_session_screen.dart, routine_creator.dart
Floating navigation, reusable SnackBars, confirmation dialogs, and RFTextField are added. Existing screens use the shared components.
Health handling and navigation behavior
workout-logger/lib/services/health_connect_service.dart, readiness_card.dart, health_detail_shell.dart, workout_flow_screen.dart
Health Connect operations use cached connectors and timeouts. Readiness evidence handles missing values. Workout completion replaces the current route.
Routine and import interactions
workout-logger/lib/screens/widgets/routine_creator.dart, workout-logger/lib/screens/programs/import_program_screen.dart
Routine reordering and notifications are updated. File picker configuration no longer sets an explicit single-file option.

Test infrastructure and application coverage

Layer / File(s) Summary
Test harnesses, fixtures, and service stubs
workout-logger/test/test_utils/*
Reusable providers, fixtures, interaction helpers, viewport setup, and Health Connect stubs are added.
Service, storage, and model tests
workout-logger/test/*_test.dart, workout-logger/test/services/*, workout-logger/test/storage_service_test.dart
Tests cover API behavior, logging, Gemini context, Health Connect, settings, storage, and sleep and heart-rate models.
Screen and widget tests
workout-logger/test/screens/*
Widget tests cover application screens, health components, cards, dialogs, reusable widgets, routines, targets, and workout flows.
End-to-end user-flow tests
workout-logger/test/userflow_*
User-flow suites cover AI, health, history, programs, routines, navigation, settings, targets, muscle sheets, and workout logging.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title identifies the release version but does not describe the main changes, such as Android upgrades, workflow updates, navigation enhancements, and expanded tests. Use a concise descriptive title that identifies the R2.1.0 release and its primary changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.90075% with 279 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.79%. Comparing base (7834a50) to head (41513a9).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
...kout-logger/lib/services/ai/gemini_ai_service.dart 16.47% 71 Missing ⚠️
...out-logger/lib/services/ai/coach_tool_service.dart 71.48% 69 Missing ⚠️
...er/lib/screens/widgets/exercise_input_section.dart 19.04% 34 Missing ⚠️
...t-logger/lib/screens/widgets/floating_nav_bar.dart 87.68% 25 Missing ⚠️
...ut-logger/lib/services/health_connect_service.dart 44.82% 16 Missing ⚠️
workout-logger/lib/services/workout_provider.dart 61.90% 16 Missing ⚠️
...ut-logger/lib/screens/widgets/routine_creator.dart 30.00% 7 Missing ⚠️
workout-logger/lib/services/settings_provider.dart 41.66% 7 Missing ⚠️
...orkout-logger/lib/screens/workout_flow_screen.dart 60.00% 6 Missing ⚠️
...-logger/lib/genui/src/components/metric_gauge.dart 94.11% 5 Missing ⚠️
... and 10 more
Additional details and impacted files
@@             Coverage Diff             @@
##             main      #62       +/-   ##
===========================================
+ Coverage   38.50%   75.79%   +37.29%     
===========================================
  Files          85      108       +23     
  Lines       14084    15838     +1754     
===========================================
+ Hits         5423    12005     +6582     
+ Misses       8661     3833     -4828     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

* Adds tests for screens

* Adds tests

* Adds comprehensive tests

* Adds new tests

* Updates test.yml to run on release branches

* Adds test and resolved the warnings and issues

* Updates tests and minor bug fixes

* Adds fixes for failing testsm and adds connection timeout safety for health connector

* Adds missing lines patch

* Updates the tests with analyse failures

* Updates tests and routine creator to use the common component

* Updates flutter version and adds tests

---------

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 38

🤖 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 @.github/workflows/release.yml:
- Line 25: Pin every listed GitHub Action to a full 40-character commit SHA
instead of a mutable tag, preserving the action release name in a trailing
comment. Apply this to .github/workflows/release.yml lines 25-25, 31-31, 37-37,
and 164-164, and .github/workflows/test.yml line 29-29.
- Line 140: Update the release workflow step following the Flutter build command
to persist the generated files from build/app/outputs/symbols in protected
long-term storage before the workflow ends, alongside the existing APK artifacts
without changing the obfuscation build configuration.
- Around line 127-130: Extend the release-signing validation block in the
workflow to check KEYSTORE_BASE64, KEY_STORE_PASSWORD, KEY_ALIAS, and
KEY_PASSWORD before the build. Fail immediately with a clear configuration error
if any required secret is empty, preventing the Gradle release task from
proceeding with debug signing.

In @.github/workflows/test.yml:
- Around line 68-69: Remove the empty line immediately following the slug entry
in the workflow YAML, leaving the slug configuration and surrounding formatting
otherwise unchanged.
- Around line 42-44: Update the condition guarding the flutter pub get step to
use steps.flutter-action.outputs.PUB-CACHE-HIT rather than CACHE-HIT, so
dependency resolution runs when the project pub cache was not restored while
preserving the existing working-directory and command.

In `@workout-logger/android/app/build.gradle.kts`:
- Line 14: Align Android SDK targets with the pinned AGP 8.11.1 by changing
compileSdk and targetSdk in the Android build configuration to 36. Do not target
API 37 unless you also upgrade the AGP version in the settings plugin
declaration and the Gradle wrapper to their compatible versions.
- Around line 2-3: Update the properties-loading logic to use a scoped stream
around keyPropertiesFile.inputStream(), ensuring the stream closes after
Properties.load completes, and remove the unused FileInputStream import.
- Around line 44-48: The release signing setup around keystorePath, storePass,
alias, and keyPass must fail closed when any signing input is missing or empty,
including validating the decoded keystore file before building. Treat blank
environment variables as unavailable, require non-empty values for all four
inputs, and only allow the debug signing fallback when an explicit local-only
property is enabled; do not infer local mode from missing environment variables.

In `@workout-logger/android/gradle.properties`:
- Around line 4-7: Keep the Android toolchain consistent with the built-in
Kotlin/new DSL flags: either upgrade Flutter to 3.47+ and AGP to 9+ across
workout-logger/android/gradle.properties lines 4-7 and
workout-logger/android/app/build.gradle.kts lines 5-10 and 22-25, or disable
those flags and retain the existing legacy Kotlin Gradle Plugin configuration at
all listed sites.

In `@workout-logger/lib/screens/widgets/floating_nav_bar.dart`:
- Around line 562-580: Add a badgeColor field to FloatingNavBarTheme and update
the hasBadge indicator in the floating navigation bar to use that theme value,
falling back to cs.error when unset instead of hardcoding Colors.red. Preserve
the existing chipBg border styling.
- Around line 263-285: Update the bottom padding calculation in the floating
navigation bar to use MediaQuery.paddingOf(context) and add bottomPad to
theme.bottomMargin rather than choosing between them. Preserve the documented
padding.bottom + theme.navHeight + theme.bottomMargin spacing across devices.
- Around line 697-717: The _handleScrollNotification method currently responds
to nested and horizontal scroll updates. Restrict its ScrollUpdateNotification
handling to notifications with depth 0 and vertical axis, while preserving the
existing visibility threshold and top-of-scroll behavior for accepted
notifications.
- Around line 470-479: Update the tab widget’s Semantics/GestureDetector
interaction so it exposes an explicit onTap semantics action and supports
keyboard and switch-access focus and activation. Prefer replacing
GestureDetector with InkWell while preserving the existing haptic feedback and
widget.onTap behavior; if InkWell’s splash is unsuitable for the glassmorphic
chip, retain GestureDetector and add a Semantics onTap plus a Focus wrapper.
- Around line 725-763: Wrap the animated nav bar subtree beginning with
AnimatedSlide in Positioned(left: 0, right: 0, bottom: 0) within the Stack,
keeping the existing AnimatedSlide, AnimatedOpacity, IgnorePointer, and
FloatingNavBar behavior unchanged. This must let FloatingNavBar shrink-wrap to
the pill height so slide offsets are relative to the nav bar rather than the
full Scaffold body.

In `@workout-logger/lib/screens/widgets/rf_dialogs.dart`:
- Around line 20-41: Update the RFSnackBarType.success and RFSnackBarType.error
branches to use WCAG AA-compliant foreground/background pairs: switch success to
a dark foreground on AppColors.success, and darken the error
foreground/background combination as needed. Replace the existing inaccurate
contrast comments with comments reflecting the validated ratios, while leaving
warning and info unchanged.

In `@workout-logger/lib/screens/widgets/rf_widgets.dart`:
- Around line 904-926: Update RFTextField to use an int? maxLines while
preserving the default of 1, and add an optional FocusNode parameter. Reuse the
supplied focus node for the TextField; otherwise create and retain an internal
node, disposing it only when RFTextField created it.

In `@workout-logger/lib/screens/widgets/routine_creator.dart`:
- Around line 431-435: Update the routine save error handling around the mounted
context.showRFSnackBar call to avoid interpolating the raw exception into the
user-facing message. Log the exception with debugPrint, then show a generic
save-failure SnackBar message, matching the pattern used in
edit_workout_session_screen.dart.
- Line 147: Move the “Add Exercises” action from the item list into the
ReorderableListView.builder footer. Update itemCount to represent only
_selectedIds, remove the itemBuilder branch that renders the button at
_selectedIds.length, and adjust the reorder bounds guard to match the reduced
item range.

In `@workout-logger/lib/services/health_connect_service.dart`:
- Around line 65-73: Update _getConnector to cache the in-flight connector
creation Future so concurrent callers await the same HealthConnector.create
operation. Return the resolved connector for every caller, and clear the cached
Future only when initialization fails; preserve the existing timeout and debug
logging behavior.
- Around line 328-334: Store an hcClientRecordVersion field on WorkoutSession,
initialize it for new sessions, and increment it in
WorkoutProvider.updateWorkoutSession whenever any Health Connect-recordable
field changes. Use the current version when constructing the Health Connect
record metadata in the sync flow, while preserving hcSyncedAt solely as the
synchronization marker.

In `@workout-logger/scripts/build_release.py`:
- Around line 33-37: Update the subprocess invocation in the release-build flow
to pass the Flutter command as an argument list rather than a shell-parsed
string, and remove the shell=True usage from subprocess.run. Preserve the
existing environment and result-code propagation through sys.exit.

In `@workout-logger/test/debug_log_buffer_test.dart`:
- Around line 25-56: Update the affected tests around DebugLogBuffer.attach and
buffer.clear to store each callback passed to buffer.addListener, then remove
those same callbacks with buffer.removeListener during test cleanup. Ensure
listener cleanup runs for both tests and preserves their existing assertions.

In `@workout-logger/test/gemini_context_builder_test.dart`:
- Around line 35-86: The test exposes incorrect singular grammar in
GeminiContextBuilder.buildWeeklyInsightsContext. Update the formatter to use
“session” for a count of one and “set” for a single set, while preserving plural
forms for larger counts; then change the corresponding test expectations from “1
sessions” and “1×sets” to “1 session” and “1×set”.

In `@workout-logger/test/screens/edit_workout_session_screen_test.dart`:
- Around line 54-61: Update the add-set and delete-set assertions in the workout
session tests to require exactly one close-icon change per tap, replacing the
greater-than comparison with an assertion that the updated count equals the
initial count plus one and applying the corresponding minus-one assertion to the
delete flow.

In `@workout-logger/test/screens/heart_rate_detail_screen_test.dart`:
- Around line 18-23: Update the granularity-control assertion in the heart rate
detail screen test to require both `Day` and `Week` controls, replacing the
combined OR condition with separate expectations for each text.

In `@workout-logger/test/screens/history_screen_test.dart`:
- Around line 35-40: Replace the broad text searches in the empty-history
assertion with an exact empty-state label or dedicated widget key from the
history screen. Update the assertion around the empty-history test so it only
passes when that specific indicator is present.

In `@workout-logger/test/screens/programs/programs_screens_test.dart`:
- Around line 46-76: Strengthen the interaction tests with postcondition
assertions: in
workout-logger/test/screens/programs/programs_screens_test.dart:46-76, assert
the successful validation result after tapping Validate; in
workout-logger/test/screens/widgets/routine_creator_test.dart:26-36, require an
available exercise option and assert the selected exercise or saved routine
exists; in workout-logger/test/screens/widgets/targets_tab_test.dart:51-60,
assert a stable identifier for target_1, such as its exact exercise label or
widget key.

In `@workout-logger/test/screens/widgets/editable_exercise_card_full_test.dart`:
- Around line 21-31: Replace the consecutive DateTime.now() values in the
EditableSet fixtures within EditableExerciseCard with fixed, distinct timestamp
values, ensuring each sibling set has a unique deterministic ValueKey.
- Around line 63-68: Make both required test interactions fail when unavailable:
in workout-logger/test/screens/widgets/editable_exercise_card_full_test.dart
lines 63-68, assert the “+ Add Set” finder with findsOneWidget before tapping
it, then retain the setAdded assertion; in
workout-logger/test/screens/ai_coach_screen_full_test.dart lines 72-76,
configure input so the send control is enabled, assert the send icon exists, and
tap it without conditionally skipping the action.

In `@workout-logger/test/screens/widgets/health_cards_test.dart`:
- Around line 132-135: Update the card interaction tests around SleepHrCard and
HeartRateCard to assert their actual outcomes, not only tester.takeException().
After tapping SleepHrCard, verify its expected sheet is displayed; after tapping
HeartRateCard, verify navigation reaches the expected destination screen. Retain
the exception checks only as supplementary assertions.

In `@workout-logger/test/screens/widgets/health_widgets_test.dart`:
- Around line 74-95: Extend the HealthDetailShell test around the existing
currentG and onGranularityChanged setup to tap an alternate granularity option,
then assert that currentG changes from HealthGranularity.day to the selected
value. Keep the existing rendering and exception assertions intact.

In `@workout-logger/test/screens/widgets/workout_hr_section_test.dart`:
- Around line 75-78: Update the test around WorkoutHrSection to assert a
user-visible heart-rate statistic derived from analysis, such as the expected
average or peak BPM, in addition to verifying the widget exists. Use the test’s
existing analysis data and appropriate finder or text assertion so the test
detects missing computed values.

In `@workout-logger/test/screens/workout_flow_screen_full_test.dart`:
- Around line 44-53: Strengthen the workout-flow scenarios in
workout-logger/test/screens/workout_flow_screen_full_test.dart: lines 44-53 must
seed an exercise, require LOG SET, and verify the set was logged; lines 73-82
must likewise require and verify logging in the routine-backed flow; lines
110-118 must assert an observable deload-specific target or configuration
instead of only checking WorkoutFlowScreen visibility.

In `@workout-logger/test/services/health_connect_service_test.dart`:
- Around line 140-156: Update the test around HealthConnectService.isAvailable
to use a successful delayed platform response after 200ms and expect available
to be true, verifying the delayed success path within the deadline. If the null
response must remain, rename the test to describe unavailable-platform behavior
instead.

In `@workout-logger/test/test_utils/test_harness.dart`:
- Around line 82-86: Update the teardown callback registered by addTearDown to
be async and await tester.binding.setSurfaceSize(null), while preserving the
existing physical-size and device-pixel-ratio resets.

In `@workout-logger/test/test_utils/test_robot.dart`:
- Line 16: Update the TestRobot constructor to be const, preserving its existing
tester initialization and final-field behavior.

In `@workout-logger/test/userflow_history_and_session_details_test.dart`:
- Around line 73-75: Update the test setup around tester.view.devicePixelRatio
to register tester.view.resetDevicePixelRatio with addTearDown alongside
resetPhysicalSize, ensuring the binding restores both view properties after the
test.

In `@workout-logger/test/userflow_program_design_and_generator_test.dart`:
- Around line 55-67: Make the required UI interactions and outcome assertions
mandatory across the user-flow tests: in
workout-logger/test/userflow_program_design_and_generator_test.dart#L55-L67,
require both TextField controls and Save Program, then verify the entered
program persisted; at `#L70-L106`, load and open the program created through the
UI instead of directly saving sampleProgram; at `#L120-L135`, require the
suggestion and generate controls and assert the expected API-configuration
result. In workout-logger/test/userflow_screens_sweep_test.dart#L149-L163,
assert the required field and actions before invoking the sweep helper. In
workout-logger/test/userflow_targets_and_muscle_sheets_full_test.dart#L138-L154,
assert the required builder controls and verify a persisted or rendered result
after saving.
🪄 Autofix

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 20f22fbd-fd47-436c-94fe-6bbd6251f463

📥 Commits

Reviewing files that changed from the base of the PR and between 5624818 and 39fbb5e.

⛔ Files ignored due to path filters (1)
  • workout-logger/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (77)
  • .codecov.yml
  • .github/workflows/release.yml
  • .github/workflows/test.yml
  • .gitignore
  • README.md
  • workout-logger/.gitignore
  • workout-logger/android/app/build.gradle.kts
  • workout-logger/android/app/proguard-rules.pro
  • workout-logger/android/gradle.properties
  • workout-logger/android/gradle/wrapper/gradle-wrapper.properties
  • workout-logger/android/key.properties.example
  • workout-logger/android/settings.gradle.kts
  • workout-logger/fastlane/metadata/android/en-US/full_description.txt
  • workout-logger/lib/screens/edit_workout_session_screen.dart
  • workout-logger/lib/screens/home_screen.dart
  • workout-logger/lib/screens/programs/import_program_screen.dart
  • workout-logger/lib/screens/widgets/floating_nav_bar.dart
  • workout-logger/lib/screens/widgets/health_detail_shell.dart
  • workout-logger/lib/screens/widgets/readiness_card.dart
  • workout-logger/lib/screens/widgets/rf_dialogs.dart
  • workout-logger/lib/screens/widgets/rf_widgets.dart
  • workout-logger/lib/screens/widgets/routine_creator.dart
  • workout-logger/lib/screens/workout_flow_screen.dart
  • workout-logger/lib/services/health_connect_service.dart
  • workout-logger/pubspec.yaml
  • workout-logger/scripts/build_release.py
  • workout-logger/test/api_service_test.dart
  • workout-logger/test/debug_log_buffer_test.dart
  • workout-logger/test/gemini_context_builder_test.dart
  • workout-logger/test/screens/ai_coach_screen_full_test.dart
  • workout-logger/test/screens/ai_coach_screen_test.dart
  • workout-logger/test/screens/ai_program_generator_screen_test.dart
  • workout-logger/test/screens/edit_workout_session_screen_test.dart
  • workout-logger/test/screens/heart_rate_detail_screen_test.dart
  • workout-logger/test/screens/history_screen_test.dart
  • workout-logger/test/screens/home_screen_test.dart
  • workout-logger/test/screens/onboarding_screen_test.dart
  • workout-logger/test/screens/profile_screen_full_test.dart
  • workout-logger/test/screens/profile_screen_test.dart
  • workout-logger/test/screens/programs/program_designer_screen_test.dart
  • workout-logger/test/screens/programs/programs_screens_test.dart
  • workout-logger/test/screens/settings_screen_test.dart
  • workout-logger/test/screens/sleep_detail_screen_test.dart
  • workout-logger/test/screens/widgets/editable_exercise_card_full_test.dart
  • workout-logger/test/screens/widgets/health_bar_chart_test.dart
  • workout-logger/test/screens/widgets/health_cards_test.dart
  • workout-logger/test/screens/widgets/health_widgets_test.dart
  • workout-logger/test/screens/widgets/rf_cards_test.dart
  • workout-logger/test/screens/widgets/rf_dialogs_test.dart
  • workout-logger/test/screens/widgets/rf_widgets_test.dart
  • workout-logger/test/screens/widgets/routine_creator_test.dart
  • workout-logger/test/screens/widgets/targets_tab_test.dart
  • workout-logger/test/screens/widgets/workout_hr_section_test.dart
  • workout-logger/test/screens/workout_flow_screen_full_test.dart
  • workout-logger/test/screens/workout_flow_screen_test.dart
  • workout-logger/test/services/health_connect_service_test.dart
  • workout-logger/test/settings_provider_test.dart
  • workout-logger/test/sleep_hr_builder_test.dart
  • workout-logger/test/sleep_hr_models_test.dart
  • workout-logger/test/storage_service_test.dart
  • workout-logger/test/test_utils/stub_health_connect_service.dart
  • workout-logger/test/test_utils/test_fixtures.dart
  • workout-logger/test/test_utils/test_harness.dart
  • workout-logger/test/test_utils/test_robot.dart
  • workout-logger/test/test_utils/test_sweep.dart
  • workout-logger/test/userflow_ai_coach_and_gemini_service_test.dart
  • workout-logger/test/userflow_health_and_profile_screen_test.dart
  • workout-logger/test/userflow_history_and_session_details_test.dart
  • workout-logger/test/userflow_program_design_and_generator_test.dart
  • workout-logger/test/userflow_programs_screen_deep_test.dart
  • workout-logger/test/userflow_routine_creation_test.dart
  • workout-logger/test/userflow_screens_sweep_test.dart
  • workout-logger/test/userflow_services_and_ai_sweep_test.dart
  • workout-logger/test/userflow_settings_and_storage_test.dart
  • workout-logger/test/userflow_targets_and_muscle_sheets_full_test.dart
  • workout-logger/test/userflow_targets_and_muscle_sheets_test.dart
  • workout-logger/test/userflow_workout_logging_test.dart
💤 Files with no reviewable changes (1)
  • workout-logger/lib/screens/programs/import_program_screen.dart

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Workflow relevant lines:"
for f in .github/workflows/release.yml .github/workflows/test.yml; do
  if [ -f "$f" ]; then
    echo "--- $f"
    grep -n 'uses:\|pin|full commit|commit hash' "$f" || true
  fi
done

echo
echo "All action refs in workflows:"
python3 - <<'PY'
import pathlib, re
for p in sorted(pathlib.Path('.github/workflows').glob('*.yml')) + sorted(pathlib.Path('.github/workflows').glob('*.yaml')):
    for i,line in enumerate(p.read_text().splitlines(),1):
        m=re.search(r'uses:\s*([^#\s]+)\s*#?\s*(.*)', line.strip())
        if m:
            print(f"{p}:{i}: {m.group(1)}")
PY

Repository: Devasy/RepForge

Length of output: 1190


🌐 Web query:

GitHub Actions recommended secure workflow pinning commit SHA version tags

💡 Result:

To secure GitHub Actions workflows against supply chain attacks, GitHub and security experts strongly recommend pinning actions to a full-length (40-character) commit SHA [1][2][3][4]. Why Pinning to Commit SHAs is Necessary Standard version tags (e.g., @v4) are mutable references [3][4][5]. If an action's repository is compromised, an attacker can overwrite these tags to point to malicious code, which your workflows would then automatically execute [6][3][4][7]. Because Git tags can be moved or deleted, they do not guarantee that the code running today is the same code you audited previously [8][5][9]. A full-length commit SHA, by contrast, is a unique, immutable cryptographic digest [6][8][9]. Pinning to a specific SHA ensures your workflow always executes the exact code you intended, preventing silent, malicious updates [6][4][5][9]. Recommended Implementation To maintain both security and ease of maintenance, follow these best practices: 1. Use Full-Length SHAs: Always use the 40-character full commit SHA. Never use shortened SHAs, as they are susceptible to collision attacks [1][2][8]. 2. Keep Human-Readable References: Include the version tag as a trailing comment in your workflow file (e.g., uses: actions/checkout@a1b2c3d4e5f6... # v4.2.0) [3][4][9]. This allows automated dependency management tools like Dependabot and Renovate to detect new versions and open pull requests, ensuring you can update your actions securely while maintaining immutability [3][4][7]. 3. Enforce via Policy: If you are an administrator, you can enforce this security standard across your organization or enterprise by enabling the GitHub Actions policy that requires actions to be pinned to a full-length commit SHA [6][2][5]. When enabled, any workflow using a mutable reference (tags or branches) will fail [6][5]. Security Note: While SHA pinning prevents tag-retargeting attacks, it does not protect against code that was already malicious at the time of the initial pin [7]. Always continue to vet the actions and authors you choose to include in your pipelines [10][11].

Citations:


🌐 Web query:

actions/checkout release v5 v4 tag commit SHA

💡 Result:

In the actions/checkout repository, both v4 and v5 are major release versions [1][2]. The transition from v4 to v5 primarily involved updating the underlying Node.js runtime from Node.js 20 to Node.js 24 [3][4][5]. This update requires the use of GitHub Actions runners with version v2.327.1 or higher [5]. Regarding the use of "tag" or "commit SHA" with these versions: 1. Usage: You can reference any specific version (v4, v5, v6, v7, etc.) in your workflow file using the syntax actions/checkout@v [4][6][2]. 2. Specifying Ref/SHA: Regardless of the action version used (@v4, @v5, etc.), you can checkout a specific branch, tag, or commit SHA by using the ref input [2][7]. Example: - uses: actions/checkout@v5 with: ref: By default, the action checks out the ref or SHA that triggered the workflow [5][2]. Specifying a ref in the with block allows you to override this default behavior [2][7].

Citations:


Pin the remaining GitHub Actions to full commit hashes.

The listed mutable action tags can move or be replaced, changing pipeline behavior without a repository commit. Pin each action to a full 40-character commit SHA and keep the release name in a trailing comment.

🧰 Tools
🪛 zizmor (1.29.0)

[error] 25-25: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

📍 Affects 2 files
  • .github/workflows/release.yml#L25-L25 (this comment)
  • .github/workflows/release.yml#L31-L31
  • .github/workflows/release.yml#L37-L37
  • .github/workflows/release.yml#L164-L164
  • .github/workflows/test.yml#L29-L29
🤖 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 @.github/workflows/release.yml at line 25, Pin every listed GitHub Action to
a full 40-character commit SHA instead of a mutable tag, preserving the action
release name in a trailing comment. Apply this to .github/workflows/release.yml
lines 25-25, 31-31, 37-37, and 164-164, and .github/workflows/test.yml line
29-29.

Source: Linters/SAST tools

Comment on lines +127 to +130
if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then
echo "Error: KEYSTORE_BASE64 secret is not configured in repository secrets."
exit 1
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Validate all release-signing secrets before the build.

Lines 127-130 validate only KEYSTORE_BASE64. The Gradle signing contract also requires KEY_STORE_PASSWORD, KEY_ALIAS, and KEY_PASSWORD. If any of these values are missing, Gradle falls back to the debug key and the workflow can publish debug-signed APKs as a release.

Proposed fix
       - name: Decode release keystore
+        env:
+          KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
+          KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
+          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
+          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
         run: |
-          if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then
-            echo "Error: KEYSTORE_BASE64 secret is not configured in repository secrets."
-            exit 1
-          fi
-          echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > /tmp/repforge-release.jks
+          : "${KEYSTORE_BASE64:?KEYSTORE_BASE64 is not configured}"
+          : "${KEY_STORE_PASSWORD:?KEY_STORE_PASSWORD is not configured}"
+          : "${KEY_ALIAS:?KEY_ALIAS is not configured}"
+          : "${KEY_PASSWORD:?KEY_PASSWORD is not configured}"
+          printf '%s' "$KEYSTORE_BASE64" | base64 --decode > /tmp/repforge-release.jks
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then
echo "Error: KEYSTORE_BASE64 secret is not configured in repository secrets."
exit 1
fi
- name: Decode release keystore
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
: "${KEYSTORE_BASE64:?KEYSTORE_BASE64 is not configured}"
: "${KEY_STORE_PASSWORD:?KEY_STORE_PASSWORD is not configured}"
: "${KEY_ALIAS:?KEY_ALIAS is not configured}"
: "${KEY_PASSWORD:?KEY_PASSWORD is not configured}"
printf '%s' "$KEYSTORE_BASE64" | base64 --decode > /tmp/repforge-release.jks
🧰 Tools
🪛 zizmor (1.29.0)

[warning] 127-127: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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 @.github/workflows/release.yml around lines 127 - 130, Extend the
release-signing validation block in the workflow to check KEYSTORE_BASE64,
KEY_STORE_PASSWORD, KEY_ALIAS, and KEY_PASSWORD before the build. Fail
immediately with a clear configuration error if any required secret is empty,
preventing the Gradle release task from proceeding with debug signing.

KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: flutter build apk --release --split-per-abi
run: flutter build apk --release --split-per-abi --obfuscate --split-debug-info=build/app/outputs/symbols

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)release\.yml$|workflow|flutter|build|artifact|symbol|crash' || true

echo
echo "== release workflow around build/artifact/release =="
if [ -f .github/workflows/release.yml ]; then
  nl -ba .github/workflows/release.yml | sed -n '1,240p'
fi

echo
echo "== search for build/app/outputs/symbols and symbol upload mentions =="
rg -n "split-debug-info|build/app/outputs/symbols|flutter symbol|firebase crash|sentry|crash" .github . -g '!**/*.png' -g '!**/*.svg' || true

echo
echo "== workflow uses/references related to symbols/artifacts =="
rg -n "upload-artifact|download-artifact|split-debug-info|split-per-abi|obfuscate" . -g '*.yml' -g '*.yaml' || true

Repository: Devasy/RepForge

Length of output: 840


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release workflow lines =="
if [ -f .github/workflows/release.yml ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/release.yml | sed -n '1,240p'
fi

echo
echo "== search for symbol-related references =="
rg -n "split-debug-info|build/app/outputs/symbols|flutter symbol|firebase crash|sentry|crash|upload-artifact|download-artifact|split-per-abi|obfuscate" .github workout-logger scripts . -g '*.yml' -g '*.yaml' -g '*.py' -g '*.gradle*' -g '*.dart' -g '!**/*.png' -g '!**/*.svg' || true

Repository: Devasy/RepForge

Length of output: 10250


🌐 Web query:

Flutter build apk --split-debug-info output location documentation

💡 Result:

When running a Flutter build with the --split-debug-info flag, the output location for debug symbols is determined by the directory path you explicitly provide as an argument to the flag [1][2]. The flag syntax is: flutter build --split-debug-info=/ [1][2] Key details regarding the output location: - User-Defined Path: You specify the directory path where Flutter should save the symbol files [1][2]. For example, using --split-debug-info=out/android will create a directory named out/android (relative to your project root) and place the generated symbol files there [3][2]. - Not Automatic: Flutter does not place these symbols in a default build folder unless you explicitly direct it to do so (e.g., --split-debug-info=build/app/outputs/symbols) [3]. - Purpose: This command generates symbol map files (e.g., app.android-arm.symbols), which are essential for de-obfuscating stack traces from crash reports [1][4][5]. - Persistence: It is recommended to store these symbols outside of ephemeral build directories (like the standard build/ folder), as they may be cleared by a flutter clean command [4]. Developers often commit these files to version control or an external symbol storage service to ensure they are available for future debugging sessions [4].

Citations:


Persist the obfuscation symbol files after the APK builds.

Line 140 writes split-debug-info files to build/app/outputs/symbols, but the following steps only upload and release APK files. Upload or store those symbol files in protected long-term storage before the workflow ends.

🤖 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 @.github/workflows/release.yml at line 140, Update the release workflow step
following the Flutter build command to persist the generated files from
build/app/outputs/symbols in protected long-term storage before the workflow
ends, alongside the existing APK artifacts without changing the obfuscation
build configuration.

Comment on lines +42 to 44
if: steps.flutter-action.outputs.CACHE-HIT != 'true'
working-directory: ./workout-logger
run: flutter pub get

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,140p' .github/workflows/test.yml 2>/dev/null || true

echo
echo "== related files =="
git ls-files | rg '(^|/)(pubspec\.yaml|\.dart_tool/package_config\.json|workflows/test\.yml)$' || true

echo
echo "== pubspec/package_config references =="
rg -n "pubspec|package_config|flutter pub get|CACHE-HIT|flutter-analyz|flutter test" .github workout-logger pubspec.yaml 2>/dev/null || true

echo
echo "== git status =="
git status --short

Repository: Devasy/RepForge

Length of output: 3559


🌐 Web query:

subosito flutter-action flutter analyze test cache true outputs CACHE-HIT

💡 Result:

The subosito/flutter-action GitHub Action provides CACHE-HIT and PUB-CACHE-HIT outputs that allow you to determine whether the Flutter SDK or pub dependencies were successfully retrieved from the cache [1][2][3]. These outputs are available starting from version v2.21.0 [2]. They directly reflect the cache-hit output from the underlying actions/cache steps [4][5][3]. Key details regarding these outputs: - CACHE-HIT: Indicates whether the Flutter SDK cache was a hit [1][3]. - PUB-CACHE-HIT: Indicates whether the pub dependencies cache was a hit [1][3]. - Behavior: These outputs return 'true' if there is an exact match for the cache key, and an empty string if there is a cache miss [4][6][5]. Example usage: You can use these outputs to conditionally execute steps in your workflow, such as skipping dependency installation when a cache hit occurs [7][3]: steps: - name: Set up Flutter uses: subosito/flutter-action@v2 id: flutter-action with: channel: stable cache: true - name: Conditionally run dependencies installation if: steps.flutter-action.outputs.PUB-CACHE-HIT!= 'true' run: flutter pub get - name: Continue with build run: flutter build apk Note that you must provide an id to the flutter-action step (e.g., id: flutter-action) to reference these outputs in subsequent steps [7][3].

Citations:


🌐 Web query:

site:github.com/subosito/flutter-action action.yaml CACHE-HIT PUB-CACHE-HIT cache

💡 Result:

In the context of the subosito/flutter-action GitHub Action, CACHE-HIT and PUB-CACHE-HIT are output variables that provide information about the status of the Flutter SDK and Dart pub dependencies caches, respectively [1][2]. These outputs are defined in the action's configuration (action.yaml) to allow users to conditionally execute steps based on whether a cache was restored successfully [1][2]. Specifically: CACHE-HIT: Indicates whether the Flutter SDK cache was a hit (returns 'true' if there is an exact match for the cache key) [1]. PUB-CACHE-HIT: Indicates whether the Dart pub dependencies cache was a hit (returns 'true' if there is an exact match for the cache key) [1]. These values are sourced directly from the cache-hit output of the underlying actions/cache step used within the action [3][4]. They are commonly used in workflow YAML files to skip redundant installation or setup steps, such as bootstrapping dependencies, when a valid cache already exists [2][5]. Example usage in a workflow: - name: Set up Flutter uses: subosito/flutter-action@v2 id: flutter-action with: cache: true pub-cache: true - name: Conditionally run setup if: steps.flutter-action.outputs.PUB-CACHE-HIT!= 'true' run: dart pub get

Citations:


🌐 Web query:

site:docs.dart.dev packages flutter test analyze package_config.json pub cache

💡 Result:

Relevant official Dart/Flutter documentation:

  • Packages: use pubspec.yaml and dart pub get to resolve dependencies. [1]
  • Flutter package commands: use flutter pub, flutter analyze, and flutter test; Flutter recommends these instead of the equivalent Dart commands in Flutter projects. [2]
  • Static analysis: dart analyze [DIRECTORY|FILE] analyzes Dart source code. [3]
  • Testing: Flutter tests typically live under test/; run them with flutter test. [4]
  • package_config.json: Pub generates .dart_tool/package_config.json, mapping package names to their locations; workspaces use one shared file. [5]
  • Pub cache: recent Dart versions include dart pub cache gc to remove packages no longer referenced by current projects. [6]

The original query returned no exact docs.dart.dev match; current documentation is primarily on dart.dev and docs.flutter.dev.


Run dependency resolution when the Flutter SDK cache hits.

CACHE-HIT reports whether the Flutter SDK cache was restored. This skips flutter pub get even when the project pub cache was not restored, leaving .dart_tool/package_config.json unresolved for flutter analyze and flutter test. Check steps.flutter-action.outputs.PUB-CACHE-HIT instead, or always run flutter pub get for clean runners.

🤖 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 @.github/workflows/test.yml around lines 42 - 44, Update the condition
guarding the flutter pub get step to use
steps.flutter-action.outputs.PUB-CACHE-HIT rather than CACHE-HIT, so dependency
resolution runs when the project pub cache was not restored while preserving the
existing working-directory and command.

Comment on lines +68 to +69
slug: Devasy/RepForge

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the added empty line.

The configured YAML linter rejects the empty line after slug.

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 69-69: too many blank lines (1 > 0)

(empty-lines)

🤖 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 @.github/workflows/test.yml around lines 68 - 69, Remove the empty line
immediately following the slug entry in the workflow YAML, leaving the slug
configuration and surrounding formatting otherwise unchanged.

Source: Linters/SAST tools

Comment on lines +140 to +156
test('HealthConnectService succeeds when platform response takes > 100ms within deadline', () async {
const channel = 'dev.flutter.pigeon.health_connector_hc_android.HealthConnectorHCAndroidApi.getHealthPlatformStatus';
addTearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMessageHandler(channel, null);
});

TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
channel,
(ByteData? message) async {
await Future.delayed(const Duration(milliseconds: 200));
return null;
},
);

final service = HealthConnectService();
final available = await service.isAvailable();
expect(available, isFalse);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test the delayed success path or rename this test.

The handler returns null, and Line 156 expects false. This test does not verify that a delayed successful platform response completes within the deadline. An implementation that always returns false passes this test.

Configure a successful platform response and expect true. If failure is the intended case, rename the test to describe unavailable-platform behavior.

🤖 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 `@workout-logger/test/services/health_connect_service_test.dart` around lines
140 - 156, Update the test around HealthConnectService.isAvailable to use a
successful delayed platform response after 200ms and expect available to be
true, verifying the delayed success path within the deadline. If the null
response must remain, rename the test to describe unavailable-platform behavior
instead.

Comment on lines +82 to +86
addTearDown(() {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
tester.binding.setSurfaceSize(null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect all test surface-size setup and teardown calls.
rg -n -C 3 'setSurfaceSize\(|addTearDown\(' workout-logger/test

Repository: Devasy/RepForge

Length of output: 3600


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate flutter test harness source to inspect setSurfaceSize contract and widget tester method signatures.
rg -n "Future<void>\s+setSurfaceSize|setSurfaceSize\(" --glob '*flutter*' --glob '*test*' /tmp 2>/dev/null || true
python3 - <<'PY'
import subprocess, sys, re, pathlib
try:
    out=subprocess.check_output(["dart", "--version"], text=True, stderr=subprocess.STDOUT)
    print("dart_version", out.strip())
except Exception as e:
    print("dart_unavailable", repr(e))
try:
    out=subprocess.check_output(["flutter", "--version"], text=True, stderr=subprocess.STDOUT)
    print("flutter_version", out.strip())
except Exception as e:
    print("flutter_unavailable", repr(e))
PY

# Inspect TestHarness implementation around prepareTester and any docs.
nl -ba workout-logger/test/test_utils/test_harness.dart | sed -n '1,120p'

Repository: Devasy/RepForge

Length of output: 331


🌐 Web query:

Flutter WidgetTester binding setSurfaceSize Future<void> await signature

💡 Result:

The setSurfaceSize method is a member of the TestWidgetsFlutterBinding class in the flutter_test library [1][2]. Its signature is as follows: Future setSurfaceSize(Size? size) [1][3] This method artificially changes the logical size of the WidgetTester.view to the specified size and flushes microtasks [1][2]. Because it returns a Future, it must be awaited when called within a test [1][3]. Key usage and implementation details include: * Awaiting the call: You should use the await keyword when calling setSurfaceSize to ensure the size change and subsequent microtask flushes are completed before proceeding with the test [1][3]. * State Management: This method affects the state of the WidgetTester.view and can leak across tests if not properly reset [1][3]. It is standard practice to reset the surface size to null (the default) after the test using addTearDown [1][3]. * Example: await binding.setSurfaceSize(someSize); addTearDown( => binding.setSurfaceSize(null)); * Deprecation Warning: Official documentation and framework source code suggest that setSurfaceSize is considered for deprecation [1][4]. It is recommended to consider using TestFlutterView.physicalSize instead, which is a more modern and flexible way to manage view configurations for any view, including the WidgetTester.view [1][3]. Top results: [1], [3], [4]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n workout-logger/test/test_utils/test_harness.dart | sed -n '68,90p'

Repository: Devasy/RepForge

Length of output: 878


Await the setSurfaceSize(null) reset in teardown.

tester.binding.setSurfaceSize returns a Future<void>, and the setup awaits it; call addTearDown with an async callback and await the same reset to keep the fixture restoration consistent.

Proposed fix
-    addTearDown(() {
+    addTearDown(() async {
       tester.view.resetPhysicalSize();
       tester.view.resetDevicePixelRatio();
-      tester.binding.setSurfaceSize(null);
+      await tester.binding.setSurfaceSize(null);
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
addTearDown(() {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
tester.binding.setSurfaceSize(null);
});
addTearDown(() async {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
await tester.binding.setSurfaceSize(null);
});
🤖 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 `@workout-logger/test/test_utils/test_harness.dart` around lines 82 - 86,
Update the teardown callback registered by addTearDown to be async and await
tester.binding.setSurfaceSize(null), while preserving the existing physical-size
and device-pixel-ratio resets.

class TestRobot {
final WidgetTester tester;

TestRobot(this.tester);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the TestRobot constructor const.

The constructor only initializes a final field. Add const to satisfy the Dart constructor guideline.

As per coding guidelines, use const constructors wherever possible.

🤖 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 `@workout-logger/test/test_utils/test_robot.dart` at line 16, Update the
TestRobot constructor to be const, preserving its existing tester initialization
and final-field behavior.

Source: Coding guidelines

Comment on lines +73 to +75
tester.view.physicalSize = const Size(800, 1800);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching target:"
fd -a 'userflow_history_and_session_details_test.dart' . || true

file="$(fd 'userflow_history_and_session_details_test.dart' . | head -n1 || true)"
if [ -n "$file" ]; then
  echo "\nTarget file: $file"
  wc -l "$file"
  echo "\nRelevant lines:"
  cat -n "$file" | sed -n '1,140p'
fi

echo "\nSearch reset device pixel ratio usages:"
rg -n "resetDevicePixelRatio|devicePixelRatio\\s*=|physicalSize\\s*=|resetPhysicalSize" workout-logger/test || true

Repository: Devasy/RepForge

Length of output: 6400


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "history_screen_test.dart relevant setup section:"
cat -n workout-logger/test/screens/history_screen_test.dart | sed -n '1,180p'

echo "\nDart Flutter View reset API availability in installed flutter_test source:"
flutter --version 2>/dev/null || true
for root in "$HOME/flutter/bin/cache/pkg/flutter_test/lib" "/opt/flutter/bin/cache/pkg/flutter_test/lib"; do
  [ -d "$root/src/dart_test" ] && find "$root/src/dart_test" -type f \( -name '*view*.dart' -o -name '*binding*.dart' \) -print | sort
done

echo "\nSearch package source for View.resetDevicePixelRatio:"
find "$PWD" -path '*flutter_test*' -maxdepth 8 -type f -name '*.dart' -print 2>/dev/null | xargs rg -n "resetDevicePixelRatio|class View|devicePixelRatio" 2>/dev/null || true

# Determine current binding type by source presence only.
python3 - <<'PY'
from pathlib import Path
roots=["src/flutter_test","test/flutter_test"]
for r in roots:
    if (Path(r)).exists():
        print(f"FOUND_PREFIX {r}")
PY

Repository: Devasy/RepForge

Length of output: 3951


Reset the test view device pixel ratio.

This helper changes tester.view.devicePixelRatio but only adds teardown for physical size, so tests run in the same binding can inherit 1.0. Add teardown for tester.view.resetDevicePixelRatio.

Proposed fix
       tester.view.physicalSize = const Size(800, 1800);
       tester.view.devicePixelRatio = 1.0;
       addTearDown(tester.view.resetPhysicalSize);
+      addTearDown(tester.view.resetDevicePixelRatio);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tester.view.physicalSize = const Size(800, 1800);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
tester.view.physicalSize = const Size(800, 1800);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
🤖 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 `@workout-logger/test/userflow_history_and_session_details_test.dart` around
lines 73 - 75, Update the test setup around tester.view.devicePixelRatio to
register tester.view.resetDevicePixelRatio with addTearDown alongside
resetPhysicalSize, ensuring the binding restores both view properties after the
test.

Comment on lines +55 to +67
final textFields = find.byType(TextField);
if (textFields.evaluate().length >= 2) {
await tester.enterText(textFields.at(0), 'Strength Block 1');
await tester.enterText(textFields.at(1), '4-week progressive overload');
await tester.pumpAndSettle();
}

// Tap Save Program button
final saveBtn = find.text('Save Program');
if (saveBtn.evaluate().isNotEmpty) {
await tester.tap(saveBtn);
await tester.pumpAndSettle();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make required UI actions mandatory in these user-flow tests.

Each listed test can pass when required controls are missing because it skips interactions when a finder is empty. The designer test then saves sampleProgram directly, so it does not verify that the UI save action persisted the entered program.

  • workout-logger/test/userflow_program_design_and_generator_test.dart#L55-L67: require the fields and Save Program, then assert that the entered program was persisted.
  • workout-logger/test/userflow_program_design_and_generator_test.dart#L70-L106: load and open the program created by the UI instead of saving sampleProgram directly.
  • workout-logger/test/userflow_program_design_and_generator_test.dart#L120-L135: require the suggestion and generate action, then assert the expected API-configuration result.
  • workout-logger/test/userflow_screens_sweep_test.dart#L149-L163: assert the required field and actions before invoking the sweep helper.
  • workout-logger/test/userflow_targets_and_muscle_sheets_full_test.dart#L138-L154: assert required builder controls and a persisted or rendered result after saving.
📍 Affects 3 files
  • workout-logger/test/userflow_program_design_and_generator_test.dart#L55-L67 (this comment)
  • workout-logger/test/userflow_program_design_and_generator_test.dart#L70-L106
  • workout-logger/test/userflow_program_design_and_generator_test.dart#L120-L135
  • workout-logger/test/userflow_screens_sweep_test.dart#L149-L163
  • workout-logger/test/userflow_targets_and_muscle_sheets_full_test.dart#L138-L154
🤖 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 `@workout-logger/test/userflow_program_design_and_generator_test.dart` around
lines 55 - 67, Make the required UI interactions and outcome assertions
mandatory across the user-flow tests: in
workout-logger/test/userflow_program_design_and_generator_test.dart#L55-L67,
require both TextField controls and Save Program, then verify the entered
program persisted; at `#L70-L106`, load and open the program created through the
UI instead of directly saving sampleProgram; at `#L120-L135`, require the
suggestion and generate controls and assert the expected API-configuration
result. In workout-logger/test/userflow_screens_sweep_test.dart#L149-L163,
assert the required field and actions before invoking the sweep helper. In
workout-logger/test/userflow_targets_and_muscle_sheets_full_test.dart#L138-L154,
assert the required builder controls and verify a persisted or rendered result
after saving.

Devasy and others added 2 commits August 15, 2026 21:59
* Adds tests for screens

* Adds tests

* Adds comprehensive tests

* Adds new tests

* Updates test.yml to run on release branches

* Adds test and resolved the warnings and issues

* Updates tests and minor bug fixes

* Adds fixes for failing testsm and adds connection timeout safety for health connector

* Adds missing lines patch

* Updates the tests with analyse failures

* Updates tests and routine creator to use the common component

* Updates flutter version and adds tests

* Adds major genui Feature and renderer

* chore: remove patch_so script

* build: add --build-id=none for jni package in F-Droid metadata

* ci: add jni build-id sed step for future reproducible releases

* feat: assisted pullups, deload-aware ML, handle-scoped PRs, sleeping HR tool

Batches several in-flight features that were sitting uncommitted:

- Bodyweight/assisted pullup volume: (BW - assist + extra) * reps
- MLService reads the past 3 sessions and recovers from a deload week
  using the pre-deload baseline instead of the deload trough
- PRManager scopes records per handle variation (Rope vs Bar)
- CoachToolService.get_sleeping_hr_analytics: p5/p25/mean, stdev,
  variance and linear trend over the last N nights
- GenUI parser tolerates numeric StatCard values, loose trend words and
  Markdown code fences

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add A2UiProps alias-aware coercing property reader

Foundation for the genui refactor: a never-throwing view over raw
component prop maps that resolves keys by exact match, normalized
match (case/underscore/hyphen/space-insensitive), then semantic
alias, and coerces values to typed accessors with documented
fallbacks instead of throwing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add A2UiSpec contract, A2UiNode and A2UiRegistry

Adds the four-in-one component contract (A2UiSpec) that lets each UI
component name itself, parse its own props, build its own widget and
document itself for the LLM prompt on one object, plus the
A2UiRegistry lookup table that replaces the old allowedA2UiComponents
set and two parallel switch statements. Includes an A2UiTheme skeleton
(filled in by Task 4) and A2UiNode, the parsed-tree node type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genui): make A2UiRegistry throw on name/alias collisions

Code review found that A2UiRegistry's constructor loop silently
resolved canonical-name/alias collisions (last-writer-wins for names,
first-writer-wins for aliases), which would produce unreachable specs
or dropped aliases with no signal as more components are registered in
later tasks. The constructor now throws a StateError identifying both
colliding specs for any of: two specs sharing a canonical name, an
alias colliding with another spec's canonical name, or two specs
sharing an alias. Adds three regression tests using a new configurable
_NamedFakeSpec fake.

Also documents (doc-comment only, no behavior change) that
A2UiNode.children is not defensively copied, per the review's Minor
finding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add A2UiParser with fence, envelope and alias repair

Adds the single gate that decides whether an LLM reply is a UI payload
or ordinary prose, and turns UI payloads into an A2UiNode tree. Handles
markdown fences, prose-wrapped JSON, flat vs props-wrapped shapes,
bare-array/envelope auto-wrapping into GridContainer, and recursive
children, without ever throwing.

Also promotes A2UiProps._asStringKeyed to a public static
A2UiProps.stringKeyed so the parser can re-key decoded JSON maps
without an awkward part-of coupling between the two libraries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genui): balanced-bracket JSON extraction and envelope singleton fix

_extractJson previously sliced from the first { to the last }, which
broke on any stray brace in surrounding prose (e.g. "add reps
{optional}"). Replace with a scan that tries jsonDecode on every
balanced {..}/[..] span found via a depth counter that correctly skips
brackets inside string literals, preferring the longest successful
decode as the actual payload.

Also fix _wrap's unconditional single-child collapse: an explicit
envelope key ({"components":[...]}) is a deliberate container request
and must still produce a GridContainer with one child, while a bare
top-level array with one item keeps collapsing since it's ambiguous
between "a list of one" and "just one component."

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): inject A2UiTheme and extract shared panel chrome

Adds A2UiThemeProvider (InheritedWidget, falls back to A2UiTheme.dark)
and the panel/title/empty-state/legend widgets every component spec
will share, plus lib/theme/a2ui_app_theme.dart mapping RepForge's real
design tokens onto A2UiTheme. This is the only file where the two
systems meet - lib/genui/ still imports nothing app-specific.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(genui): strengthen theme-injection and add A2UiPanel coverage

The injection test compared against repforgeA2UiTheme, which is
field-for-field identical to the A2UiThemeProvider.of fallback
(A2UiTheme.dark), so it passed even if the InheritedWidget lookup were
broken. Inject a fixture with distinct values instead, and assert a
sibling context still falls back to the default. Also add direct
coverage for A2UiPanel's padding, decoration, and child rendering,
previously only exercised indirectly via A2UiEmptyPanel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add A2UiSeries as the shared categorical data shape

A2UiSeries.extract() and maxValue() give line/bar/pie and radar chart
components one common {name, values} shape to consume, so a model that
learns {labels, series} once can drive all four components.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genui): cover fallback path in A2UiSeries.extract, fix negative-max bug

Address code review findings on A2UiSeries:
- Add tests pinning down the series->values fallback when every series
  entry drops to empty/unparseable values, and when series is an empty
  list — the risky path the brief called out but left untested.
- Rename the misleading 'reads the axes alias' test; it only exercised
  stringified-number coercion inside series values, not alias resolution.
- Fix maxValue() to track whether any value has been seen instead of
  seeding with 0.0, so all-negative series report their true max
  instead of silently clamping to 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add StatCardSpec with typed props and trend synonyms

Establishes the pattern for Tasks 7-13: a typed props record, an
A2UiSpec bundling name/aliases/doc/parseProps/buildWidget, and
never-throwing parsing that degrades to documented fallbacks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add MetricGaugeSpec with safe progress and null value

Fixes the validator/renderer contradiction where a String value was
accepted but cast to num, and the min == max NaN sweep angle bug.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add DynamicChartSpec for line, bar and pie

Adds the most-used and most complex A2UI component so far, covering
line/bar/pie rendering over the shared {labels, series} shape with
never-throwing prop parsing and label padding to prevent out-of-range
axis lookups.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add ScatterPlotSpec with point repair and safe bounds

Adds paired x/y observation plotting with an optional correlation badge,
following the Task 6-8 A2UiSpec pattern. Malformed points are dropped
rather than throwing, and bounds widen degenerate axes so fl_chart never
sees a zero-span range.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add RadarChartSpec sharing the labels/series shape

Task 10 of the a2ui/genui refactor: RadarChart consumes the same
{labels, series} shape as DynamicChart, with `axes` kept as a
backward-compatible alias for `labels`. Every series is truncated
or zero-padded to labels.length at parse time so fl_chart's radar
never sees a mismatched entry count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add DataListGroupSpec with row repair and optional title

Adds a titled list-of-rows component with a defensive row-extraction
fallback chain: named fields, bare scalars, first-stringifiable-value
fallback, and silent drop of rows with nothing displayable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add FilterChipsSpec with nullable active option

Renders a decorative, non-interactive row of scope chips (e.g. "7d /
30d / 90d") and fixes the old renderer's `activeOption as String`
crash by matching case-insensitively and falling back to null instead
of throwing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): add GridContainerSpec, default registry and renderer

Task 13: assembles all eight leaf components into defaultA2UiRegistry,
adds the GridContainerSpec layout wrapper, the public A2UiRenderer
widget, and the lib/genui/a2ui.dart barrel file that will be the only
import path the rest of the app uses going forward.

fix(genui): make structural children lookup exact, not alias-resolved

Cross-task fix to a2ui_parser.dart (a Task 3 file), discovered during
Task 13 registry integration. A2UiParser._parseChildren and
_declaresChildren resolved the structural `children` key through
A2UiProps' alias-aware lookup(), which treats `items` as an alias for
`children`. That collided with DataListGroupSpec, whose own canonical
data-row key is also `items`: a DataListGroup node's `items` list of
{primaryText, ...} maps was mistaken for child components, none of
them parsed as one, and the whole node was then discarded as an
emptied-out container. Reading the literal `children` key only fixes
this and matches the precision _envelopeKeys already had (it does not
include `items` as a synonym for `children` either).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(genui): generate the A2UI prompt section from the registry

Replaces hand-written component-schema prose in the coach system
prompt with a section generated from defaultA2UiRegistry, so the
vocabulary advertised to the model can never drift from what the
parser/renderer actually support.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(genui): wire coach screen to the A2UI package, drop legacy renderer

Replaces private _CoachMessageContent with a public, stateful
CoachMessageContent that memoizes parsing per text value and shows a
"Building dashboard..." placeholder for partial JSON while streaming,
instead of letting raw braces scroll past or losing prose on a mixed
reply. Wraps the app root in A2UiThemeProvider(theme: repforgeA2UiTheme)
so the renderer picks up RepForge's design tokens. Deletes the
superseded lib/genui/a2ui_component.dart and lib/genui/a2ui_renderer.dart,
and drops test/new_features_test.dart's GenUI Component Resilience Tests
group, whose two cases are already covered more thoroughly by
test/genui/a2ui_parser_test.dart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genui): bracket negative-value ranges in DynamicChart line/bar axes

minY was hardcoded to 0 while maxY derived from the true series max, so an
all-negative dataset (e.g. [-10, -5, -3]) produced a visible axis range of
[0, 1] with every real data point falling outside it — a silent blank
chart despite valid, non-empty data. Adds A2UiSeries.minValue mirroring
the existing maxValue, and a shared _yBounds helper used by both _line and
_bar so the two renderers can't diverge on axis math. Also covers
multi-series label padding, which was previously only exercised through
series[0].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(genui): cover all-negative bounds and malformed point entries

Task 9 review flagged that ScatterPlotProps.bounds had no regression pin
for all-negative-coordinate spreads (same failure class as Task 8's
DynamicChartSpec axis bug) and that point-parsing had no test for
structurally invalid entries (nested objects, raw lists). Adds both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genui): widen per-node children lookup back to components/elements/content

Follow-up to the Task 13 a2ui_parser.dart fix: restricting the per-node
_parseChildren/_declaresChildren lookup to the literal 'children' key
was narrower than intended. It regressed 'components'/'elements'/
'content' as per-node child-list keys, which never collided with
anything (only 'items' did, via DataListGroup's own canonical data key).
A payload like {"component":"GridContainer","props":{"columns":1,
"components":[...]}} resolved fine before the original bug and silently
rendered blank (zero children, no null fallback) after the first fix,
since _declaresChildren no longer recognized 'components' as a
children-declaring key either.

Adds a _childKeys constant (children/components/elements/content,
still excluding items) mirroring _envelopeKeys' existing tolerance, and
routes both _parseChildren and _declaresChildren through a shared
_firstChildList literal (non-alias) lookup over that key set.

Adds regression tests in a2ui_renderer_test.dart: per-node
components/elements/content resolve to real children, and items stays
excluded at the per-node level.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genui): widen looksLikeUi to catch prose-prefixed fences, fix vacuous memoization test

looksLikeUi only checked whether the text, after stripping a *leading*
fence, started with `{`/`[`. A model that writes a sentence before
opening a fenced payload (e.g. "Here is your data:\n```json\n{...")
fell through undetected, so CoachMessageContent showed the raw partial
JSON instead of the streaming placeholder -- the exact symptom this
task exists to fix. Now also treats an unclosed ``` fence found
anywhere in the streamed-so-far text as a UI signal, while plain prose
with no JSON or fence anywhere still returns false.

Also fixes the memoization regression test in
test/screens/ai_coach_genui_test.dart: the second observation was
taken after a bare `tester.pump()`, which doesn't mark the element
dirty and never actually calls build() again, so the test could not
distinguish memoized parsing from a widget that never rebuilds at all.
It now pumps a second CoachMessageContent instance with identical text
at the same tree location, which reuses the existing State and
genuinely triggers didUpdateWidget/build.

Adds regression tests for both the prose-prefixed-fence case and the
plain-prose-no-json case in test/genui/a2ui_parser_test.dart, plus a
widget-level test in test/screens/ai_coach_genui_test.dart confirming
the placeholder (not raw JSON) renders end-to-end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(genui): drop presentation payload from tools, add purity and fuzz suites

The sleeping-HR analytics tool was hand-constructing an A2UI DynamicChart
payload directly, leaking presentation decisions into the data layer.
Replace `genui_chart_props` with neutral `labels`/`series` keys so the
prompt — not the tool — decides how to present the data.

Add two permanent guard suites: a2ui_purity_test.dart proves lib/genui/
never imports app-specific code (theme/models/services/screens) and its
component renderers never cast raw model data; a2ui_robustness_test.dart
fuzzes the parser and renderer against ~26 hostile/malformed LLM payloads
to confirm nothing throws.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genui): depth-agnostic purity regex, pin two silent-visual regressions

Review of the previous commit found the purity test's forbidden-import
check was depth-blind: its literal needle list only covered one and two
../ hops, but components live three levels below lib/, so a real
../../../theme/... import passed undetected. Replace it with a regex
that matches any number of ../ hops (or a package:repforge/ prefix),
covering import and export directives alike, and add a self-test that
proves the regex catches every relevant depth/form without touching real
source files.

Also widen the no-raw-casts check to include bool/Object/dynamic, make
the components-directory scan recursive, and pin down the two historical
silent-visual regressions (Task 8's chart axis-bounds clamp, Task 13's
GridContainer child-key aliasing) with positive assertions in the fuzz
suite, since neither throws and the existing no-throw checks structurally
can't catch either.

Reword analyze_health_workout_correlation's tool declaration to drop
direct component names, closing the same presentation-leak class this
task already fixed for the sleeping-HR tool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genui): propagate registry through recursion, pin prompt drift, close review findings

Final whole-branch review fix wave for the A2UI genui refactor:

- A2UiRenderer's registry override used to be silently dropped past one
  level of nesting because GridContainerSpec recurses via bare
  A2UiRenderer(node: ...) calls. Mirror the existing theme-injection
  pattern with a new A2UiRegistryProvider InheritedWidget so an explicit
  registry override at any level propagates ambiently to everything below
  it (explicit param > inherited provider > defaultA2UiRegistry fallback).
- Pin the hand-written "WHICH COMPONENT TO REACH FOR" prose in
  gemini_context_builder.dart against silent drift: every component name
  it mentions must resolve in defaultA2UiRegistry, and the registry's
  spec count is asserted directly.
- Delete A2UiProps.object()/has() — confirmed zero call sites.
- Repurpose the orphaned Task 3 scaffolding test
  (a2ui_parser_stub_test.dart, redundant with a2ui_parser_test.dart) into
  a2ui_custom_registry_test.dart, the regression coverage the registry-
  propagation fix needed.
- Add scanned-file-count floors to the purity test's two directory scans
  so an empty/unreachable directory can't produce a vacuous pass.
- Document FilterChips' SizedBox.shrink() as a deliberate exception to
  the plan's "always A2UiEmptyPanel" rule (decorative chrome, not data).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: add design spec for Hive->SQLite migration + coach SQL query tool

* fix: persist assisted-load volume correctly, tighten exercise-handle scoping

- WorkoutSet now snapshots bodyweight/assist/extra at logging time instead
  of recomputing effective load from the CURRENT profile bodyweight on every
  read, which was silently corrupting historical volume whenever a user
  updated their weight. ExerciseLog.totalVolume and the workout_flow_screen
  logging path thread the snapshot through.
- Exercise-handle matching (workout_provider) now requires an exact handle
  match whenever a handle is set, falling back to legacy behavior only when
  no exact match exists — a null-handle log was previously matching ANY
  requested handle, surfacing the wrong variation's "last session" data.
- Handle selector no longer visually pre-selects an unpersisted handle, and
  setExerciseHandle no longer retroactively relabels already-logged sets.
- Assisted-load display values now respect the user's unit preference; the
  assisted-exercise classification is computed once and shared instead of
  drifting between two separate predicates.
- Body-weight input (settings_provider) now rejects non-finite/non-positive
  values on both the load and set paths, falling back to 70.0 when invalid.
- ml_service: deload-recovery reasoning no longer hardcodes "kg" regardless
  of unit settings; recovery detection now requires the comparison session
  to be recent and uses effective (not raw) load for assisted exercises.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: bound sleep-analytics window, drop fabricated data, resolve muscle groups by id

- get_sleeping_hr_analytics clamps the model-provided days window instead of
  looping unbounded; get_health_metrics now honors the requested days window
  instead of always querying one week, and both its and the correlation
  tool's declarations no longer advertise fields (resting HR, readiness)
  that aren't actually backed by implementation.
- analyze_health_workout_correlation no longer fabricates synthetic sleep
  data points to pad out insufficient real pairs — returns the existing
  insufficient-data error instead, so correlation/regression/chart output is
  never partly made up.
- get_muscle_group_volume now resolves requested names to ids via
  _resolveMuscleGroup and compares ids (also aggregating secondary muscle
  activations) instead of raw display-name substring matching.
- CoachToolService's optional HealthHistoryManager is now a named parameter.
- gemini_ai_service: daily-quota classification narrowed to actual
  daily-limit identifiers so minute-scale rate limits go through normal
  retry-delay handling instead of being misclassified as daily exhaustion;
  function-call ids are now preserved and matched into their responses;
  the fallback path now builds a thinkingConfig compatible with whichever
  model was actually selected. Mirrored in scripts/test_gemini_api.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genui): pie negative-value filtering, overflow guard, stat-card unit match

- DynamicChart's pie mode now filters to positive values before computing
  percentages/sections (preserving original index alignment with labels and
  series colors), falling back to an empty panel when nothing positive
  remains, instead of rendering a nonsense chart from negative/zero data.
- A2UiPanelTitle's trailing label is now Flexible with maxLines/ellipsis so
  a long model-provided string can't overflow the row.
- StatCard's unit-already-present check now requires a trailing-suffix
  match instead of any substring, fixing a false positive like unit "s"
  matching inside value "10 reps".
- MetricGauge's arc painter now also compares `track` in shouldRepaint, so
  a background-color-only change still triggers a repaint.
- A2UiTheme.seriesColor asserts a non-empty palette before the modulo index
  that would otherwise throw on one.
- A2UiParser: props/outer-children now merge (props wins on conflict) so a
  model writing children as a sibling of props isn't silently dropped; adds
  a whole-text jsonDecode fast path ahead of the balanced-span scan.
- A2UiRenderer logs the unresolved component name via the app's existing
  debugPrint/kDebugMode convention before falling back to an empty widget.
- a2ui_app_theme now imports A2UiTheme via the public genui barrel instead
  of an internal src path.
- CI: the release workflow's linker-patch step now requires and quotes
  PUB_CACHE, restricts the patch to resolved jni-*/src/CMakeLists.txt
  targets, is idempotent against re-runs, and fails the build instead of
  silently continuing when no target is found or patching fails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: close vacuous-test gaps and pin already-fixed regressions

Fixes tests that would pass identically whether the behavior they claim to
verify was correct or broken:
- stat_card_test's pump() helper now actually threads its props argument
  into the rendered node (it previously always rendered empty props).
- new_features_test's assisted-pullups case now uses distinguishable
  weight/assistWeight values, so the test fails if the wrong field is used.

Tightens two guardrail-class tests to actually detect what they claim to:
- a2ui_prompt_test's worked-example extraction is now bounded to the region
  after the "WORKED EXAMPLE:" marker via balanced-brace matching, instead of
  the last '}' anywhere in the whole prompt.
- a2ui_purity_test's forbidden-import regex now also guards lib/data/.
- a2ui_robustness_test's negative-axis assertion now requires minY to
  actually bracket the dataset's true minimum, not just be below -10.
- a2ui_theme_test's panel-decoration finders are scoped to the panel under
  test rather than the first Container anywhere in the tree.

Adds regression coverage pinning fixes already shipped in prior commits:
DynamicChart pie's negative-value filtering, StatCard's unit-suffix match,
and CoachToolService's days-window/insufficient-data/muscle-id fixes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: address CodeRabbit review findings on PR #64 (feat/genui)

Fixes real findings from PR #64's own review, ahead of merging into
r2.1.0, so the sqflite-migration branch (which currently carries these
genui files unmerged) won't reintroduce them as merge conflicts.

- a2ui_theme: seriesColor() now falls back to accent on an empty
  seriesPalette instead of only asserting (release builds strip
  asserts, so this was still a release-mode divide-by-zero)
- coach_tool_service: removed the synthetic "readiness_score" metric
  from analyze_health_workout_correlation — it was a made-up
  70-100 formula derived from sleep duration, presented as if it were
  an independent measured health signal in statistical output
- coach_tool_service, main.dart: CoachToolService constructor now uses
  named parameters (3+ args); updated every call site
- workout_provider: getRecommendations no longer passes the
  exercise-wide growth model into a handle-scoped recommendation,
  since _growthModels isn't trained per-handle and would mix
  variations (e.g. "Rope pushdown" trend bleeding into "Bar pushdown")
- test_gemini_api.py: post_generate_content_with_retry could fall off
  the end returning None after a quota-fallback on the final attempt,
  despite its dict return type; restructured so every path returns or
  raises
- test coverage: legend-absence assertions for single-series/pie
  charts, NaN/Infinity scatter-point coordinates, stable payload-based
  test names in the robustness suite, hoisted regex in the purity
  test, const constructor, and a corrected self-contradictory comment

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant