Skip to content

Add markdown preview pane with per-result content types#4529

Draft
TrueCrimeDev wants to merge 21 commits into
Flow-Launcher:devfrom
TrueCrimeDev:upstream-markdown-preview
Draft

Add markdown preview pane with per-result content types#4529
TrueCrimeDev wants to merge 21 commits into
Flow-Launcher:devfrom
TrueCrimeDev:upstream-markdown-preview

Conversation

@TrueCrimeDev

@TrueCrimeDev TrueCrimeDev commented Jun 12, 2026

Copy link
Copy Markdown

What

Results can now declare how their preview description is rendered via Result.Preview.ContentType:

  • text (default) — the classic plain-text preview, completely unchanged
  • markdown — renders the description as markdown in the preview pane, with syntax-highlighted code blocks
  • hidden — suppresses the preview pane for that result, even when Always Preview is on

Selecting a markdown result auto-opens the internal preview pane, and moving to a non-markdown result closes it again. Panes the user opened manually (preview hotkey) or via Always Preview are never auto-closed, and Always Preview no longer reopens the pane against a manual close mid-query.

Why

Plugins that produce rich text (AI assistants, documentation/snippet/note search, dictionary-style lookups) currently have to cram everything into plain SubTitle/Description text. This lets them opt into a proper rendered preview per result without affecting any existing plugin: every current result keeps the default text behavior.

How

  • New PreviewMarkdownScrollViewer control built on MdXaml (already a dependency) with code blocks highlighted via AvalonEdit (the one new package) and a CodeHighlightTheme mapped to the active theme
  • PreviewMarkdownStyle resource in Themes/Base.xaml so themes can restyle the rendered markdown
  • PreviewContentType enum on PreviewInfo, serialized as lowercase strings — JSON-RPC plugins opt in with "preview": { "contentType": "markdown", "description": "**hello**" }
  • Pane gating lives in MainViewModel.UpdatePreviewAsync with state for result-suppressed and auto-opened panes

Demo

📹 markdown-preview-demo.mp4 (hosted on my fork)

Tests

Full suite passes (256/256), including new coverage: pane gating rules (MainViewModelPreviewTest), markdown control behavior (PreviewMarkdownScrollViewerTest, PreviewMarkdownStyleTest), and JSON-RPC contentType deserialization (JsonRPCPluginTest).

🤖 Generated with Claude Code


Summary by cubic

Adds a markdown preview pane with syntax‑highlighted code blocks, per‑result control of preview visibility, and clickable links. Adds a selectable code theme (Auto by default) and theme‑aware horizontal scrollbars across built‑in themes.

Summary of changes

  • Changed
    • Preview behavior: honors per‑result PreviewVisibility (default, never, always); one‑shot restore after never; auto‑open/close for always; manual close persists even with Always Preview on; ResetPreview restores forced panes and closes external before opening internal.
    • Content type: default renamed to imageWithText (JSON‑RPC wire name imageWithText).
    • UI: wider preview column; markdown preview caps height; FlowDocument width tracks pane size; preview and code‑block scrollbars use theme‑aware styles (HorizontalThumbStyle/HorizontalScrollBarStyle) across built‑in themes; hyperlinks keep underline and are now clickable (web URLs only; no relative links); arrow keys stay in focused code blocks.
    • Performance/robustness: coalesced queued markdown fixes; per‑editor theming via a ThemedHighlightingColorizer (no mutation of shared highlighters; recolors styling‑free names like “Punctuation”); code‑highlight brush compares by value to avoid redundant re‑tints; truly async preview toggle.
  • Added
    • Result.Preview.ContentType (imageWithText, markdown) and Result.PreviewVisibility (default, never, always) with JSON‑RPC wire values.
    • PreviewMarkdownScrollViewer using MdXaml + AvalonEdit, with AHK→C++ highlighting aliases, per‑editor highlighter theming, and dynamic retinting via CodeHighlightTheme. Utility: PreviewMarkdownScrollViewer.IsCodeBlockFocused.
    • Settings.CodeHighlightTheme (default "Auto") with a Theme page dropdown; Auto tracks Light/Dark. Built‑in themes: VS Code Light, VS Code Dark+, Catppuccin Macchiato, One Dark.
    • PreviewMarkdownStyle for consistent markdown styling; ResultViewModel flags: IsMarkdownPreview, HidePreviewPane, ForcePreviewPane.
  • Removed
    • The old hidden preview content type (replaced by PreviewVisibility.Never).
  • Memory
    • Small increase from AvalonEdit, per‑editor colorizers, and new style/theme resources. Controls are created on demand.
  • Security
    • Low risk. Markdown renders to a FlowDocument with no scripts or auto‑navigation; links open only on click and are treated as web URLs (no local relative links); code blocks are read‑only.
  • Unit tests
    • Added/updated: MainViewModelPreviewTest (pane gating, manual‑close persistence, reopen fixes), PreviewMarkdownScrollViewerTest, PreviewMarkdownStyleTest, CodeHighlightThemeTest (Auto resolver + unrecognized setting), JsonRPCPluginTest (contentType default imageWithText and markdown; previewVisibility round‑trips).

Release Note
You can now see rich, themed markdown previews (with clickable links and code), and each result can choose to show, hide, or auto‑open the preview panel.

Written for commit 61cfd30. Summary will update on new commits.

Review in cubic

Results can now declare how their preview description is rendered via
Result.Preview.ContentType:

- text (default): the classic plain-text preview, unchanged
- markdown: renders the description as markdown in the preview pane,
  with syntax-highlighted code blocks (MdXaml + AvalonEdit), themed
  via the new PreviewMarkdownStyle resources
- hidden: suppresses the preview pane for that result, even when
  Always Preview is on

Selecting a markdown result auto-opens the internal preview and
leaving it closes the pane again; panes the user opened with the
preview hotkey (or via Always Preview) are never auto-closed.
JSON-RPC plugins opt in with "contentType": "markdown" on the
preview object.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 12, 2026 02:18
@github-actions github-actions Bot added this to the 2.2.0 milestone Jun 12, 2026

Copilot AI 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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds per-result preview rendering modes (text/markdown/hidden) and introduces a markdown-capable internal preview pane with code highlighting, including updated preview-pane behavior and new tests.

Changes:

  • Add PreviewContentType to plugin Result.PreviewInfo and surface flags in ResultViewModel for markdown/hidden handling.
  • Implement PreviewMarkdownScrollViewer + theme resources to render markdown (with AvalonEdit fenced code blocks) in the preview pane.
  • Update MainViewModel preview-open/close logic to support per-result suppression and markdown auto-open/close; add NUnit coverage.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Flow.Launcher/packages.lock.json Locks AvalonEdit as a direct dependency and updates plugin dependency range.
Flow.Launcher/Flow.Launcher.csproj Adds AvalonEdit package reference required for markdown code blocks.
Flow.Launcher.Plugin/Result.cs Adds PreviewContentType enum and JSON serialization support on PreviewInfo.
Flow.Launcher/ViewModel/ResultViewModel.cs Exposes IsMarkdownPreview / HidePreviewPane for UI + preview logic.
Flow.Launcher/ViewModel/MainViewModel.cs Implements suppression/auto-open behavior for preview based on selected result.
Flow.Launcher/MainWindow.xaml Wires in PreviewMarkdownScrollViewer and toggles layout/visibility for markdown vs default preview.
Flow.Launcher/Themes/Base.xaml Adds PreviewMarkdownStyle including hyperlink, blockquote, and code block styling.
Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs New control to render markdown and apply compatibility fixes + syntax recoloring.
Flow.Launcher/Resources/Controls/CodeHighlightTheme.cs Defines code highlight palettes and name mapping for AvalonEdit token categories.
Flow.Launcher.Test/PreviewMarkdownStyleTest.cs Validates key style setters in Base.xaml for markdown rendering.
Flow.Launcher.Test/PreviewMarkdownScrollViewerTest.cs Validates hyperlink accent color + document page width sizing behavior.
Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs Adds JSON-RPC deserialization test for contentType: "markdown".
Flow.Launcher.Test/MainViewModelPreviewTest.cs Adds behavioral tests for hidden/markdown preview suppression + restoration logic.

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

Comment thread Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs
Comment on lines +38 to +47
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);

if (e.Property == MarkdownProperty)
{
UpdateDocumentPageWidth(ActualWidth);
_ = Dispatcher.BeginInvoke(ApplyMarkdownCompatibilityFixes, DispatcherPriority.Loaded);
}
}
Comment on lines 1147 to +1154
[RelayCommand]
private void TogglePreview()
{
if (ShouldSuppressPreview(PreviewSelectedItem))
{
_ = SuppressPreviewAsync();
return;
}
Comment on lines 1194 to +1200
public void ResetPreview()
{
if (ShouldSuppressPreview(PreviewSelectedItem))
{
_ = SuppressPreviewAsync();
return;
}
Comment on lines +468 to +476
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMarkdownPreview}" Value="True">
<Setter Property="MaxHeight" Value="600" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
Comment thread Flow.Launcher/Themes/Base.xaml

@cubic-dev-ai cubic-dev-ai 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.

6 issues found across 13 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Flow.Launcher/MainWindow.xaml">

<violation number="1" location="Flow.Launcher/MainWindow.xaml:447">
P2: GridSplitter is no longer keyboard-focusable, removing keyboard-based pane resizing and visible focus indication</violation>
</file>

<file name="Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs">

<violation number="1" location="Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs:45">
P2: Uncoalesced `Dispatcher.BeginInvoke` calls queue redundant expensive document traversals during rapid markdown preview updates.</violation>

<violation number="2" location="Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs:210">
P1: RetintEditor mutates shared AvalonEdit highlighting definitions, causing global theming side effects</violation>
</file>

<file name="Flow.Launcher.Plugin/Result.cs">

<violation number="1" location="Flow.Launcher.Plugin/Result.cs:413">
P1: Mutable static `PreviewInfo.Default` can leak newly added `ContentType` changes across unrelated results.</violation>
</file>

<file name="Flow.Launcher/ViewModel/MainViewModel.cs">

<violation number="1" location="Flow.Launcher/ViewModel/MainViewModel.cs:1152">
P2: `SuppressPreviewAsync()` is fire-and-forgotten here. If it throws (e.g., in `CloseExternalPreviewAsync`), the exception will go unobserved and `_previewSuppressedBySelectedResult`/`_previewAutoOpenedBySelectedResult` may be left inconsistent. Either make `TogglePreview` async and await the call, use an async relay command, or route through a centralized fire-and-forget helper that observes and logs exceptions.</violation>
</file>

<file name="Flow.Launcher/Themes/Base.xaml">

<violation number="1" location="Flow.Launcher/Themes/Base.xaml:726">
P2: Setting `TextDecorations="None"` unconditionally removes the only non-color cue for hyperlinks. Users with color-vision deficiencies or low-contrast themes may not be able to identify links. Consider adding a trigger to restore the underline on hover or focus for better accessibility.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs Outdated
Comment thread Flow.Launcher.Plugin/Result.cs Outdated
Background="Transparent"
ShowsPreview="True"
FocusVisualStyle="{x:Null}"
IsTabStop="False"

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.

P2: GridSplitter is no longer keyboard-focusable, removing keyboard-based pane resizing and visible focus indication

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher/MainWindow.xaml, line 447:

<comment>GridSplitter is no longer keyboard-focusable, removing keyboard-based pane resizing and visible focus indication</comment>

<file context>
@@ -442,7 +443,9 @@
                         Background="Transparent"
-                        ShowsPreview="True"
+                        FocusVisualStyle="{x:Null}"
+                        IsTabStop="False"
+                        ShowsPreview="False"
                         Visibility="{Binding InternalPreviewVisible, Converter={StaticResource BoolToVisibilityConverter}}">
</file context>

Comment thread Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs Outdated
Comment thread Flow.Launcher/ViewModel/MainViewModel.cs Outdated
Comment thread Flow.Launcher/Themes/Base.xaml Outdated
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds markdown preview rendering, preview visibility controls, code highlighting theme selection, UI wiring, and supporting tests across the preview pipeline.

Changes

Markdown Preview Feature

Layer / File(s) Summary
Preview contract and view model
Flow.Launcher.Plugin/Result.cs, Flow.Launcher/ViewModel/ResultViewModel.cs
Result adds PreviewVisibility and PreviewInfo.ContentType, PreviewInfo.Default sets text content type, and ResultViewModel derives markdown and pane-visibility flags from preview metadata.
Markdown rendering and theming
Flow.Launcher/Resources/Controls/CodeHighlightTheme.cs, Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs, Flow.Launcher/Themes/Base.xaml
Adds markdown document sizing, emphasis cleanup, embedded editor recoloring, AHK highlight aliases, theme presets and resolution, and the PreviewMarkdownStyle resource for markdown elements.
Theme settings and preview UI wiring
Flow.Launcher.Infrastructure/UserSettings/Settings.cs, Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs, Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml, Flow.Launcher/MainWindow.xaml, Flow.Launcher/MainWindow.xaml.cs, Flow.Launcher/Flow.Launcher.csproj, Flow.Launcher/packages.lock.json, Flow.Launcher/Languages/en.xaml
Adds the code-highlight theme setting, exposes it in theme settings UI, applies it on load/theme changes, switches the main preview layout to the markdown viewer, and updates package/dependency metadata and localized labels.
Preview visibility control
Flow.Launcher/ViewModel/MainViewModel.cs
Adds suppression state and result-driven preview pane behavior for hiding, restoring, forcing open, and auto-closing the preview.
Validation coverage
Flow.Launcher.Test/MainViewModelPreviewTest.cs, Flow.Launcher.Test/PreviewMarkdownScrollViewerTest.cs, Flow.Launcher.Test/PreviewMarkdownStyleTest.cs, Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs, Flow.Launcher.Test/CodeHighlightThemeTest.cs
Adds tests for preview visibility transitions, markdown rendering behavior, theme selection, XAML style values, and JSON-RPC markdown deserialization.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Jack251970
  • onesounds
  • VictoriousRaptor
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the main change: adding a markdown preview pane with per-result content types.
Description check ✅ Passed The description matches the changeset and explains the markdown preview pane, content types, and related behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 2

🧹 Nitpick comments (2)
Flow.Launcher.Test/PreviewMarkdownScrollViewerTest.cs (1)

28-31: 💤 Low value

Consider a type check before casting Hyperlink.Foreground.

Line 29 casts hyperlink.Foreground to SolidColorBrush without verifying the type. While the test creates a style with SolidColorBrush, a pattern match or is check would make the test more robust against future style changes or theme variations.

♻️ Proposed fix using pattern matching
 var hyperlink = EnumerateInlines(viewer.Document.Blocks).OfType<Hyperlink>().Single();
-var foreground = (SolidColorBrush)hyperlink.Foreground;
-
-ClassicAssert.AreEqual(accentBrush.Color, foreground.Color);
+ClassicAssert.IsInstanceOf<SolidColorBrush>(hyperlink.Foreground);
+var foreground = (SolidColorBrush)hyperlink.Foreground;
+ClassicAssert.AreEqual(accentBrush.Color, foreground.Color);
🤖 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 `@Flow.Launcher.Test/PreviewMarkdownScrollViewerTest.cs` around lines 28 - 31,
The test currently casts hyperlink.Foreground to SolidColorBrush without
verifying the type; update the assertion to first check the runtime type of
Hyperlink.Foreground (e.g., using pattern matching "is SolidColorBrush" or an
"as" check and a not-null/assert instance) before accessing Color, so replace
the direct cast of hyperlink.Foreground with a safe type check on
Hyperlink.Foreground and then compare the brush.Color to accentBrush.Color
(referencing the variables hyperlink, Hyperlink.Foreground, SolidColorBrush,
foreground and the assertion ClassicAssert.AreEqual).
Flow.Launcher.Test/MainViewModelPreviewTest.cs (1)

161-204: ⚡ Quick win

Consider guarding reflection calls against null to improve test diagnostics.

The helper methods use reflection to access private MainViewModel members by string name (backing fields, static fields, methods). If any of these members are renamed or removed during refactoring, the reflection calls will throw NullReferenceException at runtime instead of failing at compile time. Adding null checks with descriptive error messages would make test failures easier to diagnose.

🛡️ Example: Add null guard for GetField
 private static MainViewModel CreatePreviewViewModel(Settings settings, int resultAreaColumn)
 {
     var viewModel = (MainViewModel)RuntimeHelpers.GetUninitializedObject(typeof(MainViewModel));
-    typeof(MainViewModel)
+    var settingsField = typeof(MainViewModel)
         .GetField("<Settings>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)
-        .SetValue(viewModel, settings);
+    if (settingsField == null)
+        throw new InvalidOperationException("MainViewModel.<Settings>k__BackingField not found; internal member may have been renamed.");
+    settingsField.SetValue(viewModel, settings);
     viewModel.ResultAreaColumn = resultAreaColumn;
     return viewModel;
 }

Apply similar guards to GetMethod and other GetField calls.

🤖 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 `@Flow.Launcher.Test/MainViewModelPreviewTest.cs` around lines 161 - 204, Guard
every reflection lookup and invocation in CreatePreviewViewModel,
ResultAreaColumnPreviewShown, ResultAreaColumnPreviewHidden,
InvokeUpdatePreviewAsync, and SetExternalPreviewVisible by checking the return
of Type.GetField/GetMethod for null and throwing a descriptive exception (e.g.,
InvalidOperationException) that names the missing member string (like
"<Settings>k__BackingField", "ResultAreaColumnPreviewShown",
"ResultAreaColumnPreviewHidden", "UpdatePreviewAsync",
"<ExternalPreviewVisible>k__BackingField") and the target type MainViewModel;
also validate MethodInfo.Invoke returns non-null where you expect a Task before
awaiting and provide clear messages for both missing members and unexpected
return types to make test failures easy to diagnose.
🤖 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 `@Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs`:
- Around line 217-228: The current code recreates a SolidHighlightingBrush each
loop and uses reference equality (Equals(color.Foreground, brush)), causing
unnecessary assignments and redraws; change the check to compare brush content
instead of object identity: inspect color.Foreground, cast it to
SolidHighlightingBrush (or check its exposed color property) and compare that
brush's color/value to the target before assigning; only set color.Foreground =
new SolidHighlightingBrush(target) and mark changed when the existing brush's
color differs (this prevents redundant NamedHighlightingColors rewrites and
avoids calling editor.TextArea.TextView.Redraw() when nothing changed).
- Line 19: ActiveTheme is hardcoded and not synced with the app theme, and
RetintEditor creates new SolidHighlightingBrush objects and compares by
reference which forces redundant Redraws; update ActiveTheme to read Flow's
current theme value (e.g., subscribe to the app/theme manager and set
PreviewMarkdownScrollViewer.ActiveTheme when the app theme changes) so the
control follows the selected app theme, and in RetintEditor stop allocating new
SolidHighlightingBrush for comparisons—reuse existing brushes or compare the
actual color values (e.g., compare color.Foreground.Color or brush.Color using
value equality) and only call editor.TextArea.TextView.Redraw() when the
color/value actually changed.

---

Nitpick comments:
In `@Flow.Launcher.Test/MainViewModelPreviewTest.cs`:
- Around line 161-204: Guard every reflection lookup and invocation in
CreatePreviewViewModel, ResultAreaColumnPreviewShown,
ResultAreaColumnPreviewHidden, InvokeUpdatePreviewAsync, and
SetExternalPreviewVisible by checking the return of Type.GetField/GetMethod for
null and throwing a descriptive exception (e.g., InvalidOperationException) that
names the missing member string (like "<Settings>k__BackingField",
"ResultAreaColumnPreviewShown", "ResultAreaColumnPreviewHidden",
"UpdatePreviewAsync", "<ExternalPreviewVisible>k__BackingField") and the target
type MainViewModel; also validate MethodInfo.Invoke returns non-null where you
expect a Task before awaiting and provide clear messages for both missing
members and unexpected return types to make test failures easy to diagnose.

In `@Flow.Launcher.Test/PreviewMarkdownScrollViewerTest.cs`:
- Around line 28-31: The test currently casts hyperlink.Foreground to
SolidColorBrush without verifying the type; update the assertion to first check
the runtime type of Hyperlink.Foreground (e.g., using pattern matching "is
SolidColorBrush" or an "as" check and a not-null/assert instance) before
accessing Color, so replace the direct cast of hyperlink.Foreground with a safe
type check on Hyperlink.Foreground and then compare the brush.Color to
accentBrush.Color (referencing the variables hyperlink, Hyperlink.Foreground,
SolidColorBrush, foreground and the assertion ClassicAssert.AreEqual).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9171e764-4894-436e-8051-480231edb557

📥 Commits

Reviewing files that changed from the base of the PR and between 5400502 and 3af13af.

📒 Files selected for processing (13)
  • Flow.Launcher.Plugin/Result.cs
  • Flow.Launcher.Test/MainViewModelPreviewTest.cs
  • Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
  • Flow.Launcher.Test/PreviewMarkdownScrollViewerTest.cs
  • Flow.Launcher.Test/PreviewMarkdownStyleTest.cs
  • Flow.Launcher/Flow.Launcher.csproj
  • Flow.Launcher/MainWindow.xaml
  • Flow.Launcher/Resources/Controls/CodeHighlightTheme.cs
  • Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs
  • Flow.Launcher/Themes/Base.xaml
  • Flow.Launcher/ViewModel/MainViewModel.cs
  • Flow.Launcher/ViewModel/ResultViewModel.cs
  • Flow.Launcher/packages.lock.json

Comment thread Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs Outdated
Comment on lines +217 to +228
var brush = new SolidHighlightingBrush(target);
if (!Equals(color.Foreground, brush))
{
color.Foreground = brush;
changed = true;
}
}

if (changed)
{
editor.TextArea.TextView.Redraw();
}

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid reference equality when checking highlight brush changes.

brush is recreated on every iteration, and SolidHighlightingBrush does not override equality, so this branch stays hot even when the target color is unchanged. That forces redundant NamedHighlightingColors rewrites and redraws every time compatibility fixes run.

♻️ Proposed fix
         foreach (var color in definition.NamedHighlightingColors)
         {
             if (color.Name is null || !theme.TryGetColor(color.Name, out var target))
             {
                 continue;
             }

-            var brush = new SolidHighlightingBrush(target);
-            if (!Equals(color.Foreground, brush))
+            var currentColor =
+                color.Foreground?.GetBrush(null) is SolidColorBrush currentBrush
+                    ? currentBrush.Color
+                    : (Color?)null;
+
+            if (currentColor != target)
             {
-                color.Foreground = brush;
+                color.Foreground = new SolidHighlightingBrush(target);
                 changed = true;
             }
         }
📝 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
var brush = new SolidHighlightingBrush(target);
if (!Equals(color.Foreground, brush))
{
color.Foreground = brush;
changed = true;
}
}
if (changed)
{
editor.TextArea.TextView.Redraw();
}
var currentColor =
color.Foreground?.GetBrush(null) is SolidColorBrush currentBrush
? currentBrush.Color
: (Color?)null;
if (currentColor != target)
{
color.Foreground = new SolidHighlightingBrush(target);
changed = true;
}
}
if (changed)
{
editor.TextArea.TextView.Redraw();
}
🤖 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 `@Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs` around lines
217 - 228, The current code recreates a SolidHighlightingBrush each loop and
uses reference equality (Equals(color.Foreground, brush)), causing unnecessary
assignments and redraws; change the check to compare brush content instead of
object identity: inspect color.Foreground, cast it to SolidHighlightingBrush (or
check its exposed color property) and compare that brush's color/value to the
target before assigning; only set color.Foreground = new
SolidHighlightingBrush(target) and mark changed when the existing brush's color
differs (this prevents redundant NamedHighlightingColors rewrites and avoids
calling editor.TextArea.TextView.Redraw() when nothing changed).

Comment thread Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs
Comment thread Flow.Launcher/MainWindow.xaml
Comment thread Flow.Launcher/Resources/Controls/CodeHighlightTheme.cs
Comment thread Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs
Comment thread Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs Outdated
return;
}

if (_previewSuppressedBySelectedResult)

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.

Why is there this double check for preview suppressed?
Does the previous call to ShouldSuppressPreview not handle this?
Or is this doing something else?

This probably needs a comment added to explain why

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

They're two different things — sorry that wasn't clear. The first is ShouldSuppressPreview (does the current result opt out, i.e. PreviewVisibility.Never). The second was a one-shot restore flag for when a previous Never result hid a pane we'd promised to bring back. Renamed it _restorePreviewAfterNeverResult and added comments spelling out why both checks exist (ea78b53).

Comment thread Flow.Launcher.Plugin/Result.cs Outdated
@DavidGBrett

Copy link
Copy Markdown
Contributor

Incase anyone else is reviewing this, heres the link to the plugin @TrueCrimeDev made using this feature:
https://github.com/TrueCrimeDev/Flow.Launcher.Plugin.Shorty

@Jack251970 Jack251970 added the enhancement New feature or request label Jun 14, 2026
TrueCrimeDev and others added 4 commits June 29, 2026 07:13
…enum

Addresses review feedback that hiding the preview pane did not belong on
PreviewContentType. Content type now controls only rendering (Text/Markdown);
a new Result.PreviewVisibility { Default, Never, Always } controls whether the
pane is shown — discoverable on Result itself rather than nested in PreviewInfo.

- Never replaces ContentType.Hidden (suppress even when Always Preview is on)
- Always forces the pane open even when Always Preview is off, decoupling the
  "force open" behaviour from markdown content
- Rename _previewSuppressedBySelectedResult -> _restorePreviewAfterNeverResult
  and document why the per-result opt-out check and the restore-arming check in
  UpdatePreviewAsync are distinct
- Update unit tests to the new API and add JSON-RPC round-trip coverage for the
  "never"/"always" wire values

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM
…o default

Addresses review feedback that the bundled code themes were never selectable and
that none suited a light colour scheme.

- Add a "Code Highlight Theme" dropdown under Settings > Theme (Appearance)
- New Settings.CodeHighlightTheme (default "Auto") + CodeHighlightThemes enum
- "Auto" follows the app colour scheme: a new VS Code Light theme on light, the
  existing dark themes on dark; explicit picks always win (CodeHighlightTheme.Resolve)
- Apply the resolved theme at startup and whenever the app's actual theme changes,
  so Auto tracks System/Light/Dark switches
- Unit-test the resolver via PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM
With Always Preview off, ResetPreview() (run on every window show) unconditionally
hid the pane, so a result with PreviewVisibility.Always lost its preview on reopen
until the user hovered another result and re-triggered the auto-open.

Honour ForcePreviewPane in ResetPreview's Always-Preview-off branch so forced
previews come back immediately on reopen. Add regression tests for both the forced
(opens) and default (stays hidden) cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM
Embedded code editors are measured at exact content height inside the markdown
FlowDocument, leaving no room for the horizontal scrollbar, which then overlaps
the last line. Reserve bottom padding so the last line stays clear.

Note: visual-only change; should be confirmed by eye against a long-line code block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 14 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Flow.Launcher/ViewModel/MainViewModel.cs">

<violation number="1" location="Flow.Launcher/ViewModel/MainViewModel.cs:1152">
P2: Always Preview re-arms the preview pane after a manual close when passing through a hidden/Never result. SuppressPreviewAsync cannot distinguish a pane hidden by manual TogglePreview from one that was never opened, so the `else if (Settings.AlwaysPreview)` branch unconditionally sets `_restorePreviewAfterNeverResult = true` even after the user explicitly closed the preview. The next non-Never selection then reopens the pane via UpdatePreviewAsync, contradicting the PR's stated behavior that manual close should persist.</violation>

<violation number="2" location="Flow.Launcher/ViewModel/MainViewModel.cs:1220">
P2: ResetPreview's ForcePreviewPane branch can leave an existing external preview open because it bypasses HidePreview.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Flow.Launcher/ViewModel/MainViewModel.cs
Comment thread Flow.Launcher/ViewModel/MainViewModel.cs

@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: 3

🤖 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 `@Flow.Launcher.Plugin/Result.cs`:
- Line 380: The Result.Clone() implementation is still shallow-copying Preview,
and PreviewInfo.Default is being shared as a mutable singleton, so updates can
leak across results. Update Result.Clone() to deep-copy the PreviewInfo instead
of reusing the same instance, and replace the shared default preview object with
a fresh PreviewInfo created per Result (for example via
PreviewInfo.CreateDefault()) in the Result/PreviewInfo initialization path.
Refer to Result.Clone() and the PreviewInfo.Default/CreateDefault setup so each
result owns its own preview state.

In `@Flow.Launcher.Test/CodeHighlightThemeTest.cs`:
- Around line 34-39: The test in
`GivenUnknownOrEmptySetting_WhenApplied_ThenFallsBackToAutoBehaviour` only
covers the empty-string path, so add a separate assertion using a truly
unrecognized theme name such as a bogus value to exercise the unknown-setting
fallback in `PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme`. Keep the
existing empty case if needed, but ensure the test explicitly verifies that an
invalid non-empty setting still resolves to the default theme via
`PreviewMarkdownScrollViewer.ActiveThemeName`.
- Around line 10-39: The tests in CodeHighlightThemeTest are mutating the static
theme state on PreviewMarkdownScrollViewer, which can leak between tests and
make the fixture order-dependent. Add a small setup/teardown in this test class
to save and restore the previous PreviewMarkdownScrollViewer.ActiveTheme (or
otherwise reset the theme after each test), so each
ApplyCodeHighlightTheme/ActiveThemeName assertion runs in isolation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90be7157-4534-4d06-b869-267e8d57511f

📥 Commits

Reviewing files that changed from the base of the PR and between 3af13af and 20c7c62.

📒 Files selected for processing (14)
  • Flow.Launcher.Infrastructure/UserSettings/Settings.cs
  • Flow.Launcher.Plugin/Result.cs
  • Flow.Launcher.Test/CodeHighlightThemeTest.cs
  • Flow.Launcher.Test/MainViewModelPreviewTest.cs
  • Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
  • Flow.Launcher/Languages/en.xaml
  • Flow.Launcher/MainWindow.xaml.cs
  • Flow.Launcher/Resources/Controls/CodeHighlightTheme.cs
  • Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs
  • Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs
  • Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml
  • Flow.Launcher/Themes/Base.xaml
  • Flow.Launcher/ViewModel/MainViewModel.cs
  • Flow.Launcher/ViewModel/ResultViewModel.cs
✅ Files skipped from review due to trivial changes (2)
  • Flow.Launcher/Languages/en.xaml
  • Flow.Launcher.Infrastructure/UserSettings/Settings.cs
🚧 Files skipped from review as they are similar to previous changes (3)
  • Flow.Launcher/Themes/Base.xaml
  • Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
  • Flow.Launcher/ViewModel/MainViewModel.cs

Comment thread Flow.Launcher.Plugin/Result.cs
Comment on lines +10 to +39
[Test]
public void GivenAutoSetting_WhenAppIsDark_ThenActiveThemeIsADarkTheme()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("Auto", isDark: true);

ClassicAssert.AreEqual("VS Code Dark+", PreviewMarkdownScrollViewer.ActiveThemeName);
}

[Test]
public void GivenAutoSetting_WhenAppIsLight_ThenActiveThemeIsALightTheme()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("Auto", isDark: false);

ClassicAssert.AreEqual("VS Code Light", PreviewMarkdownScrollViewer.ActiveThemeName);
}

[Test]
public void GivenExplicitTheme_WhenApplied_ThenActiveThemeMatchesRegardlessOfAppScheme()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("OneDark", isDark: false);

ClassicAssert.AreEqual("One Dark", PreviewMarkdownScrollViewer.ActiveThemeName);
}

[Test]
public void GivenUnknownOrEmptySetting_WhenApplied_ThenFallsBackToAutoBehaviour()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("", isDark: false);

ClassicAssert.AreEqual("VS Code Light", PreviewMarkdownScrollViewer.ActiveThemeName);

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

Reset the global theme between tests.

Each test mutates PreviewMarkdownScrollViewer.ActiveTheme, and that state is static in Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs:18-33. Leaving it changed makes this fixture order-dependent with other preview tests. A small [SetUp]/[TearDown] that restores the previous theme would keep the suite isolated.

🤖 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 `@Flow.Launcher.Test/CodeHighlightThemeTest.cs` around lines 10 - 39, The tests
in CodeHighlightThemeTest are mutating the static theme state on
PreviewMarkdownScrollViewer, which can leak between tests and make the fixture
order-dependent. Add a small setup/teardown in this test class to save and
restore the previous PreviewMarkdownScrollViewer.ActiveTheme (or otherwise reset
the theme after each test), so each ApplyCodeHighlightTheme/ActiveThemeName
assertion runs in isolation.

Comment on lines +34 to +39
[Test]
public void GivenUnknownOrEmptySetting_WhenApplied_ThenFallsBackToAutoBehaviour()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("", isDark: false);

ClassicAssert.AreEqual("VS Code Light", PreviewMarkdownScrollViewer.ActiveThemeName);

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

Add a real unknown-setting case here.

The test name and the resolver contract both mention "unknown or empty", but this only exercises "". Passing an unrecognized value (for example "BogusTheme") is a separate fallback path worth covering so malformed persisted settings don't slip through.

🤖 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 `@Flow.Launcher.Test/CodeHighlightThemeTest.cs` around lines 34 - 39, The test
in `GivenUnknownOrEmptySetting_WhenApplied_ThenFallsBackToAutoBehaviour` only
covers the empty-string path, so add a separate assertion using a truly
unrecognized theme name such as a bogus value to exercise the unknown-setting
fallback in `PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme`. Keep the
existing empty case if needed, but ensure the test explicitly verifies that an
invalid non-empty setting still resolves to the default theme via
`PreviewMarkdownScrollViewer.ActiveThemeName`.

TrueCrimeDev and others added 2 commits June 29, 2026 08:14
The preview pane (a FlowDocumentScrollViewer) and the embedded code blocks used
the default chunky/light WPF scrollbar, which clashed with the dark UI. Scope a
thin #898989 scrollbar (matching Flow's main window) to the preview so both its
own scrollbar and the code-block scrollbars are consistent.

Note: visual change; pending an in-app confirmation screenshot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM
Still image of the markdown preview rendering a syntax-highlighted code block,
alongside the existing demo video.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Flow.Launcher/MainWindow.xaml">

<violation number="1" location="Flow.Launcher/MainWindow.xaml:541">
P2: The preview-pane scrollbar uses a local implicit style that hardcodes the thumb color (#898989) and bypasses the theme system, contradicting the comment calling it a "themed scrollbar". Flow Launcher has extensive per-theme styling in Themes/Base.xaml; hardcoded colors here will not adapt to different themes and cannot be overridden by theme files.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Flow.Launcher/MainWindow.xaml
TrueCrimeDev and others added 2 commits June 29, 2026 08:51
The first scrollbar fix only reached the preview pane's own scrollbar; the
AvalonEdit code-block scrollbars are nested inside the FlowDocument and don't
inherit it, so they kept the default chunky scrollbar (arrows and all). Theme
them from the editor's own style resources instead.

Note: visual change; pending an in-app confirmation screenshot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM
Relying on an implicit ScrollBar style alone left the code-block scrollbars
looking like the default (arrows, chunky). Give the editor's ScrollViewer an
explicit template: the horizontal scrollbar now sits in its own grid row (so it
can never overlap the last line of code) and both bars use the thin themed
ScrollBar style. Drops the bottom-padding workaround since the dedicated row
handles clearance now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Flow.Launcher/Themes/Base.xaml">

<violation number="1" location="Flow.Launcher/Themes/Base.xaml:617">
P2: Custom ScrollBar template omits DecreaseRepeatButton and IncreaseRepeatButton, breaking standard track/page-click behavior. Users can only drag the thumb, not click the track to page through content.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Flow.Launcher/Themes/Base.xaml Outdated
@TrueCrimeDev

Copy link
Copy Markdown
Author

Thanks for the thorough review @DavidGBrett — all points addressed, with replies in each thread. Quick map:

  • PreviewVisibility { Default, Never, Always } on Result replaces PreviewContentType.Hidden, decoupling pane visibility from content type (ea78b53)
  • Code-highlight theme is now a Settings → Theme dropdown, with a new VS Code Light theme and an Auto (match color scheme) default (38c27fd)
  • Preview pane reappears on reopen for forced previews, with regression tests (1504177)
  • Preview + code-block scrollbars now use Flow's thin themed style; the code-block horizontal scrollbar sits in its own grid row so it can't cover the last line (7092ad2, 1ade524)
  • Clarified the double preview-suppress check (renamed _restorePreviewAfterNeverResult + comments)

Two notes:

  1. The scrollbar / last-line fixes are visual — I'd appreciate a quick check that they render as expected on your setup.
  2. Behaviour change: content type no longer forces the pane open on its own; forced (e.g. markdown) previews now opt in via PreviewVisibility.Always. Migrated the Shorty plugin to match: Migrate to the PreviewVisibility API TrueCrimeDev/Flow.Launcher.Plugin.Shorty#4

@TrueCrimeDev

Copy link
Copy Markdown
Author

@Flow-Launcher — this one's ready for another look whenever a maintainer has time.

All of @DavidGBrett's review feedback has been addressed (responses in each thread, summary above) and the branch is green. The only items worth a second pair of eyes are the two scrollbar visuals, which I've called out in-thread. Happy to make any further changes — thanks!

TrueCrimeDev and others added 3 commits June 29, 2026 10:15
The ScrollBar ControlTemplate added for code-block scrollbars left its root
<Grid> unclosed, with ControlTemplate.Triggers nested inside it. Themes/*.xaml
are loose Content (runtime-loaded, not BAML-compiled), so the build didn't catch
it — but loading the theme at runtime throws XamlParseException, breaking the
preview (and the app theme). Close the Grid and move the triggers to the template
root. Caught by rendering the control to an image with the real Base.xaml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM
Renders the PreviewMarkdownScrollViewer through the real Base.xaml styles with the
dark-theme colours, showing the fixes from this branch: the outer pane scrollbar
and the code-block horizontal scrollbar both use Flow's thin themed bar (no
default arrows), and the last line of code stays clear of the horizontal scrollbar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM
The themed scrollbars used an opaque #898989 thumb at full width, which read as a
heavy solid grey bar against the dark UI (especially the outer pane bar when the
content only just overflowed). Switch to a thin, semi-transparent inset pill
(#66FFFFFF, 2px inset, 8px track) so both the pane and code-block scrollbars are
understated and don't compete with the content. Updates the proof render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TcJGQVjhYU3tEMi8FF4TM

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Flow.Launcher/MainWindow.xaml">

<violation number="1" location="Flow.Launcher/MainWindow.xaml:447">
P2: GridSplitter is no longer keyboard-focusable, removing keyboard-based pane resizing and visible focus indication</violation>
</file>

<file name="Flow.Launcher.Plugin/Result.cs">

<violation number="1" location="Flow.Launcher.Plugin/Result.cs:413">
P1: Mutable static `PreviewInfo.Default` can leak newly added `ContentType` changes across unrelated results.</violation>
</file>

<file name="Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs">

<violation number="1" location="Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs:45">
P2: Uncoalesced `Dispatcher.BeginInvoke` calls queue redundant expensive document traversals during rapid markdown preview updates.</violation>

<violation number="2" location="Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs:210">
P1: RetintEditor mutates shared AvalonEdit highlighting definitions, causing global theming side effects</violation>
</file>

<file name="Flow.Launcher/ViewModel/MainViewModel.cs">

<violation number="1" location="Flow.Launcher/ViewModel/MainViewModel.cs:1152">
P2: `SuppressPreviewAsync()` is fire-and-forgotten here. If it throws (e.g., in `CloseExternalPreviewAsync`), the exception will go unobserved and `_previewSuppressedBySelectedResult`/`_previewAutoOpenedBySelectedResult` may be left inconsistent. Either make `TogglePreview` async and await the call, use an async relay command, or route through a centralized fire-and-forget helper that observes and logs exceptions.</violation>

<violation number="2" location="Flow.Launcher/ViewModel/MainViewModel.cs:1152">
P2: Always Preview re-arms the preview pane after a manual close when passing through a hidden/Never result. SuppressPreviewAsync cannot distinguish a pane hidden by manual TogglePreview from one that was never opened, so the `else if (Settings.AlwaysPreview)` branch unconditionally sets `_restorePreviewAfterNeverResult = true` even after the user explicitly closed the preview. The next non-Never selection then reopens the pane via UpdatePreviewAsync, contradicting the PR's stated behavior that manual close should persist.</violation>

<violation number="3" location="Flow.Launcher/ViewModel/MainViewModel.cs:1220">
P2: ResetPreview's ForcePreviewPane branch can leave an existing external preview open because it bypasses HidePreview.</violation>
</file>

<file name="Flow.Launcher/Themes/Base.xaml">

<violation number="1" location="Flow.Launcher/Themes/Base.xaml:726">
P2: Setting `TextDecorations="None"` unconditionally removes the only non-color cue for hyperlinks. Users with color-vision deficiencies or low-contrast themes may not be able to identify links. Consider adding a trigger to restore the underline on hover or focus for better accessibility.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Flow.Launcher/Themes/Base.xaml Outdated
Comment thread Flow.Launcher/MainWindow.xaml Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Flow.Launcher/MainWindow.xaml">

<violation number="1" location="Flow.Launcher/MainWindow.xaml:447">
P2: GridSplitter is no longer keyboard-focusable, removing keyboard-based pane resizing and visible focus indication</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Flow.Launcher/Themes/Base.xaml Outdated
@DavidGBrett DavidGBrett marked this pull request as draft July 5, 2026 13:54
@DavidGBrett

Copy link
Copy Markdown
Contributor

As the shorty plugin is out of date with the latest changes, and to verify this works with JsonRPC, I built my own python plugin to test this:

https://github.com/DavidGBrett/Flow.Launcher.Plugin.MarkdownPreviewTest

Not in the plugin store yet - might not be a need to add it
So just manually install from the releases if anyone wants to use this

@DavidGBrett DavidGBrett self-assigned this Jul 5, 2026
@DavidGBrett DavidGBrett added the kind/ui related to UI, icons, themes, etc label Jul 5, 2026
@DavidGBrett

DavidGBrett commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Marking this as draft as I plan to push some more commits later.
There are a few more things to look into and clean up.

Thanks again for your contribution @TrueCrimeDev

@TrueCrimeDev

Copy link
Copy Markdown
Author

Feel free to send me more fixes, I am
happy to continue to working on it to make it easier on your end.

@DavidGBrett

Copy link
Copy Markdown
Contributor

Ok then, thank you

Don't worry about the theming for the horizontal scrollbar I'll add that to the built in themes soon

So far the biggest issue I see right now is:

  • Arrow keys eventually move the curser in the code block but still change selected query or go to context menu first - need to disable those actions while focused on the code block

API Design wise:

  • I think its better to rename the default preview content type from Text to ImageWithText since it actually primarily shows a preview image with text below

Beyond that there's some cleanup to do such as:

  • Removing the doc/assets folder and images - if you can re-render them after the final changes that would be great to include in the PR description, but there's no need to include them in the repo directly, only adds maintenance

Beyond that its checking over the issues raised by the bots to see if they are valid
For example the one about hyperlink underlines seems important

TrueCrimeDev and others added 3 commits July 5, 2026 16:17
- Base.xaml: make horizontal scrollbar theme-aware by adding
  HorizontalThumbStyle/HorizontalScrollBarStyle dynamic resource keys
  (BaseHorizontalScrollBarStyle now uses DynamicResource HorizontalThumbStyle;
  code-block ScrollViewer uses DynamicResource HorizontalScrollBarStyle so
  custom themes can override it, matching the vertical ScrollBarStyle pattern)

- Base.xaml: restore hyperlink underline on hover/focus so users who rely
  on non-colour cues (colour-vision deficiencies, low-contrast themes) can
  still identify links

- PreviewMarkdownScrollViewer: coalesce Dispatcher.BeginInvoke calls —
  rapid Markdown changes no longer queue redundant document traversals;
  a second BeginInvoke is skipped while one is already pending

- PreviewMarkdownScrollViewer: override Equals/GetHashCode on
  SolidHighlightingBrush so colour comparisons work by value, preventing
  unnecessary re-tints and redraws when the theme colour is unchanged

- MainViewModel: make TogglePreviewAsync properly async so SuppressPreviewAsync
  is awaited instead of fire-and-forgotten; exceptions are no longer unobserved

- MainViewModel: add _previewManuallyClosed flag — when the user explicitly
  closes the preview via F1 the AlwaysPreview restore path no longer re-arms
  when the selection passes through a Never result, so the pane stays closed
  until the user re-opens it (regression test added)

- MainViewModel: ResetPreview now closes an open external preview before
  calling ShowInternalPreview in the ForcePreviewPane branch, preventing both
  preview types from being visible simultaneously

- Tests: isolate CodeHighlightThemeTest with SetUp/TearDown so static
  ActiveTheme does not bleed across tests; add coverage for an unrecognised
  (non-empty) theme setting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MainWindow: while a markdown-preview code block owns keyboard focus,
  skip the window's key handler so arrows move the editor caret instead
  of changing the selected result or opening the context menu
  (PreviewMarkdownScrollViewer.IsCodeBlockFocused walks the focus tree
  for a TextEditor ancestor)

- PreviewMarkdownScrollViewer: stop mutating the shared AvalonEdit
  highlighting definition in RetintEditor. Colours are now applied through
  a per-editor ThemedHighlightingColorizer that wraps the highlighter, so
  the tint can no longer leak into other consumers of the same definition.
  Theming at the highlighter level also recolours styling-free named
  colours (e.g. C#'s "Punctuation") that the base colorizer would skip.
  The colorizer is inserted at index 0 so the selection colorizer still
  wins and selected code keeps a legible selection foreground.

- Result: rename PreviewContentType.Text to ImageWithText (wire name
  "imageWithText") to reflect that the default preview is an image with
  the text description below it; update defaults and docs

- Base.xaml: keep hyperlink underlines instead of suppressing them, so
  links retain a non-colour cue for accessibility

- remove docs/assets preview images from the repo to avoid ongoing
  maintenance; they can be re-rendered for the PR description

- tests: cover the default (imageWithText) JSON-RPC content type

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Mpb5d92pu8wiAT2niw5d6
@TrueCrimeDev

Copy link
Copy Markdown
Author

@DavidGBrett thanks for the detailed pass — pushed 8636fb2c0 addressing the follow-up list:

  • Code-block key handling — while a code block owns keyboard focus, the window's key handler now bows out, so arrows move the caret inside the block instead of changing the selected result or opening the context menu. Fixes the reported arrow-key behaviour.
  • API renamePreviewContentType.TextImageWithText (wire name imageWithText), since the default preview is the image with the text description below it. Defaults, docs and tests updated.
  • docs/assets — removed the two preview PNGs from the repo; I'll re-render them for the PR description after the final changes rather than keep them tracked.
  • Hyperlink underlines — kept the underline (dropped the TextDecorations="None") so links retain a non-colour cue.

While I was in there I also folded in a still-valid bot finding: RetintEditor no longer mutates the shared AvalonEdit highlighting definition — colours now apply through a per-editor ThemedHighlightingColorizer, so the tint can't leak into other editors. It's inserted at index 0 so the selection colorizer still wins and selected code stays legible.

One item I deliberately left for you: window-level KeyBindings that the read-only editor doesn't consume (Alt+Home/End, the context-menu hotkey, Ctrl/Alt+Enter, modifier+digit) still fire while a code block is focused — they're matched during bubbling, separate from the key handler above. A correct fix needs per-gesture decisions (which of Escape/F5/Ctrl+O/Alt+Home/Enter/PageUp to block vs keep) and blanket suppression would break code-block scrolling, so that one felt like yours to shape. Happy to take it on if you have a behaviour in mind.

Intentionally did not make this scrollbar thin even when the theme made the vertical one thin as its very hard to grab the scrollbar in if thin.
For the current use case of the code blocks there is no work around of using the mousewheel to scroll.
These are treated as web urls - no support for local relative markdown links provided yet
@DavidGBrett

DavidGBrett commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Thanks for sorting those out
I will look further into the key binding concerns you mentioned - the current behavior seems fine to me so far anyways.

Right now I've noticed an uncaught crash seemingly happening when the preview changes quickly so I'm focusing on trying to figure out what's causing that
To test it out you can use my own plugin which renders the query as the first line of the markdown so changing that changes the preview.

@DavidGBrett

DavidGBrett commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

The crash is not easy to reliably replicate but it seems that this is actually caused by a bug in how MdXaml handles image loading
at MdXaml.ImageLoaderManager.OpenStreamAsync(Uri resourceUrl)
Some unhandled http request failure

My test plugin included a markdown image link
I only added that recently which explains why im only seeing it now.

The reason why this was crashing on my build was that for some reason in debug mode the top level exception handlers are disabled - unfortunately I would need to discuss with the team and fix that in a sperate PR so will have to leave this unaddressed here

It's less than ideal to leave it like this, but at least in release mode the crash shouldn't happen for normal users...

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

Labels

enhancement New feature or request kind/ui related to UI, icons, themes, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants