Add markdown preview type and per result preview visibility control#4529
Add markdown preview type and per result preview visibility control#4529TrueCrimeDev wants to merge 38 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.
There was a problem hiding this comment.
All reported issues were addressed across 13 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
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
|
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
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
|
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 |
Stop blocking all keys when a code block is focused. Only arrows, page keys, home/end are captured. Everything else passes through normally. A new method IsCodeBlockNavigationKey is used to check if other key press should be captured
Replace the three-flag state-tracking system (_restorePreviewAfterNeverResult, previewAutoOpenedBySelectedResult, _previewManuallyClosed) with a single bool? _manualPreviewOverride. UpdatePreviewAsync now re-evaluates the pane state from scratch each time through ShouldShowPreview(), which checks: 1. Never result -> hide 2. Always result -> show 3. _manualPreviewOverride ?? AlwaysPreview TogglePreviewAsync just flips the override and delegates to UpdatePreviewAsync. ResetPreview clears the override and re-evaluates. The preview pane is hidden when the window closes (in Hide()) to prevent flicker on reopen. Tests were updated to reflect changes in how manual toggle is set and the lack of previous state tracking
… sync WPF boundaries All async preview methods in MainViewModel are now properly awaited. Fire-and-forget with ContinueWith+OnlyOnFaulted logging is moved to the two MainWindow.xaml.cs call sites where WPF sync context prevents await.
…ight Replaced the hardcoded 600px max height on the markdown preview (and the duplicate on the other custom preview) with a binding to the result list height, then moved both MinHeight and MaxHeight to the shared parent Grid to eliminate repetition.
- Add ResetPreviewAsync tests with stale external preview visible - add setup correctness assertions in all ResetPreviewAsync tests - Add ReadManualPreviewOverride helper for reading private state
…rdown in CodeHighlightThemeTest Ugly but for now this is better than the previous method that only worked by coincidence due to falling back to matching defaults - otherwise it was wrong
This more clearly and explicitly reflects how it works rather than just trying to imply it was how it works before this change It still remains the default setting and works the same as before. We now have Optional, Never & Always as the PreviewVisibility enum values
…as their tooltip to better reflect the different preview visibility types They now only apply to results with preview visibility set to optional so need to clarify this
They were only used in one place so just inlined the access of the Result.PreviewVisibility
There was a problem hiding this comment.
I've polished it up so I believe it should be good enough to merge now
A good few changes in the mean time, but I've updated the PR description so hopefully they should be clear.
If anyone else wants to review this remember to use the release build - dev build can crash as described above but that's not a concern for this PR I believe
There was a problem hiding this comment.
1 issue found across 40 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.Test/MainViewModelPreviewTest.cs">
<violation number="1" location="Flow.Launcher.Test/MainViewModelPreviewTest.cs:331">
P2: Observation: The test helper creates a MainViewModel with RuntimeHelpers.GetUninitializedObject and then writes directly to the compiler-generated backing field "<Settings>k__BackingField". That bypasses the MainViewModel constructor and ties the test to a private backing-field name, which produces unrealistic objects and brittle tests that can break under refactoring.
Recommendation: Create the view model through a real constructor or a deliberate test seam instead of using GetUninitializedObject and touching backing fields. Options, in order of preference:
- Add an internal or protected constructor or a factory on MainViewModel that accepts Settings (and mark it InternalsVisibleTo the test assembly) and call that from tests.
- If a non-public constructor already exists, use Activator.CreateInstance(typeof(MainViewModel), nonPublic: true) so the constructor runs, then set observable properties via public API.
- As a last resort, if you must use reflection, set the public or non-public property (via GetProperty) rather than referencing the compiler-generated backing field name.
Making this change will ensure test instances reflect real runtime initialization and avoid brittle coupling to compiler-generated names.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
|
|
||
| private static MainViewModel CreatePreviewViewModel(Settings settings, int resultAreaColumn) | ||
| { | ||
| var viewModel = (MainViewModel)RuntimeHelpers.GetUninitializedObject(typeof(MainViewModel)); |
There was a problem hiding this comment.
P2: Observation: The test helper creates a MainViewModel with RuntimeHelpers.GetUninitializedObject and then writes directly to the compiler-generated backing field "k__BackingField". That bypasses the MainViewModel constructor and ties the test to a private backing-field name, which produces unrealistic objects and brittle tests that can break under refactoring.
Recommendation: Create the view model through a real constructor or a deliberate test seam instead of using GetUninitializedObject and touching backing fields. Options, in order of preference:
- Add an internal or protected constructor or a factory on MainViewModel that accepts Settings (and mark it InternalsVisibleTo the test assembly) and call that from tests.
- If a non-public constructor already exists, use Activator.CreateInstance(typeof(MainViewModel), nonPublic: true) so the constructor runs, then set observable properties via public API.
- As a last resort, if you must use reflection, set the public or non-public property (via GetProperty) rather than referencing the compiler-generated backing field name.
Making this change will ensure test instances reflect real runtime initialization and avoid brittle coupling to compiler-generated names.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Test/MainViewModelPreviewTest.cs, line 331:
<comment>Observation: The test helper creates a MainViewModel with RuntimeHelpers.GetUninitializedObject and then writes directly to the compiler-generated backing field "<Settings>k__BackingField". That bypasses the MainViewModel constructor and ties the test to a private backing-field name, which produces unrealistic objects and brittle tests that can break under refactoring.
Recommendation: Create the view model through a real constructor or a deliberate test seam instead of using GetUninitializedObject and touching backing fields. Options, in order of preference:
- Add an internal or protected constructor or a factory on MainViewModel that accepts Settings (and mark it InternalsVisibleTo the test assembly) and call that from tests.
- If a non-public constructor already exists, use Activator.CreateInstance(typeof(MainViewModel), nonPublic: true) so the constructor runs, then set observable properties via public API.
- As a last resort, if you must use reflection, set the public or non-public property (via GetProperty) rather than referencing the compiler-generated backing field name.
Making this change will ensure test instances reflect real runtime initialization and avoid brittle coupling to compiler-generated names.</comment>
<file context>
@@ -0,0 +1,389 @@
+
+ private static MainViewModel CreatePreviewViewModel(Settings settings, int resultAreaColumn)
+ {
+ var viewModel = (MainViewModel)RuntimeHelpers.GetUninitializedObject(typeof(MainViewModel));
+ typeof(MainViewModel)
+ .GetField("<Settings>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)
</file context>
There was a problem hiding this comment.
All reported issues were addressed across 35 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Removed mention of previews being optional by default - both from those names and the tooltips/subtitles - as it was deemed unnecessary The tooltips/subtitles still mention that this can be ignored by certain plugins though.
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…yChanged event handlers
1d73a4a to
9ad461c
Compare
…ted and clone now does a deep copy Solves Flow-Launcher#4561
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
What
The preview pane can now render formatted markdown with syntax-highlighted code blocks. Plugins opt in by setting
Result.Preview.ContentTypetoMarkdownand providing a markdown description.A new
Result.PreviewVisibilitylets plugins control whether the preview pane shows:And
PreviewContentTypecontrols how the preview is rendered:Existing plugins are unaffected as all defaults match current behavior.
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.The visibility controls exist so plugins can make sure users see the preview when it matters, without relying on them to manually toggle it or change settings. Results that exist primarily for their rendered content (like markdown) always show their preview; results with nothing useful to preview can hide it.
Details
Markdown rendering — Built on MdXaml (already a dependency) with code blocks highlighted through AvalonEdit (the one new package). A
CodeHighlightThemesetting under Settings > Theme lets users pick a syntax theme or use Auto to match the app colour scheme. Bundled themes include VS Code Dark+, VS Code Light, One Dark, and Catppuccin Macchiato.JSON-RPC — Both
contentTypeandpreviewVisibilityare added as keys on the result object (withcontentTypenested inpreview):{ "preview": { "contentType": "markdown", "description": "**hello**" }, "previewVisibility": "always" }Themed horizontal scrollbars — Code blocks need horizontal scrolling, so horizontal scrollbar styles were added to Base.xaml and every built-in theme. Minimum thickness is 5px since horizontal bars are harder to click and have no scrollwheel alternative.
New dependency —
AvalonEdit6.3.0.90.Tests
Full suite passes (256/256), including new coverage: pane gating rules (
MainViewModelPreviewTest), markdown control behavior (PreviewMarkdownScrollViewerTest,PreviewMarkdownStyleTest), and JSON-RPCcontentTypedeserialization (JsonRPCPluginTest).Demo
Slightly outdated but still a good demonstration
📹 markdown-preview-demo.mp4
Summary by cubic
Adds a themed markdown preview pane with per-result visibility and a selectable code highlight theme. Refines preview behavior, accessibility, theming, and error handling; existing results keep the default
imageWithTextpreview.Summary of changes
Previewobject;Result.Clone()deep-copiesPreviewto prevent shared state (fixes BUG: Mutable PreviewInfo.Default shallow copying #4561)._manualPreviewOverride: Never → hide, Always → show, else_manualPreviewOverride ?? AlwaysPreview. Toggle flips; Reset clears and re‑evaluates. Pane hides on window close; manual close persists even if Always Preview is on; forced previews restore on window reopen.imageWithText;PreviewVisibility.Defaultrenamed toOptional.HorizontalScrollBarStyleandHorizontalThumbStyle; explicit code‑block scrollbar templates prevent last‑line overlap; styles added to all built‑in themes with subtle thumbnails.UpdatePreviewAsynccalls inMainViewModelPropertyChanged handlers are wrapped in try/catch; async preview methods are awaited (fire‑and‑forget only at WPF boundaries). Reset closes any external preview before opening an internal one.Result.Preview.ContentType(imageWithText,markdown) andResult.PreviewVisibility(optional,never,always) with JSON‑RPC string values.PreviewMarkdownScrollViewerusing@MdXaml+AvalonEdit, per‑editor highlighting colorizer/theming, language aliases, themed code‑block scrollbars, andIsCodeBlockFocused.Settings.CodeHighlightTheme(default "Auto") with a Theme page dropdown; Auto tracks app light/dark. Bundled themes: VS Code Light, VS Code Dark+, Catppuccin Macchiato, One Dark. Applies on startup and on theme change.PreviewMarkdownStyleand base/theme resources for consistent markdown styling and horizontal scrollbars. Dependency:AvalonEdit6.3.0.90.PreviewVisibility.Never).HidePreviewPane/ForcePreviewPanewrappers (inlinePreviewVisibilityaccess).IsSafeSchemehelper (inlined; links restricted to http/https).AvalonEdit, per‑editor colorizers, and style/theme resources. Controls are created on demand.MainViewModelPreviewTest(override logic, gating, restore on reopen, reset with stale external preview),PreviewMarkdownScrollViewerTest,PreviewMarkdownStyleTest,CodeHighlightThemeTest(Auto + unrecognized setting),JsonRPCPluginTest(imageWithTextdefaults andpreviewVisibilityround‑trips withoptional/never/always).Release Note
You can see rich markdown previews with clickable links and highlighted code, choose a code theme, and control which results show a preview.
Written for commit 9a7f95d. Summary will update on new commits.