Skip to content

Conversation

@jjw24
Copy link
Member

@jjw24 jjw24 commented Oct 17, 2025

Context:
Display icon on last history results

Follows on with #4042

Changes:

  • Fixed result highlighting and scrolling when switched to history mode
  • Changed the property flow uses to display icons, from IcoPath to new IcoPathAbsolute. This helps IcoPath be serializable while flow uses the constructed full path from IcoPathAbsolute.
  • Changed LastOpenedHistoryItem class to LastOpenedHistoryResult.
  • Introduced inheritance, LastOpenedHistoryResult is now a child of Result. This helps retaining properties from the Result class and not needing to new up the Result class every time to return results for display. LastOpenedHistoryResult and Result are now serializable, useful for future when we need to provide similar special Results that need to be saved to disk.

Before
{7AD844BE-A3D1-412F-8F99-5BF9B6B3CCA5}

After
{080F9018-460F-4CEC-9893-8C38F1FF3947}

Tested

  1. Results with the same query do not duplicate when saved
  2. IcoPathAbsolute contains the full path
  3. IcoPath updated from user selection when different
  4. Moved flow to a different location and relative path images still work
  5. History mode navigation behaves correctly

@jjw24 jjw24 self-assigned this Oct 17, 2025
@jjw24 jjw24 added the Dev branch only An issue or fix for the Dev branch build label Oct 17, 2025
@github-actions github-actions bot added this to the 2.1.0 milestone Oct 17, 2025
@jjw24
Copy link
Member Author

jjw24 commented Oct 17, 2025

@Jack251970 @01Dri thoughts on showing the last opened with the actual result icon? Should it just show the icon instead of badge icon as well?

@jjw24
Copy link
Member Author

jjw24 commented Oct 17, 2025

Also, there is a bug in the comparison logic when saving history results because as seen in the screenshot 'Save Settings' result shouldn't be duplicated.

@01Dri
Copy link
Contributor

01Dri commented Oct 18, 2025

Also, there is a bug in the comparison logic when saving history results because as seen in the screenshot 'Save Settings' result shouldn't be duplicated.

It’s not really a bug. The equality comparison also uses the query, so in scenarios where I type “Ter” and open Terminal, and then type “Term” and open Terminal again, I end up with two history items “Ter” and “Term” both referring to the same application (Terminal), which causes duplicate results.

In Query style, that makes sense because the two queries are different.
However, in Last Opened style, it doesn’t really make sense.
A suggestion would be to filter the history items, removing duplicates by name and sorting them by the most recent execution date/time.
Here’s an example in MainViewModels.cs -> GetHistoryItems:

    foreach (var h in historyItems
               .OrderByDescending(x => x.ExecutedDateTime) 
               .DistinctBy(x => x.GetCompositeKey()).ToList())

CompositeKey (This name is just an example; it could be something else)

   public string GetCompositeKey()
    {
        var parts = new[] { Title, SubTitle, RecordKey }
            .Where(s => !string.IsNullOrEmpty(s));

        return string.Join("-", parts);
    }

@01Dri
Copy link
Contributor

01Dri commented Oct 18, 2025

@Jack251970 @01Dri thoughts on showing the last opened with the actual result icon? Should it just show the icon instead of badge icon as well?

I think the icon with the badge is more intuitive for the user, as it indicates that the item is a history entry.

@Jack251970
Copy link
Member

Also, there is a bug in the comparison logic when saving history results because as seen in the screenshot 'Save Settings' result shouldn't be duplicated.

As 01Dri said, it is not a bug. Duplicated items will occur if you click A for many times.

Copy link
Member

@Jack251970 Jack251970 left a comment

Choose a reason for hiding this comment

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

Showing icons enhance users' experience and I agree with this change. But there is still something we need to resolve

@jjw24
Copy link
Member Author

jjw24 commented Oct 18, 2025

@01Dri @Jack251970

I think the icon with the badge is more intuitive for the user, as it indicates that the item is a history entry.

What about just use the actual results icons, no badge?

However, in Last Opened style, it doesn’t really make sense.
A suggestion would be to filter the history items, removing duplicates by name and sorting them by the most recent execution date/time.

I think we should apply filtering of duplicates when last opened style is selected. What do you think?

@Jack251970
Copy link
Member

I think we should apply filtering of duplicates when last opened style is selected. What do you think?

@jjw24 I think we can do the filtering here as long as we keep the history in the order of execute time so that we can utilize the history list more effectively.

@01Dri
Copy link
Contributor

01Dri commented Oct 19, 2025

@Jack251970 @jjw24 Sorry for the question, I’m new to the open source community. My question is: am I allowed to make the requested changes, or is this PR only for jjw? Thanks!

@Jack251970
Copy link
Member

@Jack251970 @jjw24 Sorry for the question, I’m new to the open source community. My question is: am I allowed to make the requested changes, or is this PR only for jjw? Thanks!

You can create PR to last_history_show_result_icon branch

@01Dri
Copy link
Contributor

01Dri commented Oct 20, 2025

@Jack251970 @jjw24 Sorry for the question, I’m new to the open source community. My question is: am I allowed to make the requested changes, or is this PR only for jjw? Thanks!

You can create PR to last_history_show_result_icon branch

Ok thanks!

@jjw24
Copy link
Member Author

jjw24 commented Oct 20, 2025

@01Dri just to add, of course you can, just let the PR author know what/which area you are changing to avoid overlapping. If very minor like comments, typos or small corrections to the logic that you are sure won't overlap with what the author is doing then go ahead, otherwise its good to let the PR author know you intend to help out with certain areas of the PR.

Having said that, I am still making changes to this draft but help is very welcomed still.

@jjw24 jjw24 requested a review from Jack251970 January 5, 2026 10:06
Added CheckIcoPathValidity to QueryHistory to set default icons for history items missing IcoPath, addressing legacy data issues. Integrated this check into MainViewModel's RefreshLastOpenedHistoryResults to guarantee icon validity before updating absolute paths.
if (existingHistoryItem.IcoPath != result.IcoPath)
{
existingHistoryItem.IcoPath = result.IcoPath;
}
Copy link
Member

Choose a reason for hiding this comment

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

Do you think we should also copy Glyph here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I forgot about Glyph. If Glyph is different then reassign it.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I forgot about Glyph. If Glyph is different then reassign it.

However, the Glyph is a init-only property. How can we edit it?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
Flow.Launcher/ViewModel/MainViewModel.cs (1)

2-2: Remove unused using System.Collections; directive.

The code uses only generic collections from System.Collections.Generic. This non-generic namespace import is unused.

🧹 Nitpick comments (1)
Flow.Launcher/Storage/QueryHistory.cs (1)

73-86: Consider moving updated items to the end of the list.

When an existing history item is found and updated, it remains at its original position in the list. Since GetHistoryItems in MainViewModel.cs now explicitly orders by ExecutedDateTime descending (line 1332), this works correctly for display. However, relying on the runtime sort rather than maintaining insertion order could lead to confusion when inspecting the raw JSON storage.

If the team prefers the storage to reflect chronological order, consider removing and re-adding the item:

🔎 Optional refactor
             if (LastOpenedHistoryItems.Count > 0 &&
                 TryGetLastOpenedHistoryResult(result, out var existingHistoryItem))
             {
+                LastOpenedHistoryItems.Remove(existingHistoryItem);
                 existingHistoryItem.ExecutedDateTime = DateTime.Now;
                 if (existingHistoryItem.IcoPath != result.IcoPath)
                 {
                     existingHistoryItem.IcoPath = result.IcoPath;
                 }
+                LastOpenedHistoryItems.Add(existingHistoryItem);
             }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b2fb1f and e403c41.

📒 Files selected for processing (2)
  • Flow.Launcher/Storage/QueryHistory.cs
  • Flow.Launcher/ViewModel/MainViewModel.cs
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.
Learnt from: NoPlagiarism
Repo: Flow-Launcher/Flow.Launcher PR: 3104
File: Flow.Launcher.Plugin/Result.cs:73-74
Timestamp: 2024-12-02T07:37:46.838Z
Learning: In 'Result.cs', the user prefers not to add additional data URI validation in the 'IcoPath' property setter.
📚 Learning: 2026-01-05T10:06:36.650Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.

Applied to files:

  • Flow.Launcher/ViewModel/MainViewModel.cs
  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-05-01T05:38:25.673Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3500
File: Flow.Launcher/Storage/TopMostRecord.cs:145-149
Timestamp: 2025-05-01T05:38:25.673Z
Learning: For the MultipleTopMostRecord implementation in Flow.Launcher, sequence order of records in the ConcurrentBag does not need to be preserved, as confirmed by the developer. The unordered nature of ConcurrentBag is acceptable for this implementation.

Applied to files:

  • Flow.Launcher/ViewModel/MainViewModel.cs
📚 Learning: 2024-11-03T07:40:11.014Z
Learnt from: Yusyuriv
Repo: Flow-Launcher/Flow.Launcher PR: 3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-10-16T10:48:30.573Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3854
File: Flow.Launcher/App.xaml.cs:252-275
Timestamp: 2025-10-16T10:48:30.573Z
Learning: In Flow Launcher's App.xaml.cs, the plugin initialization block (lines 252-275) that includes PreStartPluginExecutablePathUpdate, PluginManager.LoadPlugins, PluginManager.InitializePluginsAsync, _mainVM.QueryResults(), and API.SaveAppAllSettings() does not require additional Task.Run wrappers or Dispatcher.InvokeAsync calls according to maintainer Jack251970, as the threading model is already safe as designed.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-09-04T11:52:29.096Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 3932
File: Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs:48-55
Timestamp: 2025-09-04T11:52:29.096Z
Learning: In Flow Launcher's PluginsManifest.cs, when dealing with version parsing for the MinimumAppVersion feature, maintainer jjw24 prefers to keep the solution simple rather than implementing comprehensive helper methods for SemVer parsing normalization.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
🧬 Code graph analysis (1)
Flow.Launcher/ViewModel/MainViewModel.cs (5)
Flow.Launcher/Storage/TopMostRecord.cs (2)
  • FlowLauncherJsonStorageTopMostRecord (12-103)
  • FlowLauncherJsonStorageTopMostRecord (17-77)
Flow.Launcher/Storage/QueryHistory.cs (3)
  • History (11-119)
  • Add (59-87)
  • UpdateIcoPathAbsolute (105-118)
Flow.Launcher/ViewModel/ResultsViewModel.cs (2)
  • List (227-238)
  • List (240-261)
Flow.Launcher/Storage/LastOpenedHistoryResult.cs (4)
  • LastOpenedHistoryResult (10-117)
  • LastOpenedHistoryResult (26-28)
  • LastOpenedHistoryResult (35-56)
  • LastOpenedHistoryResult (63-93)
Flow.Launcher.Core/Plugin/QueryBuilder.cs (1)
  • Query (9-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (10)
Flow.Launcher/Storage/QueryHistory.cs (4)

5-6: LGTM!

The new using directives and type change to List<LastOpenedHistoryResult> are necessary for the refactored history storage.

Also applies to: 18-19


23-52: LGTM!

The legacy migration correctly converts HistoryItem objects to LastOpenedHistoryResult instances. Setting IcoPath and Glyph to null is appropriate since legacy items lack this information, and the Action provides reasonable fallback behavior.


89-97: LGTM!

Clean helper method that properly delegates equality comparison to LastOpenedHistoryResult.Equals.


99-118: LGTM!

The method correctly handles edge cases: empty history, missing plugin IDs, and uninstalled plugins. The XML documentation clearly states the prerequisite that plugins must be initialized before calling.

Flow.Launcher/ViewModel/MainViewModel.cs (6)

49-50: LGTM!

Field declaration and initialization reordering is appropriate for code organization.

Also applies to: 155-159


358-362: LGTM!

Good fix for the potential IndexOutOfRangeException when history results are empty.


644-656: LGTM!

Correctly updates the preview panel when returning to query results via Esc key, maintaining consistency with other navigation paths.


1327-1401: LGTM!

The refactored GetHistoryItems method correctly:

  • Orders by ExecutedDateTime descending before deduplication
  • Uses GroupBy with First() to retain the most recent occurrence
  • Deep copies items to prevent mutation of stored data
  • Clears PluginID on copies to prevent re-saving
  • Uses the original item (not copiedItem) in the AsyncAction for accurate result population

The fallback to re-query when PopulateResultsAsync returns null is a good resilience pattern.


1404-1422: LGTM!

Well-documented internal method with correct sequencing: legacy migration before icon path refresh. The XML remarks appropriately warn about the plugin initialization prerequisite.


1659-1672: LGTM!

Correctly uses the new selectLast parameter to limit history items displayed on the home page.

copiedItem.Title = Localize.executeQuery(copiedItem.Query);
copiedItem.IcoPath = Constant.HistoryIcon;
// TODO: Add Glyph here
// copiedItem.Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C");
Copy link
Member

Choose a reason for hiding this comment

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

I think we should also copy Glyph here.

@VictoriousRaptor
Copy link
Contributor

Don't have much time review these days:(

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e403c41 and c610100.

📒 Files selected for processing (1)
  • Flow.Launcher/Helper/ResultHelper.cs
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.
Learnt from: NoPlagiarism
Repo: Flow-Launcher/Flow.Launcher PR: 3104
File: Flow.Launcher.Plugin/Result.cs:73-74
Timestamp: 2024-12-02T07:37:46.838Z
Learning: In 'Result.cs', the user prefers not to add additional data URI validation in the 'IcoPath' property setter.
📚 Learning: 2026-01-05T10:06:36.650Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.

Applied to files:

  • Flow.Launcher/Helper/ResultHelper.cs
📚 Learning: 2025-10-16T09:29:19.653Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3854
File: Flow.Launcher.Core/Plugin/PluginManager.cs:519-523
Timestamp: 2025-10-16T09:29:19.653Z
Learning: In Flow Launcher's PluginManager.cs QueryDialogJumpForPluginAsync method, when a DialogJump plugin is still initializing, returning null is intentional behavior. This allows the query to fall through to the default Explorer plugin, which serves as a fallback handler. This is different from QueryForPluginAsync and QueryHomeForPluginAsync, which show "still initializing" messages because they don't have fallback mechanisms.

Applied to files:

  • Flow.Launcher/Helper/ResultHelper.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
Flow.Launcher/Helper/ResultHelper.cs (1)

14-17: LGTM!

The signature update from LastOpenedHistoryItem to LastOpenedHistoryResult aligns with the PR's refactoring objectives, and the delegation pattern remains intact.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
Flow.Launcher/ViewModel/MainViewModel.cs (1)

1352-1358: Consider copying the Glyph property.

A past review comment suggested copying the Glyph property for Query-style history. Currently, only IcoPath is set. If the original result had a glyph, it may be beneficial to preserve it in the copied item.

🔎 Proposed addition
 if (Settings.HistoryStyle == HistoryStyle.Query)
 {
     copiedItem.Title = Localize.executeQuery(copiedItem.Query);
     copiedItem.IcoPath = Constant.HistoryIcon;
-    // TODO: Add Glyph here
-    // copiedItem.Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C");
+    copiedItem.Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C");
 }

Based on past review comments.

Flow.Launcher/Storage/QueryHistory.cs (2)

74-82: Consider updating Glyph alongside IcoPath.

When updating an existing history item at lines 77-81, only ExecutedDateTime and IcoPath are refreshed. If the result's Glyph property can also change over time (e.g., plugin updates), you may want to update it here as well for consistency.

🔎 Proposed addition
 if (LastOpenedHistoryItems.Count > 0 &&
     TryGetLastOpenedHistoryResult(result, out var existingHistoryItem))
 {
     existingHistoryItem.ExecutedDateTime = DateTime.Now;
     if (existingHistoryItem.IcoPath != result.IcoPath)
     {
         existingHistoryItem.IcoPath = result.IcoPath;
     }
+    if (existingHistoryItem.Glyph != result.Glyph)
+    {
+        existingHistoryItem.Glyph = result.Glyph;
+    }
 }

Based on past review comments.


131-139: Consider logging when plugins are not found.

When a history item references a PluginID that no longer exists (line 135-136), the code silently continues. For troubleshooting and maintenance, it may be helpful to log a debug or warning message when a plugin cannot be resolved.

🔎 Proposed addition
 foreach (var item in LastOpenedHistoryItems)
 {
     if (string.IsNullOrEmpty(item.PluginID)) continue;

     var pluginPair = PluginManager.GetPluginForId(item.PluginID);
-    if (pluginPair == null) continue;
+    if (pluginPair == null)
+    {
+        App.API.LogDebug(nameof(History), $"Plugin {item.PluginID} not found for history item: {item.Title}");
+        continue;
+    }

     item.PluginDirectory = pluginPair.Metadata.PluginDirectory;
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c610100 and e404d02.

📒 Files selected for processing (2)
  • Flow.Launcher/Storage/QueryHistory.cs
  • Flow.Launcher/ViewModel/MainViewModel.cs
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.
Learnt from: NoPlagiarism
Repo: Flow-Launcher/Flow.Launcher PR: 3104
File: Flow.Launcher.Plugin/Result.cs:73-74
Timestamp: 2024-12-02T07:37:46.838Z
Learning: In 'Result.cs', the user prefers not to add additional data URI validation in the 'IcoPath' property setter.
📚 Learning: 2026-01-05T10:06:36.650Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
  • Flow.Launcher/ViewModel/MainViewModel.cs
📚 Learning: 2024-11-03T07:40:11.014Z
Learnt from: Yusyuriv
Repo: Flow-Launcher/Flow.Launcher PR: 3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-10-16T10:48:30.573Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3854
File: Flow.Launcher/App.xaml.cs:252-275
Timestamp: 2025-10-16T10:48:30.573Z
Learning: In Flow Launcher's App.xaml.cs, the plugin initialization block (lines 252-275) that includes PreStartPluginExecutablePathUpdate, PluginManager.LoadPlugins, PluginManager.InitializePluginsAsync, _mainVM.QueryResults(), and API.SaveAppAllSettings() does not require additional Task.Run wrappers or Dispatcher.InvokeAsync calls according to maintainer Jack251970, as the threading model is already safe as designed.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-09-04T11:52:29.096Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 3932
File: Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs:48-55
Timestamp: 2025-09-04T11:52:29.096Z
Learning: In Flow Launcher's PluginsManifest.cs, when dealing with version parsing for the MinimumAppVersion feature, maintainer jjw24 prefers to keep the solution simple rather than implementing comprehensive helper methods for SemVer parsing normalization.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-05-01T05:38:25.673Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3500
File: Flow.Launcher/Storage/TopMostRecord.cs:145-149
Timestamp: 2025-05-01T05:38:25.673Z
Learning: For the MultipleTopMostRecord implementation in Flow.Launcher, sequence order of records in the ConcurrentBag does not need to be preserved, as confirmed by the developer. The unordered nature of ConcurrentBag is acceptable for this implementation.

Applied to files:

  • Flow.Launcher/ViewModel/MainViewModel.cs
🧬 Code graph analysis (2)
Flow.Launcher/Storage/QueryHistory.cs (3)
Flow.Launcher.Core/Plugin/PluginManager.cs (2)
  • PluginManager (24-1044)
  • PluginManager (168-174)
Flow.Launcher/Storage/LastOpenedHistoryResult.cs (5)
  • LastOpenedHistoryResult (10-117)
  • LastOpenedHistoryResult (26-28)
  • LastOpenedHistoryResult (35-56)
  • LastOpenedHistoryResult (63-93)
  • Equals (101-116)
Flow.Launcher/Storage/HistoryItem.cs (1)
  • Obsolete (5-45)
Flow.Launcher/ViewModel/MainViewModel.cs (1)
Flow.Launcher.Core/Plugin/QueryBuilder.cs (1)
  • Query (9-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
Flow.Launcher/ViewModel/MainViewModel.cs (2)

1407-1425: Well-designed helper method.

The RefreshLastOpenedHistoryResults method is clear and well-documented. The remarks correctly note that plugins must be initialized before calling, which aligns with the startup sequence where this is invoked after PluginManager.InitializePluginsAsync.


2-2: Remove the unused using System.Collections; directive.

This namespace is not used in the file—only generic collections from System.Collections.Generic are referenced.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
Flow.Launcher/Storage/QueryHistory.cs (2)

73-82: Should Glyph also be updated here?

When updating an existing history item, the code updates IcoPath if it differs from the incoming result (lines 78-81), but doesn't update Glyph. If the result's glyph has changed (e.g., plugin update, dynamic glyph logic), the history item will display stale glyph information.

Consider also updating Glyph to keep the visual representation consistent:

🔎 Proposed enhancement to update Glyph
 existingHistoryItem.ExecutedDateTime = DateTime.Now;
 if (existingHistoryItem.IcoPath != result.IcoPath)
 {
     existingHistoryItem.IcoPath = result.IcoPath;
 }
+if (existingHistoryItem.Glyph != result.Glyph)
+{
+    existingHistoryItem.Glyph = result.Glyph;
+}

Based on past review comments.


100-109: Should Glyph also be updated here?

Similar to the Add method, when updating an existing history item's IcoPath (lines 105-108), the Glyph property is not updated. For consistency and to keep visual representations current, consider updating Glyph as well.

🔎 Proposed enhancement to update Glyph
 existingHistoryItem.ExecutedDateTime = DateTime.Now;
 if (existingHistoryItem.IcoPath != result.IcoPath)
 {
     existingHistoryItem.IcoPath = result.IcoPath;
 }
+if (existingHistoryItem.Glyph != result.Glyph)
+{
+    existingHistoryItem.Glyph = result.Glyph;
+}

Based on past review comments.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e404d02 and 583bf74.

📒 Files selected for processing (1)
  • Flow.Launcher/Storage/QueryHistory.cs
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.
Learnt from: NoPlagiarism
Repo: Flow-Launcher/Flow.Launcher PR: 3104
File: Flow.Launcher.Plugin/Result.cs:73-74
Timestamp: 2024-12-02T07:37:46.838Z
Learning: In 'Result.cs', the user prefers not to add additional data URI validation in the 'IcoPath' property setter.
📚 Learning: 2026-01-05T10:06:36.650Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2026-01-06T07:16:42.908Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Helper/ResultHelper.cs:27-27
Timestamp: 2026-01-06T07:16:42.908Z
Learning: In Flow.Launcher/Helper/ResultHelper.cs, PopulateResultsAsync uses PluginManager.QueryForPluginAsync instead of direct plugin.Plugin.QueryAsync to handle plugin initialization gracefully. When a plugin is still initializing, QueryForPluginAsync returns a fake "still loading" result that won't match historical records, causing PopulateResultsAsync to return null. This is intentional and preferred over directly querying uninitialized plugins, which could produce unpredictable behavior.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2024-11-03T07:40:11.014Z
Learnt from: Yusyuriv
Repo: Flow-Launcher/Flow.Launcher PR: 3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-10-16T10:48:30.573Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3854
File: Flow.Launcher/App.xaml.cs:252-275
Timestamp: 2025-10-16T10:48:30.573Z
Learning: In Flow Launcher's App.xaml.cs, the plugin initialization block (lines 252-275) that includes PreStartPluginExecutablePathUpdate, PluginManager.LoadPlugins, PluginManager.InitializePluginsAsync, _mainVM.QueryResults(), and API.SaveAppAllSettings() does not require additional Task.Run wrappers or Dispatcher.InvokeAsync calls according to maintainer Jack251970, as the threading model is already safe as designed.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-09-04T11:52:29.096Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 3932
File: Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs:48-55
Timestamp: 2025-09-04T11:52:29.096Z
Learning: In Flow Launcher's PluginsManifest.cs, when dealing with version parsing for the MinimumAppVersion feature, maintainer jjw24 prefers to keep the solution simple rather than implementing comprehensive helper methods for SemVer parsing normalization.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
🧬 Code graph analysis (1)
Flow.Launcher/Storage/QueryHistory.cs (2)
Flow.Launcher/ViewModel/MainViewModel.cs (4)
  • List (1327-1405)
  • Query (1207-1235)
  • Result (1810-1851)
  • Result (1853-1881)
Flow.Launcher/Storage/LastOpenedHistoryResult.cs (5)
  • LastOpenedHistoryResult (10-117)
  • LastOpenedHistoryResult (26-28)
  • LastOpenedHistoryResult (35-56)
  • LastOpenedHistoryResult (63-93)
  • Equals (101-116)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
Flow.Launcher/Storage/QueryHistory.cs (6)

5-6: LGTM - Necessary imports for new functionality.

The added using directives support PluginManager access in UpdateIcoPathAbsolute() and localization in the migration code.


19-19: LGTM - Type migration is consistent with PR objectives.

The change from LastOpenedHistoryItem to LastOpenedHistoryResult aligns with the goal of using Result-based history items to preserve properties and display icons.


23-28: LGTM - Clear migration documentation.

The XML documentation and Obsolete attribute clearly communicate the migration purpose and deprecation timeline.


35-49: LGTM - Migration logic correctly handles legacy data.

The migration code appropriately converts legacy history items to the new format, setting up query-style entries with actions to re-run the original query. The absence of PluginID is intentional since legacy items represent historical queries rather than specific plugin results.


112-120: LGTM - Clean try-get pattern implementation.

The helper method cleanly encapsulates the logic to find matching history items, relying on LastOpenedHistoryResult.Equals(Result) for comparison semantics.


122-141: LGTM - Proper icon path refresh logic.

The UpdateIcoPathAbsolute() method correctly refreshes icon paths for history items by updating their PluginDirectory based on current plugin metadata. The null checks and PluginID validation ensure safe execution, and the XML remarks appropriately document the timing requirement (call after plugins are loaded).

Comment on lines 1354 to 1356
if (string.IsNullOrEmpty(copiedItem.IcoPath)) // Must manually set missing image icon here
{
Title = Localize.executeQuery(h.Query),
SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime),
IcoPath = Constant.HistoryIcon,
OriginQuery = new Query { TrimmedQuery = h.Query },
Action = _ =>
{
App.API.BackToQueryResults();
App.API.ChangeQuery(h.Query);
return false;
},
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C")
};
results.Add(result);
copiedItem.IcoPath = Constant.MissingImgIcon;
Copy link
Member Author

Choose a reason for hiding this comment

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

IcoPath shouldn't be null, this should not be necessary. If you just add the default IcoPath and Glyph as history icon when calling the PopulateHistoryFromLegacyHistory then you don't need to assign them here. Why are you setting the default IcoPath in PopulateHistoryFromLegacyHistory as null?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think you are getting the missing image icon during testing like in one of your screenshots is because you are testing with History.json from dev branch after it has been migrated by the previous PR. This means when you try this PR PopulateHistoryFromLegacyHistory is not called and consequently IcoPath is not set (because the code doesn't need to set it for normal results).

You need to test with History.json from prod build or as a fresh build where History.json doesn't exist.

Copy link
Member

@Jack251970 Jack251970 Jan 6, 2026

Choose a reason for hiding this comment

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

IcoPath shouldn't be null, this should not be necessary. If you just add the default IcoPath and Glyph as history icon when calling the PopulateHistoryFromLegacyHistory then you don't need to assign them here. Why are you setting the default IcoPath in PopulateHistoryFromLegacyHistory as null?

I think setting IcoPath to history icon path is unnecessary since this path is accessible in codes and we can use a if sentence to set the history icon

Copy link
Member

@Jack251970 Jack251970 Jan 6, 2026

Choose a reason for hiding this comment

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

I think you are getting the missing image icon during testing like in one of your screenshots is because you are testing with History.json from dev branch after it has been migrated by the previous PR. This means when you try this PR PopulateHistoryFromLegacyHistory is not called and consequently IcoPath is not set (because the code doesn't need to set it for normal results).

You need to test with History.json from prod build or as a fresh build where History.json doesn't exist.

I think if we do not know the actual icon for some history items, we should use this missing icon instead of the history icon as the default icon under Last Opened Mode.

Copy link
Member

Choose a reason for hiding this comment

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

Since the badge icon is already the history icon...

Copy link
Member

@Jack251970 Jack251970 Jan 6, 2026

Choose a reason for hiding this comment

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

Setting by default where it is loaded is better than putting in if statement, I think the correct use of if here is set a different value 'if the required property (such as SubTitle) needs to be different than the source'.

Sorry, I do not agree with you. The path saved locally is static, while the path in the code is dynamic and runtime. If we change this path from Images\history.png to Images\history_icon.png, will the old path of that history icon still work? But the path from codes should always be right.

We should know the actual icon though, because LastOpenHistoryResult is a child class of Result, so unless Result doesn't for some reason have IcoPath assigned, in this case if I remember correctly if IcoPath is null or pointing to a non-existent image then code later on will assign missing icon image by default anyway.

Yes, LastOpenHistoryResult should know the actual icons. But if we set a static history icon as the icon paths of those old data, the badge icons will be the same as the icons.

Copy link
Member

Choose a reason for hiding this comment

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

I think IcoPath should store its actual icons

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, I do not agree with you. The path saved locally is static, while the path in the code is dynamic and runtime. If we change this path from Images\history.png to Images\history_icon.png, will the old path of that history icon still work? But the path from codes should always be right.

Well, it will still work because in my code with PopulateHistoryFromLegacyHistory we set IcoPath = Constant.HistoryIcon,, dynamically assigned on startup.

You are setting null for IcoPath in PopulateHistoryFromLegacyHistory then add a specific if statement to handle this null with if (string.IsNullOrEmpty(copiedItem.IcoPath)) (Line 1354), I think is not required especially for a throw away backwards compatibility method that only gets run once. Legacy history results that we migrated for the user will always have the history icon and new history results will always contain a non-null IcoPath, so why do we need to handle IcoPath null? Have I missed a scenario?

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I do not agree with you. The path saved locally is static, while the path in the code is dynamic and runtime. If we change this path from Images\history.png to Images\history_icon.png, will the old path of that history icon still work? But the path from codes should always be right.

Well, it will still work because in my code with PopulateHistoryFromLegacyHistory we set IcoPath = Constant.HistoryIcon,, dynamically assigned on startup.

You are setting null for IcoPath in PopulateHistoryFromLegacyHistory then add a specific if statement to handle this null with if (string.IsNullOrEmpty(copiedItem.IcoPath)) (Line 1354), I think is not required especially for a throw away backwards compatibility method that only gets run once. Legacy history results that we migrated for the user will always have the history icon and new history results will always contain a non-null IcoPath, so why do we need to handle IcoPath null? Have I missed a scenario?

Well, feel free to make any changes for this

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I do not agree with you. The path saved locally is static, while the path in the code is dynamic and runtime. If we change this path from Images\history.png to Images\history_icon.png, will the old path of that history icon still work? But the path from codes should always be right.

Well, it will still work because in my code with PopulateHistoryFromLegacyHistory we set IcoPath = Constant.HistoryIcon,, dynamically assigned on startup.

You are setting null for IcoPath in PopulateHistoryFromLegacyHistory then add a specific if statement to handle this null with if (string.IsNullOrEmpty(copiedItem.IcoPath)) (Line 1354), I think is not required especially for a throw away backwards compatibility method that only gets run once. Legacy history results that we migrated for the user will always have the history icon and new history results will always contain a non-null IcoPath, so why do we need to handle IcoPath null? Have I missed a scenario?

Well, feel free to make any changes for this

@jjw24
Copy link
Member Author

jjw24 commented Jan 6, 2026

@jjw24 The item order is not correct under Last Opened History option. (I have openned Save Settings item for two times. One before Wechat and another after WeChat)

image Here are my settings. image

Homepage display takes into consideration of result selection ranking, the behavior is, when on fresh boot the result is ordered descending by date, and then they move up the more you select them. Is this not the case?

@Jack251970
Copy link
Member

@jjw24 The item order is not correct under Last Opened History option. (I have openned Save Settings item for two times. One before Wechat and another after WeChat)
image
Here are my settings.
image

Homepage display takes into consideration of result selection ranking, the behavior is, when on fresh boot the result is ordered descending by date, and then they move up the more you select them. Is this not the case?

Oh yeah. I forgot the user selection score

/// <summary>
/// Update a result into the last-opened history list (<see cref="LastOpenedHistoryItems"/>).
/// </summary>
public void Update(Result result)
Copy link
Member Author

Choose a reason for hiding this comment

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

What scenario do we need this for? This is pretty much the same as the add method.

Copy link
Member

Choose a reason for hiding this comment

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

What scenario do we need this for? This is pretty much the same as the add method.

When we are executing one command from history item itself.

Copy link
Member Author

Choose a reason for hiding this comment

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

hm, I am aware that when you select a result from the history page or I think also home page it doesn't call Add(), I thought this was by design as selecting history results don't count towards user selection like normal acutal results do. However, changing this looks like introducing a little bit of unnecessary complexity in the code.

Copy link
Member

@Jack251970 Jack251970 Jan 6, 2026

Choose a reason for hiding this comment

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

hm, I am aware that when you select a result from the history page or I think also home page it doesn't call Add(), I thought this was by design as selecting history results don't count towards user selection like normal acutal results do. However, changing this looks like introducing a little bit of unnecessary complexity in the code.

I think this logic is necessary since it keeps the icon & execute date up-to-date. And in codes, we also handle the user select record, right?

if (reflectResult != null)
{
    // Record the user selected record for result ranking
    _userSelectedRecord.Add(reflectResult);

    // Update the history with the reflected result (I think it is necessary)
    _history.Update(reflectResult);

    // Since some actions may need to hide the Flow window to execute
    // So let us populate the results of them
    return await reflectResult.ExecuteAsync(c);
}

Copy link
Member

Choose a reason for hiding this comment

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

If you think it is unnecessary, let us remove them all.

// Record the user selected record for result ranking
_userSelectedRecord.Add(reflectResult);

// Update the history with the reflected result (I think it is necessary)
_history.Update(reflectResult);

Copy link
Member

Choose a reason for hiding this comment

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

Reverted as you said

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI Agents
In @Flow.Launcher/Storage/QueryHistory.cs:
- Around line 74-82: In the block that updates an existing history item (where
LastOpenedHistoryItems.Count > 0 and TryGetLastOpenedHistoryResult(result, out
var existingHistoryItem) returns true), also compare and assign
existingHistoryItem.Glyph to result.Glyph when they differ (just like you do for
IcoPath), in addition to updating existingHistoryItem.ExecutedDateTime and
existingHistoryItem.IcoPath; update the Glyph check/assignment alongside the
IcoPath check to ensure the glyph is kept in sync with result.Glyph.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d9ab1f and 3745c44.

📒 Files selected for processing (2)
  • Flow.Launcher/Storage/QueryHistory.cs
  • Flow.Launcher/ViewModel/MainViewModel.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • Flow.Launcher/ViewModel/MainViewModel.cs
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.
📚 Learning: 2026-01-05T10:06:36.650Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:36.650Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2026-01-06T07:16:42.908Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Helper/ResultHelper.cs:27-27
Timestamp: 2026-01-06T07:16:42.908Z
Learning: In Flow.Launcher/Helper/ResultHelper.cs, PopulateResultsAsync uses PluginManager.QueryForPluginAsync instead of direct plugin.Plugin.QueryAsync to handle plugin initialization gracefully. When a plugin is still initializing, QueryForPluginAsync returns a fake "still loading" result that won't match historical records, causing PopulateResultsAsync to return null. This is intentional and preferred over directly querying uninitialized plugins, which could produce unpredictable behavior.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2024-11-03T07:40:11.014Z
Learnt from: Yusyuriv
Repo: Flow-Launcher/Flow.Launcher PR: 3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-10-16T10:48:30.573Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3854
File: Flow.Launcher/App.xaml.cs:252-275
Timestamp: 2025-10-16T10:48:30.573Z
Learning: In Flow Launcher's App.xaml.cs, the plugin initialization block (lines 252-275) that includes PreStartPluginExecutablePathUpdate, PluginManager.LoadPlugins, PluginManager.InitializePluginsAsync, _mainVM.QueryResults(), and API.SaveAppAllSettings() does not require additional Task.Run wrappers or Dispatcher.InvokeAsync calls according to maintainer Jack251970, as the threading model is already safe as designed.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-09-04T11:52:29.096Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 3932
File: Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs:48-55
Timestamp: 2025-09-04T11:52:29.096Z
Learning: In Flow Launcher's PluginsManifest.cs, when dealing with version parsing for the MinimumAppVersion feature, maintainer jjw24 prefers to keep the solution simple rather than implementing comprehensive helper methods for SemVer parsing normalization.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
Flow.Launcher/Storage/QueryHistory.cs (5)

5-6: LGTM!

The new using directives are necessary for PluginManager.GetPluginForId (line 113) and Localize.executeQuery (line 37).


19-19: LGTM!

The type change from LastOpenedHistoryItem to LastOpenedHistoryResult aligns with the PR objective to inherit from Result and retain Result properties.


23-52: LGTM!

The legacy migration correctly creates LastOpenedHistoryResult instances with IcoPath and Glyph set to null (appropriate since legacy data doesn't contain this information), and the Action lambda properly restores the historical query.


89-97: LGTM!

The helper method correctly uses FirstOrDefault with Equals to locate matching history items. The O(n) scan is acceptable given the 300-item max history size, and the pattern relies on the LastOpenedHistoryResult.Equals implementation to define matching semantics.


99-118: LGTM!

The method correctly refreshes icon paths by updating PluginDirectory for each history item (which in turn triggers IcoPathAbsolute recalculation in LastOpenedHistoryResult). The null checks and early returns are appropriate, and the remarks clearly state this should be called after plugin initialization.

Copy link
Member

@Jack251970 Jack251970 left a comment

Choose a reason for hiding this comment

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

Changes

LastOpenedHistoryItems.Add(new LastOpenedHistoryResult
{
Title = Localize.executeQuery(item.Query),
IcoPath = null,
Copy link
Member

Choose a reason for hiding this comment

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

Set it to null and parse it during runtime


if (selected != null) // SelectedItem returns null if selection is empty.
if (selected != null && // SelectedItem returns null if selection is empty.
!string.IsNullOrEmpty(selected.PluginID)) // SelectedItem must have a valid PluginID
Copy link
Member

Choose a reason for hiding this comment

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

Check item plugin ID

}

private List<Result> GetHistoryItems(IEnumerable<LastOpenedHistoryItem> historyItems)
private List<Result> GetHistoryItems(IEnumerable<LastOpenedHistoryResult> historyItems, int? selectLast = null)
Copy link
Member

Choose a reason for hiding this comment

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

Add selectLast parameter for querying history results in home page

Copy link
Member Author

Choose a reason for hiding this comment

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

What is this for?

Copy link
Member

@Jack251970 Jack251970 Jan 8, 2026

Choose a reason for hiding this comment

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

For fixing issues when querying history items in home page. I unified the its querying logic

{
var result = new Result
copiedItem.Title = Localize.executeQuery(copiedItem.Query);
copiedItem.IcoPath = Constant.HistoryIcon;
Copy link
Member

@Jack251970 Jack251970 Jan 6, 2026

Choose a reason for hiding this comment

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

Use history icon under Query History Mode

}
else
{
if (string.IsNullOrEmpty(copiedItem.IcoPath)) // Must manually set missing image icon here
Copy link
Member

Choose a reason for hiding this comment

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

Use actual icon + history icon badge icon under Last Opened Mode

}
// Since some actions may need to hide the Flow window to execute
// So let us populate the results of them
return await reflectResult.ExecuteAsync(c);
Copy link
Member

Choose a reason for hiding this comment

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

Do not record the user selected record for result ranking


var results = GetHistoryItems(historyItems);
// Select last history results
var results = GetHistoryItems(_history.LastOpenedHistoryItems, Settings.MaxHistoryResultsToShowForHomePage);
Copy link
Member

@Jack251970 Jack251970 Jan 6, 2026

Choose a reason for hiding this comment

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

Combine get history item for home page logic into GetHistoryItems

@Jack251970
Copy link
Member

Jack251970 commented Jan 7, 2026

Final effect under my changes:

Query history Mode: Use history icon.
image

Last opened history mode: Use actual icon + history icon badge. Use missing icon as default value for old data.
image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @Flow.Launcher/Storage/LastOpenedHistoryResult.cs:
- Around line 130-145: The Equals(Result r) method can throw a
NullReferenceException when r or r.OriginQuery is null because it accesses
r.OriginQuery.TrimmedQuery; update Equals to first validate r is not null and
guard access to r.OriginQuery (e.g., check r == null and r.OriginQuery == null)
before reading TrimmedQuery, and then either return false when missing or throw
ArgumentNullException for fail-fast behavior; ensure all comparisons that use
OriginQuery.TrimmedQuery (both branches) reference the guarded/nullable-safe
value and keep comparisons for RecordKey, Title, SubTitle, PluginID and Query
unchanged.
🧹 Nitpick comments (2)
Flow.Launcher/Storage/QueryHistory.cs (2)

65-68: Off-by-one in max history enforcement.

Using > _maxHistory allows the list to grow to 301 items before removal. If the intent is to keep at most 300 items, consider using >=.

Suggested fix
-            if (LastOpenedHistoryItems.Count > _maxHistory)
+            if (LastOpenedHistoryItems.Count >= _maxHistory)
             {
                 LastOpenedHistoryItems.RemoveAt(0);
             }

71-72: Redundant count check.

The LastOpenedHistoryItems.Count > 0 guard is unnecessary since TryGetLastOpenedHistoryResult uses FirstOrDefault, which safely returns null on an empty list.

Simplified condition
-            if (LastOpenedHistoryItems.Count > 0 &&
-                TryGetLastOpenedHistoryResult(result, out var existingHistoryItem))
+            if (TryGetLastOpenedHistoryResult(result, out var existingHistoryItem))
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3745c44 and e970bb4.

📒 Files selected for processing (3)
  • Flow.Launcher/Storage/LastOpenedHistoryResult.cs
  • Flow.Launcher/Storage/QueryHistory.cs
  • Flow.Launcher/ViewModel/MainViewModel.cs
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:46.190Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.
📚 Learning: 2026-01-05T10:06:46.190Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Storage/LastOpenedHistoryResult.cs:35-56
Timestamp: 2026-01-05T10:06:46.190Z
Learning: In Flow.Launcher/Storage/LastOpenedHistoryResult.cs, the LastOpenedHistoryResult(Result result) constructor should not add null checks for result or result.OriginQuery - the maintainer prefers fail-fast behavior where invalid inputs cause immediate exceptions.

Applied to files:

  • Flow.Launcher/Storage/LastOpenedHistoryResult.cs
  • Flow.Launcher/ViewModel/MainViewModel.cs
  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2026-01-06T07:16:50.302Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 4057
File: Flow.Launcher/Helper/ResultHelper.cs:27-27
Timestamp: 2026-01-06T07:16:50.302Z
Learning: In Flow.Launcher/Helper/ResultHelper.cs, PopulateResultsAsync uses PluginManager.QueryForPluginAsync instead of direct plugin.Plugin.QueryAsync to handle plugin initialization gracefully. When a plugin is still initializing, QueryForPluginAsync returns a fake "still loading" result that won't match historical records, causing PopulateResultsAsync to return null. This is intentional and preferred over directly querying uninitialized plugins, which could produce unpredictable behavior.

Applied to files:

  • Flow.Launcher/Storage/LastOpenedHistoryResult.cs
  • Flow.Launcher/ViewModel/MainViewModel.cs
  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2024-12-08T21:12:12.060Z
Learnt from: Yusyuriv
Repo: Flow-Launcher/Flow.Launcher PR: 3118
File: Flow.Launcher/ViewModel/MainViewModel.cs:1404-1413
Timestamp: 2024-12-08T21:12:12.060Z
Learning: In the `MainViewModel` class, the `_lastQuery` field is initialized in the constructor and is never null.

Applied to files:

  • Flow.Launcher/ViewModel/MainViewModel.cs
📚 Learning: 2024-12-02T07:37:46.838Z
Learnt from: NoPlagiarism
Repo: Flow-Launcher/Flow.Launcher PR: 3104
File: Flow.Launcher.Plugin/Result.cs:73-74
Timestamp: 2024-12-02T07:37:46.838Z
Learning: In 'Result.cs', the user prefers not to add additional data URI validation in the 'IcoPath' property setter.

Applied to files:

  • Flow.Launcher/ViewModel/MainViewModel.cs
📚 Learning: 2025-05-01T05:38:25.673Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3500
File: Flow.Launcher/Storage/TopMostRecord.cs:145-149
Timestamp: 2025-05-01T05:38:25.673Z
Learning: For the MultipleTopMostRecord implementation in Flow.Launcher, sequence order of records in the ConcurrentBag does not need to be preserved, as confirmed by the developer. The unordered nature of ConcurrentBag is acceptable for this implementation.

Applied to files:

  • Flow.Launcher/ViewModel/MainViewModel.cs
📚 Learning: 2024-11-03T07:40:11.014Z
Learnt from: Yusyuriv
Repo: Flow-Launcher/Flow.Launcher PR: 3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-10-16T10:48:30.573Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3854
File: Flow.Launcher/App.xaml.cs:252-275
Timestamp: 2025-10-16T10:48:30.573Z
Learning: In Flow Launcher's App.xaml.cs, the plugin initialization block (lines 252-275) that includes PreStartPluginExecutablePathUpdate, PluginManager.LoadPlugins, PluginManager.InitializePluginsAsync, _mainVM.QueryResults(), and API.SaveAppAllSettings() does not require additional Task.Run wrappers or Dispatcher.InvokeAsync calls according to maintainer Jack251970, as the threading model is already safe as designed.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-09-04T11:52:29.096Z
Learnt from: jjw24
Repo: Flow-Launcher/Flow.Launcher PR: 3932
File: Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs:48-55
Timestamp: 2025-09-04T11:52:29.096Z
Learning: In Flow Launcher's PluginsManifest.cs, when dealing with version parsing for the MinimumAppVersion feature, maintainer jjw24 prefers to keep the solution simple rather than implementing comprehensive helper methods for SemVer parsing normalization.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
📚 Learning: 2025-07-21T09:19:49.684Z
Learnt from: Jack251970
Repo: Flow-Launcher/Flow.Launcher PR: 3854
File: Flow.Launcher/App.xaml.cs:246-262
Timestamp: 2025-07-21T09:19:49.684Z
Learning: In Flow Launcher's App.xaml.cs, the asynchronous plugin initialization task (containing AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate, PluginManager.LoadPlugins, PluginManager.InitializePluginsAsync, and AutoPluginUpdates) does not require additional try-catch error handling according to maintainer Jack251970, as these operations are designed to handle exceptions internally.

Applied to files:

  • Flow.Launcher/Storage/QueryHistory.cs
🧬 Code graph analysis (2)
Flow.Launcher/Storage/LastOpenedHistoryResult.cs (1)
Flow.Launcher.Plugin/Result.cs (2)
  • Result (15-406)
  • Result (324-358)
Flow.Launcher/Storage/QueryHistory.cs (2)
Flow.Launcher/ViewModel/MainViewModel.cs (5)
  • List (1319-1368)
  • Query (1209-1237)
  • BackToQueryResults (660-666)
  • Result (1773-1814)
  • Result (1816-1844)
Flow.Launcher/Storage/LastOpenedHistoryResult.cs (5)
  • LastOpenedHistoryResult (11-146)
  • LastOpenedHistoryResult (27-29)
  • LastOpenedHistoryResult (36-57)
  • LastOpenedHistoryResult (65-122)
  • Equals (130-145)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (19)
Flow.Launcher/Storage/QueryHistory.cs (5)

5-5: LGTM!

The new import is required for PluginManager.GetPluginForId used in UpdateIcoPathAbsolute.


17-18: LGTM!

The type change to List<LastOpenedHistoryResult> properly reflects the migration from the old LastOpenedHistoryItem class, enabling Result properties to be retained directly.


22-49: LGTM!

The legacy migration logic correctly:

  • Documents the deprecation timeline with [Obsolete] attribute.
  • Maps legacy HistoryItem fields to LastOpenedHistoryResult.
  • Properly captures item.Query in the Action delegate closure.

Note that missing fields (IcoPath, PluginID, etc.) are intentionally left as defaults since legacy history didn't store this data.


86-94: LGTM!

The TryGet pattern is implemented correctly, leveraging LastOpenedHistoryResult.Equals(Result) for matching history items.


96-115: LGTM!

The method correctly refreshes PluginDirectory from current plugin metadata, enabling accurate IcoPathAbsolute computation when Flow is moved. The null check at line 111 properly handles cases where a plugin may have been uninstalled.

Flow.Launcher/Storage/LastOpenedHistoryResult.cs (6)

1-12: LGTM!

Clean imports, file-scoped namespace, and clear XML documentation explaining the class purpose and inheritance relationship.


13-22: LGTM!

Properties are well-documented with sensible defaults for serialization scenarios.


24-29: LGTM!

Parameterless constructor required for JSON deserialization.


36-57: LGTM!

Constructor properly copies essential fields and sets up the query history reopening action. The fail-fast approach (no null checks) aligns with maintainer preferences. Based on learnings.


95-122: LGTM with minor observation.

The deep copy correctly captures values for closures and configures history-style properties. The minimal Query object creation (line 103) is consistent with how Equals uses it.


100-101: This comment is based on incorrect assumptions. The code intentionally sets PluginID = string.Empty for history results, and the codebase already has defensive guards in place to prevent context menu operations for items with empty PluginID (see MainViewModel.cs lines 438 and 1264). No exceptions occur from empty PluginID—the design explicitly prevents context menu operations in such cases with the checks !string.IsNullOrEmpty(SelectedResults.SelectedItem.Result.PluginID) and !string.IsNullOrEmpty(selected.PluginID).

Likely an incorrect or invalid review comment.

Flow.Launcher/ViewModel/MainViewModel.cs (8)

48-50: LGTM!

Storage field declarations look correct.


357-361: LGTM!

Properly ensures the first history item is selected when switching to history mode, addressing the scrolling/highlighting issue mentioned in the PR objectives.


437-448: LGTM!

The PluginID check correctly prevents context menu exceptions for history items that lack a valid PluginID, and the preview update ensures proper state when returning from context menu. This addresses the context menu exception discussed in the PR.


648-653: LGTM!

Consistent preview state update when escaping back to Results view.


1263-1268: LGTM on the PluginID validation.

The check !string.IsNullOrEmpty(selected.PluginID) properly guards context menu operations for history items that lack a valid PluginID.


1319-1368: LGTM on the history deduplication and async action logic.

The implementation correctly:

  1. Orders by ExecutedDateTime descending to show most recent first
  2. Deduplicates LastOpened style by composite key (Title, SubTitle, PluginID, RecordKey) - addressing the duplicate history issue from PR discussion
  3. Uses the original item in the async closure for PopulateResultsAsync to preserve unmodified properties

One minor observation: The grouping key includes PluginID which may be null/empty for some history items, but this is acceptable as they'll correctly group together by null/empty value.


1370-1388: LGTM!

Well-documented method with clear prerequisites. The XML documentation properly notes that plugins must be initialized before calling this method for icon resolution to work correctly.


1625-1629: LGTM!

Correctly passes the MaxHistoryResultsToShowForHomePage setting to limit history results on the home page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

30 min review bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants