Add sponsors and a one-time support prompt#122

draft3
main
agent/add-project-support
openedAug 9, 2026byDDara Adedeji
View on GitHubTwo-way sync enabledPR last imported 1d ago
3 unresolved threads · Jump to file
Selected thread: lib/widgets/folder_navigator.dart:99 — <a href="#"><img alt="P1" src="https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9" align="top"></a> **WebView gate defers the third-launch prompt** On Windows without WebView2, this return occurs before the persisted prompt state is read or claimed. The application has already recorded the third launch, but `support_prompt_shown` remains unset; when WebView2 becomes available on a later launch, the `launchCount >= 3` condition causes the support toast to appear then instead of consuming the intended third-launch opportunity. Record or consume the third-launch eligibility independently of WebView2 availability. <details><summary><strong>Artifacts</strong></summary><br /> **[Executable support-prompt state reproduction source](https://app.greptile.com/trex/artifacts/cac016ce-217d-4489-b913-2b6dfbe90e3b)** - Node source used to assert the checked-out guard ordering and execute the two launch-state transitions, with the takeaway that it models the exact early-return and claim sequence. **[Third launch with unavailable WebView2 leaves prompt unclaimed](https://app.greptile.com/trex/artifacts/e556c1d8-1010-44c9-9ea0-119f3e8826f2)** - Executed third-launch reproduction with Windows WebView2 unavailable; it records launch count 3, no Hive read, no claim, and persisted \`support\_prompt\_shown: 0\`, with the takeaway that the third-launch invitation was not consumed. **[Later initialized launch claims and schedules prompt](https://app.greptile.com/trex/artifacts/805910fb-752c-465b-8648-41650b6e1cd5)** - Executed subsequent Windows launch reproduction with WebView2 initialized; it records launch count 4, a Hive read, a claim, and a scheduled toast, with the takeaway that the prompt appears later than the intended third launch. **[Flutter and Chromium runtime availability check](https://app.greptile.com/trex/artifacts/68b9bf63-db64-4a00-a20f-a798f10f38ba)** - Runtime availability command output shows \`flutter\` is not found and Chromium is absent, with the takeaway that rendered Flutter UI/video validation could not run in this environment. **[Executable reproduction syntax check](https://app.greptile.com/trex/artifacts/86cbc30f-946d-4021-a033-6dee132aca61)** - Node syntax validation for the authored reproduction completed with exit code 0, with the takeaway that the executed test harness was syntactically valid. <a href="https://app.greptile.com/trex/runs/18016461/artifacts?artifact=cac016ce-217d-4489-b913-2b6dfbe90e3b"><picture><source media="(prefers-color-scheme: dark)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewArtifactsDark.svg?v=4"><source media="(prefers-color-scheme: light)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewArtifacts.svg?v=4"><img alt="View artifacts" src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewArtifacts.svg?v=4"></picture></a> </details> <sub><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="14" align="absmiddle"></a> Ran code and verified through T-Rex</sub>
0 / 6 viewed6 files changedLine totals unavailable

lib/widgets/folder_navigator.dart · Earlier revision c80d651f · right 99

Original diff
@@ -85,10 +88,42 @@ class _FolderNavigatorState extends ConsumerState<FolderNavigator> {
         _warnWebView();
 
         _warnDemo();
+
+        _maybeShowSupportPrompt();
       }
     });
   }
 
+  Future<void> _maybeShowSupportPrompt() async {
+    if (kIsWeb) return;
+    if (Platform.isWindows && !isWebViewInitialized) return;
G

P1 WebView gate defers the third-launch prompt

On Windows without WebView2, this return occurs before the persisted prompt state is read or claimed. The application has already recorded the third launch, but support_prompt_shown remains unset; when WebView2 becomes available on a later launch, the launchCount >= 3 condition causes the support toast to appear then instead of consuming the intended third-launch opportunity. Record or consume the third-launch eligibility independently of WebView2 availability.

Artifacts

Executable support-prompt state reproduction source

  • Node source used to assert the checked-out guard ordering and execute the two launch-state transitions, with the takeaway that it models the exact early-return and claim sequence.

Third launch with unavailable WebView2 leaves prompt unclaimed

  • Executed third-launch reproduction with Windows WebView2 unavailable; it records launch count 3, no Hive read, no claim, and persisted `support_prompt_shown: 0`, with the takeaway that the third-launch invitation was not consumed.

Later initialized launch claims and schedules prompt

  • Executed subsequent Windows launch reproduction with WebView2 initialized; it records launch count 4, a Hive read, a claim, and a scheduled toast, with the takeaway that the prompt appears later than the intended third launch.

Flutter and Chromium runtime availability check

  • Runtime availability command output shows `flutter` is not found and Chromium is absent, with the takeaway that rendered Flutter UI/video validation could not run in this environment.

Executable reproduction syntax check

  • Node syntax validation for the authored reproduction completed with exit code 0, with the takeaway that the executed test harness was syntactically valid.

View artifacts

T-Rex Ran code and verified through T-Rex

lib/widgets/settings_tab.dart · Earlier revision a1b88386 · right 581–610

Original diff
@@ -562,6 +568,51 @@ class _GlobalSettingsSections extends ConsumerWidget {
             scope: MapThemeSettingsScope.global,
           ),
         ),
+        const SizedBox(height: 20),
+        const _SectionDivider(),
+        const SizedBox(height: 20),
+        SettingsScopeCard(
+          key: sectionKeys[_SettingsSection.globalSupport],
+          title: "Sponsors & support",
+          description:
+              "Icarus stays free through open-source programs and support from its users.",
+          child: Column(
+            children: [
+              _SupportLinkTile(
+                icon: Icons.bug_report_outlined,
+                title: "Greptile",
+                description:
+                    "AI code review through Greptile's Open Source Program.",
+                actionLabel: "View program",
+                onPressed: () {
+                  launchUrl(Settings.greptileOpenSourceLink);
+                },
+              ),
+              const _SettingsItemDivider(),
+              _SupportLinkTile(
+                icon: Icons.code_outlined,
+                title: "OpenAI",
+                description:
+                    "Tooling and credits through Codex for Open Source.",
+                actionLabel: "View program",
+                onPressed: () {
+                  launchUrl(Settings.openAICodexForOssLink);
+                },
+              ),
+              const _SettingsItemDivider(),
+              _SupportLinkTile(
+                icon: Icons.local_cafe_outlined,
+                title: "Support Icarus",
+                description:
+                    "If Icarus helps your team, you can help keep development going.",
+                actionLabel: "Buy me a coffee",
+                onPressed: () {
+                  launchUrl(Settings.buyMeACoffeeLink);

lib/widgets/settings_tab.dart · right 581–610

C
coderabbitaiBotAug 9, 2026

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

shell
1#!/bin/bash
2set -euo pipefail
3
4# Expect every launchUrl call to use result-aware handling.
5if rg -nP '^\s*launchUrl\s*\(' lib/widgets/settings_tab.dart; then
6 echo "Found a fire-and-forget launchUrl call"
7 exit 1
8fi
9
10rg -n -C 5 'launchUrl\(' lib/widgets/settings_tab.dart

Repository: SunkenInTime/icarus

Length of output: 387


🏁 Script executed:

shell
1#!/bin/bash
2set -euo pipefail
3
4echo "Relevant settings_tab.dart section:"
5sed -n '560,625p' lib/widgets/settings_tab.dart
6
7echo
8echo "Import/dependency declarations:"
9rg -n "url_launcher|launchUrl|Toast|showDialog|ErrorMessage|error" pubspec.yaml pubspec.lock

Repository: SunkenInTime/icarus

Length of output: 3225


🌐 Web query:

url_launcher package 6.3.1 launchUrl returns Future<bool> behavior if platform handler not found

💡 Result:

In the url_launcher package version 6.3.1, the launchUrl method returns a Future<bool> that indicates whether the URL was launched successfully [1][2]. When a platform handler for the requested URL cannot be found, the standard behavior is for launchUrl to return false [1][2]. Depending on the specific platform and the nature of the failure, it may alternatively throw a PlatformException [1][2]. It is important to note the following regarding this behavior: 1. Handling Failures: Because launchUrl can return false when an operation fails to launch, the package documentation explicitly recommends checking the boolean return value to handle such scenarios [3][1]. For example: if (!await launchUrl(url)) { // Handle failure (e.g., show an error message or try a fallback) } 2. Limitations of canLaunchUrl: You should not rely solely on canLaunchUrl to predict the outcome of launchUrl [3][4]. On many platforms (especially mobile and web), canLaunchUrl may return false even if launchUrl would succeed, often due to platform-specific security configurations like LSApplicationQueriesSchemes on iOS or <queries> elements in the Android manifest [5][6][2]. 3. Web Considerations: On web platforms, launchUrl behavior can sometimes lead to false negatives (returning false despite the operation potentially succeeding in the browser) depending on how the window or tab is opened (e.g., usage of webOnlyWindowName) [7]. For mission-critical operations, it is best to attempt the launchUrl call directly and implement fallback logic within the else or if (!result) block rather than disabling functionality based on a canLaunchUrl check [3][6].

Citations:


Handle external-link failures before shipping.

launchUrl(...) returns a Future<bool> at all three support-link call sites, and these callbacks discard the result. If the platform cannot launch the URL, the action fails silently. Route these callbacks through one async helper that checks false results and catches platform errors, then reports the failure with the existing toast/error UI.

🤖 Prompt for AI Agents
1Verify each finding against current code. Fix only still-valid issues, skip the
2rest with a brief reason, keep changes minimal, and validate.
3
4In `@lib/widgets/settings_tab.dart` around lines 581 - 610, Replace the direct
5launchUrl calls in the three _SupportLinkTile onPressed callbacks with a shared
6async helper that accepts the target URL, checks for a false launch result,
7catches platform exceptions, and reports failures through the existing
8toast/error UI. Keep each tile’s URL unchanged and ensure the callbacks await or
9return the helper’s Future.

Source: Coding guidelines

lib/widgets/folder_navigator.dart · Earlier revision a1b88386 · right 108–111

Original diff
@@ -85,10 +88,43 @@ class _FolderNavigatorState extends ConsumerState<FolderNavigator> {
         _warnWebView();
 
         _warnDemo();
+
+        _maybeShowSupportPrompt();
       }
     });
   }
 
+  Future<void> _maybeShowSupportPrompt() async {
+    if (kIsWeb) return;
+    if (!Hive.isBoxOpen(HiveBoxNames.appFlagsBox)) return;
+
+    final flags = Hive.box<int>(HiveBoxNames.appFlagsBox);
+    final launchCount =
+        flags.get(HiveBoxNames.appLaunchCountKey, defaultValue: 0) ?? 0;
+    final hasShownPrompt =
+        flags.get(HiveBoxNames.supportPromptShownKey, defaultValue: 0) == 1;
+    if (launchCount < 3 || hasShownPrompt) return;
+
+    // Claim the prompt before the delay so a route change or quick exit never
+    // turns a one-time invitation into a recurring interruption.
+    await flags.put(HiveBoxNames.supportPromptShownKey, 1);
+    if (Platform.isWindows && !isWebViewInitialized) return;

lib/widgets/folder_navigator.dart · right 108–111

C
coderabbitaiBotAug 9, 2026

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Move the persisted “shown” flag after the readiness check.

Line 110 writes supportPromptShownKey = 1. Line 111 can then return before Settings.showToast runs. On a Windows launch with an uninitialized WebView, the prompt is not shown, but later launches return at Line 106 because the flag is already set.

Move the readiness check before the write. Keep the write before the six-second delay.

Proposed fix
diff
1- // Claim the prompt before the delay so a route change or quick exit never
2- // turns a one-time invitation into a recurring interruption.
3- await flags.put(HiveBoxNames.supportPromptShownKey, 1);
4 if (Platform.isWindows && !isWebViewInitialized) return;
5+ // Claim the prompt before the delay so a route change or quick exit never
6+ // turns a one-time invitation into a recurring interruption.
7+ await flags.put(HiveBoxNames.supportPromptShownKey, 1);

As per coding guidelines, “If a library write path is uncertain, fail loudly without saving rather than persisting potentially incorrect data.”

📝 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.

suggestion
1 if (Platform.isWindows && !isWebViewInitialized) return;
2 // Claim the prompt before the delay so a route change or quick exit never
3 // turns a one-time invitation into a recurring interruption.
4 await flags.put(HiveBoxNames.supportPromptShownKey, 1);
🤖 Prompt for AI Agents
1Verify each finding against current code. Fix only still-valid issues, skip the
2rest with a brief reason, keep changes minimal, and validate.
3
4In `@lib/widgets/folder_navigator.dart` around lines 108 - 111, In the prompt flow
5around the readiness check and Settings.showToast, move the Platform.isWindows
6&& !isWebViewInitialized early return before writing
7flags.put(HiveBoxNames.supportPromptShownKey, 1). Keep the persisted write
8immediately before the existing six-second delay so the flag is saved only when
9the prompt can proceed.

Source: Coding guidelines