Add markdown preview pane with per-result content types#4529
Add markdown preview pane with per-result content types#4529TrueCrimeDev wants to merge 21 commits into
Conversation
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>
There was a problem hiding this comment.
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
PreviewContentTypeto pluginResult.PreviewInfoand surface flags inResultViewModelfor markdown/hidden handling. - Implement
PreviewMarkdownScrollViewer+ theme resources to render markdown (with AvalonEdit fenced code blocks) in the preview pane. - Update
MainViewModelpreview-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.
| protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) | ||
| { | ||
| base.OnPropertyChanged(e); | ||
|
|
||
| if (e.Property == MarkdownProperty) | ||
| { | ||
| UpdateDocumentPageWidth(ActualWidth); | ||
| _ = Dispatcher.BeginInvoke(ApplyMarkdownCompatibilityFixes, DispatcherPriority.Loaded); | ||
| } | ||
| } |
| [RelayCommand] | ||
| private void TogglePreview() | ||
| { | ||
| if (ShouldSuppressPreview(PreviewSelectedItem)) | ||
| { | ||
| _ = SuppressPreviewAsync(); | ||
| return; | ||
| } |
| public void ResetPreview() | ||
| { | ||
| if (ShouldSuppressPreview(PreviewSelectedItem)) | ||
| { | ||
| _ = SuppressPreviewAsync(); | ||
| return; | ||
| } |
| <Border.Style> | ||
| <Style TargetType="Border"> | ||
| <Style.Triggers> | ||
| <DataTrigger Binding="{Binding IsMarkdownPreview}" Value="True"> | ||
| <Setter Property="MaxHeight" Value="600" /> | ||
| </DataTrigger> | ||
| </Style.Triggers> | ||
| </Style> | ||
| </Border.Style> |
There was a problem hiding this comment.
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
| Background="Transparent" | ||
| ShowsPreview="True" | ||
| FocusVisualStyle="{x:Null}" | ||
| IsTabStop="False" |
There was a problem hiding this comment.
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>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds markdown preview rendering, preview visibility controls, code highlighting theme selection, UI wiring, and supporting tests across the preview pipeline. ChangesMarkdown Preview Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
Flow.Launcher.Test/PreviewMarkdownScrollViewerTest.cs (1)
28-31: 💤 Low valueConsider a type check before casting
Hyperlink.Foreground.Line 29 casts
hyperlink.ForegroundtoSolidColorBrushwithout verifying the type. While the test creates a style withSolidColorBrush, a pattern match orischeck 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 winConsider guarding reflection calls against null to improve test diagnostics.
The helper methods use reflection to access private
MainViewModelmembers by string name (backing fields, static fields, methods). If any of these members are renamed or removed during refactoring, the reflection calls will throwNullReferenceExceptionat 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
GetMethodand otherGetFieldcalls.🤖 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
📒 Files selected for processing (13)
Flow.Launcher.Plugin/Result.csFlow.Launcher.Test/MainViewModelPreviewTest.csFlow.Launcher.Test/Plugins/JsonRPCPluginTest.csFlow.Launcher.Test/PreviewMarkdownScrollViewerTest.csFlow.Launcher.Test/PreviewMarkdownStyleTest.csFlow.Launcher/Flow.Launcher.csprojFlow.Launcher/MainWindow.xamlFlow.Launcher/Resources/Controls/CodeHighlightTheme.csFlow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.csFlow.Launcher/Themes/Base.xamlFlow.Launcher/ViewModel/MainViewModel.csFlow.Launcher/ViewModel/ResultViewModel.csFlow.Launcher/packages.lock.json
| var brush = new SolidHighlightingBrush(target); | ||
| if (!Equals(color.Foreground, brush)) | ||
| { | ||
| color.Foreground = brush; | ||
| changed = true; | ||
| } | ||
| } | ||
|
|
||
| if (changed) | ||
| { | ||
| editor.TextArea.TextView.Redraw(); | ||
| } |
There was a problem hiding this comment.
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.
| 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).
| return; | ||
| } | ||
|
|
||
| if (_previewSuppressedBySelectedResult) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
|
Incase anyone else is reviewing this, heres the link to the plugin @TrueCrimeDev made using this feature: |
…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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
📒 Files selected for processing (14)
Flow.Launcher.Infrastructure/UserSettings/Settings.csFlow.Launcher.Plugin/Result.csFlow.Launcher.Test/CodeHighlightThemeTest.csFlow.Launcher.Test/MainViewModelPreviewTest.csFlow.Launcher.Test/Plugins/JsonRPCPluginTest.csFlow.Launcher/Languages/en.xamlFlow.Launcher/MainWindow.xaml.csFlow.Launcher/Resources/Controls/CodeHighlightTheme.csFlow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.csFlow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.csFlow.Launcher/SettingPages/Views/SettingsPaneTheme.xamlFlow.Launcher/Themes/Base.xamlFlow.Launcher/ViewModel/MainViewModel.csFlow.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
| [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); |
There was a problem hiding this comment.
📐 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.
| [Test] | ||
| public void GivenUnknownOrEmptySetting_WhenApplied_ThenFallsBackToAutoBehaviour() | ||
| { | ||
| PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("", isDark: false); | ||
|
|
||
| ClassicAssert.AreEqual("VS Code Light", PreviewMarkdownScrollViewer.ActiveThemeName); |
There was a problem hiding this comment.
📐 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`.
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
There was a problem hiding this comment.
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
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
There was a problem hiding this comment.
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
|
Thanks for the thorough review @DavidGBrett — all points addressed, with replies in each thread. Quick map:
Two notes:
|
|
@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! |
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
|
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 |
|
Marking this as draft as I plan to push some more commits later. Thanks again for your contribution @TrueCrimeDev |
|
Feel free to send me more fixes, I am |
|
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:
API Design wise:
Beyond that there's some cleanup to do such as:
Beyond that its checking over the issues raised by the bots to see if they are valid |
- 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
|
@DavidGBrett thanks for the detailed pass — pushed
While I was in there I also folded in a still-valid bot finding: One item I deliberately left for you: window-level |
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.
…ent PreviewContentType modes work
These are treated as web urls - no support for local relative markdown links provided yet
|
Thanks for sorting those out 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 |
What
Results can now declare how their preview description is rendered via
Result.Preview.ContentType:text(default) — the classic plain-text preview, completely unchangedmarkdown— renders the description as markdown in the preview pane, with syntax-highlighted code blockshidden— suppresses the preview pane for that result, even when Always Preview is onSelecting 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/Descriptiontext. This lets them opt into a proper rendered preview per result without affecting any existing plugin: every current result keeps the defaulttextbehavior.How
PreviewMarkdownScrollViewercontrol built on MdXaml (already a dependency) with code blocks highlighted via AvalonEdit (the one new package) and aCodeHighlightThememapped to the active themePreviewMarkdownStyleresource inThemes/Base.xamlso themes can restyle the rendered markdownPreviewContentTypeenum onPreviewInfo, serialized as lowercase strings — JSON-RPC plugins opt in with"preview": { "contentType": "markdown", "description": "**hello**" }MainViewModel.UpdatePreviewAsyncwith state for result-suppressed and auto-opened panesDemo
📹 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-RPCcontentTypedeserialization (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
PreviewVisibility(default,never,always); one‑shot restore afternever; auto‑open/close foralways; manual close persists even with Always Preview on;ResetPreviewrestores forced panes and closes external before opening internal.imageWithText(JSON‑RPC wire nameimageWithText).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.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.Result.Preview.ContentType(imageWithText,markdown) andResult.PreviewVisibility(default,never,always) with JSON‑RPC wire values.PreviewMarkdownScrollViewerusingMdXaml+AvalonEdit, with AHK→C++ highlighting aliases, per‑editor highlighter theming, and dynamic retinting viaCodeHighlightTheme. 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.PreviewMarkdownStylefor consistent markdown styling;ResultViewModelflags:IsMarkdownPreview,HidePreviewPane,ForcePreviewPane.PreviewVisibility.Never).AvalonEdit, per‑editor colorizers, and new style/theme resources. Controls are created on demand.MainViewModelPreviewTest(pane gating, manual‑close persistence, reopen fixes),PreviewMarkdownScrollViewerTest,PreviewMarkdownStyleTest,CodeHighlightThemeTest(Auto resolver + unrecognized setting),JsonRPCPluginTest(contentTypedefaultimageWithTextandmarkdown;previewVisibilityround‑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.