Skip to content

Release 0.3.0#6

Merged
devohmycode merged 3 commits intomainfrom
0.3.0
Mar 26, 2026
Merged

Release 0.3.0#6
devohmycode merged 3 commits intomainfrom
0.3.0

Conversation

@devohmycode
Copy link
Owner

@devohmycode devohmycode commented Mar 26, 2026

Summary

  • Startup options: start with Windows, start minimized to tray
  • Sorting & compact cards: sort notes by recent/created/alpha/color/size, compact card view toggle
  • Clipboard monitor: background clipboard monitoring
  • About panel: app info, developer credits, GitHub and Buy Me a Coffee links
  • Icon helper: extract and cache application icons from executables
  • Auto-update system: check for updates via GitHub Releases with CPU/CUDA variant detection, in-app download and install

Test plan

  • Verify startup options (start with Windows, start minimized)
  • Test sorting modes and compact cards toggle
  • Open About panel and verify links open correctly
  • Test update check from About panel
  • Verify clipboard monitor functionality

Summary by CodeRabbit

Release Notes

  • New Features

    • Startup options: launch with Windows and start minimized.
    • Customizable note sorting: recent, created, alphabetical, color, or size.
    • Compact card view toggle.
    • Quick paste hotkey (Ctrl+Alt+V) to create notes from clipboard.
    • Folder-based note organization.
    • Inline note expansion and editing.
    • Note linking with [[noteTitle]] syntax.
    • Automatic update checking with download capability.
    • Clipboard source tracking displays application source on notes.
  • Bug Fixes

    • Drag-and-drop support added to note editors.

@qodo-code-review
Copy link

Review Summary by Qodo

Release 0.3.0 - Inline editing, folders, note linking, and auto-updates

✨ Enhancement

Grey Divider

Walkthroughs

Description
• **Inline note editing**: Expand and edit notes directly in the main list without opening separate
  windows
• **Folder/category system**: Organize notes into folders with dedicated view modes and management
  UI
• **Sorting options**: Sort notes by recent, created date, alphabetical order, color, or size with
  persistent preferences
• **Compact card view**: Toggle between full and compact note card layouts
• **Note linking**: Create links between notes using [[note name]] syntax with Ctrl+Click
  navigation
• **Clipboard monitoring**: Track source application and window title when notes are created
• **Auto-update system**: Check for updates via GitHub Releases with CPU/CUDA variant detection and
  in-app download/install
• **Startup options**: Start application with Windows and optionally minimize to system tray on
  launch
• **About panel**: Display app information, version, update checker, and links to GitHub and Buy Me
  a Coffee
• **Icon extraction utility**: Extract and cache application icons from executables for clipboard
  source tracking
• **Export functionality**: Export notes as Markdown or plain text with file save and clipboard
  options
• **Drag-and-drop images**: Insert images into notes by dragging from files or browser
• **Global hotkey**: Added Ctrl+Alt+V shortcut for paste-as-note feature
• **Localization**: Added 60+ new strings in English and French for all new features
• **Installer updates**: Version bump to 0.3.0 with application restart support during installation
Diagram
flowchart LR
  A["Clipboard Monitor"] -->|"Track source app"| B["Note Entry"]
  B -->|"Organize"| C["Folder System"]
  C -->|"Display"| D["Main Window"]
  D -->|"Sort by"| E["Recent/Created/Alpha/Color/Size"]
  D -->|"Edit inline"| F["Rich Text Editor"]
  F -->|"Link notes"| G["Note Linking"]
  D -->|"Toggle"| H["Compact View"]
  I["Update Service"] -->|"Check GitHub"| J["Auto-Update"]
  J -->|"Download & Install"| K["New Version"]
  L["Startup Options"] -->|"Configure"| M["App Launch"]
  M -->|"Minimize to tray"| N["System Tray"]
  O["Icon Helper"] -->|"Extract icons"| P["Source App Display"]
Loading

Grey Divider

File Changes

1. MainWindow.xaml.cs ✨ Enhancement +983/-6

Major feature release with inline editing, folders, and auto-updates

• Added clipboard monitoring to track source application when notes are created
• Implemented inline note expansion with rich text editing directly in the main list
• Added auto-update system with background checking and in-app download/install
• Implemented folder/category system for organizing notes with dedicated view modes
• Added sorting preferences (recent, created, alphabetical, color, size) and compact card view
 toggle
• Added note linking feature with [[note name]] syntax and Ctrl+Click navigation
• Integrated startup options (start with Windows, start minimized to tray)

MainWindow.xaml.cs


2. NoteWindow.xaml.cs ✨ Enhancement +649/-0

Note linking, export, and folder management features

• Added note linking detection and insertion with hidden ID encoding
• Implemented Ctrl+Click handler to navigate between linked notes
• Added export functionality (Markdown and plain text) with file save and clipboard options
• Added folder management menu for organizing notes into categories
• Implemented drag-and-drop image insertion from files and browser
• Added ReloadFromDisk() method to sync with inline editor changes

NoteWindow.xaml.cs


3. ActionPanel.cs ✨ Enhancement +367/-1

Settings UI enhancements for sorting, startup, and about panel

• Added sort mode selector with five sorting options (recent, created, alphabetical, color, size)
• Added compact cards toggle to switch between full and compact note card layouts
• Added startup options sub-panel for Windows startup and minimized launch settings
• Added About panel with app info, version, update checker, and external links (GitHub, Buy Me a
 Coffee)
• Extended CreateSettings() method signature with new parameters for startup and sorting options

ActionPanel.cs


View more (11)
4. Lang.cs 📝 Documentation +84/-0

Localization strings for new features in English and French

• Added 60+ new localization strings for folders, sorting, export, startup, updates, and about
 features
• Provided translations in both English and French for all new UI elements
• Added strings for note linking, clipboard operations, and update notifications

Lang.cs


5. IconHelper.cs ✨ Enhancement +211/-0

Icon extraction and caching utility for clipboard source tracking

• New utility class to extract and cache application icons from executables
• Implements memory and disk caching to avoid repeated extraction
• Uses P/Invoke to extract icon pixels from .exe files and encode as PNG
• Provides LoadIconAsync() method to load icons into XAML Image controls

IconHelper.cs


6. UpdateService.cs ✨ Enhancement +136/-0

Auto-update system with GitHub release integration

• New service for checking GitHub releases and downloading updates
• Detects CPU vs CUDA build variant and downloads appropriate installer
• Implements progress reporting during download and silent installer launch
• Compares semantic versions to determine if update is available

UpdateService.cs


7. ClipboardMonitor.cs ✨ Enhancement +105/-0
 Clipboard monitoring to track note source application

ClipboardMonitor.cs


8. NotesManager.cs ✨ Enhancement +42/-3

Note manager enhancements for folders, sorting, and clipboard source tracking

• Added GetByTitle() and SearchByTitle() methods for note lookup and search
• Added GetAllFolders() to retrieve distinct folder names from notes
• Added UpdateNoteFolder() to assign or update note folder assignment
• Enhanced GetSorted() to support multiple sort modes (recent, created, alphabetical, color, size)
• Added SourceExePath, SourceTitle, and Folder properties to NoteEntry class

NotesManager.cs


9. AppSettings.cs ✨ Enhancement +102/-0

Settings persistence for startup, sorting, and compact view options

• Added startup options persistence (start with Windows via registry, start minimized)
• Added compact cards preference storage
• Added sort mode preference storage
• Implemented registry integration for Windows startup configuration

AppSettings.cs


10. HotkeyService.cs ✨ Enhancement +2/-0

Global hotkey support for paste-as-note feature

• Added HOTKEY_PASTE_NOTE constant for new global hotkey
• Added default shortcut entry for paste-as-note (Ctrl+Alt+V)

HotkeyService.cs


11. App.xaml.cs ✨ Enhancement +6/-0

Start minimized to tray support

• Added logic to hide main window on startup if startMinimized setting is enabled
• Allows app to launch directly to system tray

App.xaml.cs


12. installer.iss ⚙️ Configuration changes +4/-1

Installer configuration for version 0.3.0 release

• Updated version from 0.2.0 to 0.3.0
• Added CloseApplications directives to close running NoteUI instances during installation
• Added RestartApplications to relaunch app after installation

installer.iss


13. MainWindow.xaml ✨ Enhancement +19/-2

XAML UI updates for update banner and inline note collapse

• Added update banner UI element with download progress display
• Added PointerPressed event handler to collapse expanded notes when scrolling
• Set transparent background on scroll viewer and notes list for consistency

MainWindow.xaml


14. NoteWindow.xaml ✨ Enhancement +6/-0

Enable drag-and-drop functionality for note editors

• Added drag-and-drop support to the main note editor RichEditBox with AllowDrop="True" and
 event handlers for DragOver and Drop
• Added identical drag-and-drop support to the task note editor RichEditBox in the compact view
• Both editors now handle drag-over and drop events through NoteEditor_DragOver and
 NoteEditor_Drop handlers

NoteWindow.xaml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 26, 2026

Code Review by Qodo

🐞 Bugs (7) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Icon cache compile error 🐞 Bug ✓ Correctness
Description
IconHelper.EncodePngAsync allocates new byte[stream.Size], but stream.Size is not an int, so
this code does not compile and blocks the build.
Code

IconHelper.cs[R131-142]

+            using var stream = new InMemoryRandomAccessStream();
+            var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
+            encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied,
+                (uint)w, (uint)h, 96, 96, bgra);
+            await encoder.FlushAsync();
+
+            stream.Seek(0);
+            var bytes = new byte[stream.Size];
+            var reader = new DataReader(stream);
+            await reader.LoadAsync((uint)bytes.Length);
+            reader.ReadBytes(bytes);
+            return bytes;
Evidence
EncodePngAsync uses InMemoryRandomAccessStream.Size directly as an array length, but C# array
lengths must be int-convertible; this is a compile-time error.

IconHelper.cs[127-145]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`EncodePngAsync` uses `new byte[stream.Size]`, which fails to compile because the array length must be an `int`.

### Issue Context
This prevents the entire project from building.

### Fix Focus Areas
- IconHelper.cs[127-145]

### Suggested fix
- Convert `stream.Size` to an `int` safely (e.g., `var len = checked((int)stream.Size); var bytes = new byte[len];`).
- Prefer `using var reader = new DataReader(stream.GetInputStreamAt(0));` and dispose/detach reader if needed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Update banner z-order overlap 🐞 Bug ✓ Correctness
Description
UpdateBanner and NotesScroll are both placed in Grid.Row="3", and NotesScroll is declared after
the banner, so it will render on top and intercept input, making the banner effectively
hidden/un-clickable when shown.
Code

MainWindow.xaml[R97-118]

+        <!-- Update banner -->
+        <Button x:Name="UpdateBanner" Grid.Row="3" Visibility="Collapsed"
+                HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
+                Background="{ThemeResource AccentFillColorDefaultBrush}"
+                Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
+                BorderThickness="0" CornerRadius="6" Margin="12,0,12,8"
+                Padding="8,6" MinHeight="0" FontSize="12"
+                Click="UpdateBanner_Click">
+            <StackPanel Orientation="Horizontal" Spacing="6">
+                <FontIcon Glyph="&#xE896;" FontSize="12"
+                          Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"/>
+                <TextBlock x:Name="UpdateBannerText" Text="" VerticalAlignment="Center"
+                           Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"/>
+            </StackPanel>
+        </Button>
+
        <!-- Row 3: Notes list -->
        <ScrollViewer x:Name="NotesScroll" Grid.Row="3" Padding="12,0,12,12"
-                      VerticalScrollBarVisibility="Auto">
-            <StackPanel x:Name="NotesList" Spacing="8"/>
+                      VerticalScrollBarVisibility="Auto"
+                      Background="Transparent" PointerPressed="NotesScroll_PointerPressed">
+            <StackPanel x:Name="NotesList" Spacing="8" Background="Transparent"/>
        </ScrollViewer>
Evidence
The XAML shows both controls in the same Grid cell (row 3). In a Grid, later-declared elements are
rendered above earlier ones, so the ScrollViewer will cover the banner when the banner is made
visible.

MainWindow.xaml[97-118]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`UpdateBanner` shares the same `Grid.Row` as `NotesScroll` and is declared before it, so it will be covered by the ScrollViewer.

### Issue Context
This likely prevents users from seeing/clicking the update prompt.

### Fix Focus Areas
- MainWindow.xaml[97-118]

### Suggested fix
Choose one:
1) Add a dedicated grid row for the banner and move `NotesScroll` to the next row.
2) Keep same row but declare `UpdateBanner` after `NotesScroll` and/or set `Canvas.ZIndex` (or equivalent) so the banner is on top, and add top margin/padding to `NotesScroll` to avoid content being obscured.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Updater runs unverified EXE 🐞 Bug ⛨ Security
Description
The auto-update flow downloads an installer EXE and executes it without verifying Authenticode
signature or a known checksum, so a compromised download could lead to arbitrary code execution.
Code

UpdateService.cs[R80-127]

+    /// <summary>Downloads the installer to a temp file and returns the path.</summary>
+    public static async Task<string?> DownloadInstallerAsync(string downloadUrl, IProgress<double>? progress = null)
+    {
+        try
+        {
+            using var response = await Http.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
+            if (!response.IsSuccessStatusCode)
+                return null;
+
+            var totalBytes = response.Content.Headers.ContentLength ?? -1;
+            var tempPath = Path.Combine(Path.GetTempPath(), $"NoteUI-Setup-{Guid.NewGuid():N}.exe");
+
+            await using var contentStream = await response.Content.ReadAsStreamAsync();
+            await using var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
+
+            var buffer = new byte[81920];
+            long totalRead = 0;
+            int bytesRead;
+
+            while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0)
+            {
+                await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead));
+                totalRead += bytesRead;
+                if (totalBytes > 0)
+                    progress?.Report((double)totalRead / totalBytes);
+            }
+
+            progress?.Report(1.0);
+            return tempPath;
+        }
+        catch
+        {
+            return null;
+        }
+    }
+
+    /// <summary>Launches the downloaded installer with /SILENT and exits the app.</summary>
+    public static void LaunchInstallerAndExit(string installerPath)
+    {
+        Process.Start(new ProcessStartInfo
+        {
+            FileName = installerPath,
+            Arguments = "/SILENT",
+            UseShellExecute = true
+        });
+
+        Environment.Exit(0);
+    }
Evidence
UpdateService downloads an EXE to a temp path and LaunchInstallerAndExit runs it directly; there is
no signature or checksum verification step anywhere in UpdateService.

UpdateService.cs[80-127]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Downloaded update installers are executed without integrity/authenticity verification.

### Issue Context
The download URL comes from GitHub API, but defense-in-depth still requires verifying the downloaded binary before executing.

### Fix Focus Areas
- UpdateService.cs[80-127]

### Suggested fix
Implement at least one verification layer before `Process.Start`:
- Verify Authenticode signature matches an expected publisher certificate.
- And/or ship an expected SHA-256 checksum (or fetch checksums from release assets) and validate the downloaded file.
- Refuse to execute and surface an error state if verification fails.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Synchronous save per keystroke 🐞 Bug ➹ Performance
Description
The inline expanded RichEditBox calls SaveInlineEditor on every TextChanged, and SaveInlineEditor
calls _notes.Save(localOnly: true) which writes notes.json synchronously, causing heavy disk I/O
and typing lag.
Code

MainWindow.xaml.cs[R1586-1594]

+        editor.TextChanged += (_, _) =>
+        {
+            if (_suppressInlineTextChanged) return;
+            _inlineEditorDirty = true;
+            SaveInlineEditor();
+            // Live sync with open NoteWindow
+            var openWin = _openNoteWindows.FirstOrDefault(w => w.NoteId == capturedNoteId);
+            openWin?.ReloadFromDisk();
+        };
Evidence
MainWindow’s inline editor TextChanged handler triggers SaveInlineEditor for every edit, and
SaveInlineEditor calls NotesManager.Save; NotesManager.Save uses File.WriteAllText synchronously.

MainWindow.xaml.cs[1254-1269]
MainWindow.xaml.cs[1540-1594]
NotesManager.cs[49-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Inline expansion currently writes `notes.json` on every keystroke via a synchronous `File.WriteAllText`, which can cause UI stalls and excessive disk writes.

### Issue Context
The problem is the combination of per-keystroke `TextChanged` + synchronous `Save()`.

### Fix Focus Areas
- MainWindow.xaml.cs[1540-1594]
- MainWindow.xaml.cs[1254-1269]
- NotesManager.cs[49-66]

### Suggested fix
- Implement a debounce (e.g., DispatcherTimer ~300–750ms) so saves happen after the user pauses typing.
- Save immediately on collapse/losing focus, but not on every keystroke.
- Optionally make NotesManager.Save async (File.WriteAllTextAsync) or move serialization+write off the UI thread.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Hidden IDs leak in export 🐞 Bug ✓ Correctness
Description
Note links embed hidden note IDs into the document text, but export/copy uses TextGetOptions.None
(which this code comments includes hidden text), so exported Markdown/plain text can contain
GUIDs/zero-width characters.
Code

NoteWindow.xaml.cs[R1793-1804]

+    private string GetPlainText()
+    {
+        NoteEditor.Document.GetText(TextGetOptions.None, out var text);
+        return text.TrimEnd('\r', '\n');
+    }
+
+    private string ConvertNoteToMarkdown()
+    {
+        var doc = NoteEditor.Document;
+        doc.GetText(TextGetOptions.None, out var fullText);
+        fullText = fullText.TrimEnd('\r', '\n');
+        if (string.IsNullOrEmpty(fullText)) return "";
Evidence
InsertNoteLink appends \u200B + target.Id + \u200B and marks it Hidden; later, the code explicitly
states TextGetOptions.None includes hidden, and export routines use
GetText(TextGetOptions.None), so hidden IDs are included in exports/copy.

NoteWindow.xaml.cs[1687-1719]
NoteWindow.xaml.cs[1763-1767]
NoteWindow.xaml.cs[1793-1804]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Export/copy paths include hidden note IDs inserted for note links, causing leaked GUIDs/zero-width characters in exported output.

### Issue Context
The note-link feature uses hidden text for IDs, but export uses `TextGetOptions.None`.

### Fix Focus Areas
- NoteWindow.xaml.cs[1687-1719]
- NoteWindow.xaml.cs[1763-1767]
- NoteWindow.xaml.cs[1793-1804]

### Suggested fix
- Use a text extraction mode that excludes hidden text for export/copy (align with the code comment that `IncludeNumbering` skips hidden).
- Alternatively, post-process extracted text to remove hidden-ID segments (e.g., strip `\u200B{guid}\u200B`).
- Ensure Markdown conversion also uses the hidden-excluding extraction so it doesn’t emit IDs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Full-res images still embedded 🐞 Bug ➹ Performance
Description
InsertImageFromStream encodes the full-resolution bitmap into a PNG stream before applying the
maxWidth resize, so large images are still stored at full size (only the displayed dimensions
change).
Code

NoteWindow.xaml.cs[R2000-2030]

+    private async Task InsertImageFromStream(RichEditBox editor, Windows.Storage.Streams.IRandomAccessStream sourceStream)
+    {
+        var decoder = await Windows.Graphics.Imaging.BitmapDecoder.CreateAsync(sourceStream);
+        var w = (int)decoder.PixelWidth;
+        var h = (int)decoder.PixelHeight;
+
+        var softBitmap = await decoder.GetSoftwareBitmapAsync(
+            Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8,
+            Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied);
+
+        var pngStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
+        var encoder = await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(
+            Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId, pngStream);
+        encoder.SetSoftwareBitmap(softBitmap);
+        await encoder.FlushAsync();
+
+        const int maxWidth = 340;
+        if (w > maxWidth)
+        {
+            h = (int)(h * ((double)maxWidth / w));
+            w = maxWidth;
+        }
+
+        pngStream.Seek(0);
+        editor.Document.Selection.InsertImage(
+            w, h, 0,
+            Microsoft.UI.Text.VerticalCharacterAlignment.Baseline,
+            "image",
+            pngStream);
+        // pngStream NOT disposed — RichEditBox holds reference for rendering
+
Evidence
The code flushes the PNG encoder using the original SoftwareBitmap, then later adjusts w/h only
for InsertImage(...), meaning storage/memory footprint remains full-res.

NoteWindow.xaml.cs[2000-2038]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Dropped images are encoded at original resolution; the later `maxWidth` logic only affects display size.

### Issue Context
This can bloat note storage and slow loading/sync.

### Fix Focus Areas
- NoteWindow.xaml.cs[2000-2038]

### Suggested fix
- Apply a real downscale before encoding (e.g., decode with `BitmapTransform.ScaledWidth/ScaledHeight`, or resize the SoftwareBitmap) so the PNG data is actually smaller.
- Then call `InsertImage` with the scaled dimensions matching the encoded bitmap.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
7. Settings JSON parsed repeatedly 🐞 Bug ➹ Performance
Description
AppSettings.LoadStartMinimized/LoadCompactCards/LoadSortPreference parse JSON via
JsonDocument.Parse without disposing the JsonDocument, and these are called in hot UI paths,
causing avoidable allocations and file reads.
Code

AppSettings.cs[R214-274]

+    public static bool LoadStartMinimized()
+    {
+        try
+        {
+            if (File.Exists(SettingsPath))
+            {
+                var json = File.ReadAllText(SettingsPath);
+                var doc = JsonDocument.Parse(json);
+                if (doc.RootElement.TryGetProperty("startMinimized", out var prop))
+                    return prop.GetBoolean();
+            }
+        }
+        catch { }
+        return false;
+    }
+
+    public static void SaveStartMinimized(bool enabled)
+    {
+        MergeAndSaveSettings(new Dictionary<string, object> { ["startMinimized"] = enabled });
+    }
+
+    // ── Compact cards ────────────────────────────────────────
+
+    public static bool LoadCompactCards()
+    {
+        try
+        {
+            if (File.Exists(SettingsPath))
+            {
+                var json = File.ReadAllText(SettingsPath);
+                var doc = JsonDocument.Parse(json);
+                if (doc.RootElement.TryGetProperty("compactCards", out var prop))
+                    return prop.GetBoolean();
+            }
+        }
+        catch { }
+        return false;
+    }
+
+    public static void SaveCompactCards(bool enabled)
+    {
+        MergeAndSaveSettings(new Dictionary<string, object> { ["compactCards"] = enabled });
+    }
+
+    // ── Sort preference ────────────────────────────────────────
+
+    public static string LoadSortPreference()
+    {
+        try
+        {
+            if (File.Exists(SettingsPath))
+            {
+                var json = File.ReadAllText(SettingsPath);
+                var doc = JsonDocument.Parse(json);
+                if (doc.RootElement.TryGetProperty("sortMode", out var prop))
+                    return prop.GetString() ?? "recent";
+            }
+        }
+        catch { }
+        return "recent";
+    }
Evidence
Each Load* method reads SettingsPath and creates a JsonDocument but never wraps it in using, so
disposal doesn’t occur; these getters are invoked frequently (e.g., compact-card selection during
card creation).

AppSettings.cs[214-274]
MainWindow.xaml.cs[441-446]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Settings getters repeatedly read/parse the JSON file and leak JsonDocument resources by not disposing.

### Issue Context
These methods can be called many times during UI rendering, amplifying the overhead.

### Fix Focus Areas
- AppSettings.cs[214-274]
- MainWindow.xaml.cs[441-446]

### Suggested fix
- Change `var doc = JsonDocument.Parse(json);` to `using var doc = JsonDocument.Parse(json);` in the new Load* methods.
- Consider caching settings in memory (load once at startup + invalidate on save) so UI card creation doesn’t hit disk/JSON parsing repeatedly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@devohmycode devohmycode merged commit 35d397e into main Mar 26, 2026
1 check passed
@coderabbitai
Copy link

coderabbitai bot commented Mar 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e3ade7d-bf31-4e5c-8466-5ab71bd1805f

📥 Commits

Reviewing files that changed from the base of the PR and between 5272f2b and 6cd8f4d.

📒 Files selected for processing (14)
  • ActionPanel.cs
  • App.xaml.cs
  • AppSettings.cs
  • ClipboardMonitor.cs
  • HotkeyService.cs
  • IconHelper.cs
  • Lang.cs
  • MainWindow.xaml
  • MainWindow.xaml.cs
  • NoteWindow.xaml
  • NoteWindow.xaml.cs
  • NotesManager.cs
  • UpdateService.cs
  • installer.iss

📝 Walkthrough

Walkthrough

This PR introduces clipboard source tracking, automatic update detection with installer downloads, note linking via [[]] syntax, folder-based note organization, configurable sorting/startup/compact preferences, an About panel with expanded settings, executable icon caching, and drag-and-drop support for notes.

Changes

Cohort / File(s) Summary
Settings & Configuration Expansion
ActionPanel.cs, AppSettings.cs, HotkeyService.cs, Lang.cs
Added new optional parameters to CreateSettings() for startup toggles, sort selection, and compact cards. Introduced 8 new AppSettings methods for persisting startup preferences (registry-based), minimized state, compact cards, and sort mode (JSON-based). Added HOTKEY_PASTE_NOTE constant and 84 new localization strings for folders, shortcuts, export, startup, about, and update UI.
Update Service & Installation
UpdateService.cs, installer.iss
Created new UpdateService class for GitHub release API polling, version comparison, and installer download streaming with progress reporting. Added LaunchInstallerAndExit() to trigger silent installation. Updated installer script to version 0.3.0 and enabled auto-close/restart of target app during setup.
Clipboard & Icon Management
ClipboardMonitor.cs, IconHelper.cs
Implemented ClipboardMonitor class to track clipboard changes, resolve foreground window/process metadata via Win32, and clean browser-specific title suffixes. Added IconHelper with async icon extraction from executables, PNG encoding, and dual-tier caching (memory and disk).
Note Data Model & Organization
NotesManager.cs
Extended NoteEntry with SourceExePath, SourceTitle, and Folder properties. Refactored GetSorted() to accept a sortMode parameter (recent/created/alpha/color/size). Added GetByTitle(), SearchByTitle(), GetAllFolders(), and UpdateNoteFolder() for folder support and lookup.
Note Linking & Folder Support
NoteWindow.xaml.cs, NoteWindow.xaml
Implemented [[ pattern detection and flyout-based note-link suggestion/insertion with hidden note ID encoding. Added Ctrl+Click note navigation and NoteLinkClicked event. Extended note menu with folder management (set/clear/create). Added ReloadFromDisk() method. Enabled drag-and-drop on both RichEditBox controls.
Main Window UI, Startup & Features
MainWindow.xaml.cs, MainWindow.xaml, App.xaml.cs
Extended view modes with Folders and FolderFilter for folder browsing. Added inline note expansion/editing with expandable card UI and format flyout. Integrated clipboard source display and clipboard monitoring on note creation. Implemented update banner with background version check and installer download/launch. Added paste hotkey triggering PasteAsNewNote(). Integrated LoadSortPreference() and LoadCompactCards() into note list rendering and card creation. Enhanced window startup to conditionally hide based on LoadStartMinimized().

Sequence Diagram(s)

sequenceDiagram
    participant Clipboard as Windows<br/>Clipboard API
    participant Monitor as ClipboardMonitor
    participant Win32 as Win32 API
    participant App as MainWindow
    participant Notes as NotesManager
    participant IconHelper as IconHelper

    Clipboard->>Monitor: ContentChanged event
    Monitor->>Win32: GetForegroundWindow()
    Win32-->>Monitor: window handle
    Monitor->>Win32: QueryFullProcessImageName()
    Win32-->>Monitor: exePath, processID
    Monitor->>Win32: GetWindowText()
    Win32-->>Monitor: title
    Monitor->>Monitor: CleanTitle(title, exePath)
    Monitor-->>Monitor: sourceExePath,<br/>sourceTitle stored

    App->>Monitor: Read SourceExePath/<br/>SourceTitle
    App->>Notes: AddNote() with source
    Notes-->>Notes: NoteEntry with<br/>clipboard metadata

    App->>IconHelper: LoadIconAsync(exePath)
    IconHelper->>IconHelper: Check memory cache
    alt Cache miss
        IconHelper->>Win32: ExtractIcon,<br/>GetIconInfo, etc.
        Win32-->>IconHelper: Icon pixel data
        IconHelper->>IconHelper: Encode PNG,<br/>write to disk
    end
    IconHelper-->>App: Update Image.Source
Loading
sequenceDiagram
    participant MainWindow as MainWindow
    participant UpdateService as UpdateService
    participant GitHub as GitHub API
    participant Installer as Installer

    MainWindow->>UpdateService: CheckForUpdateAsync()
    UpdateService->>GitHub: GET /releases/latest
    GitHub-->>UpdateService: JSON (tag_name, assets)
    UpdateService->>UpdateService: Compare CurrentVersion<br/>vs tag_name
    alt Update available
        UpdateService-->>UpdateService: Select asset URL<br/>(CUDA or CPU)
        UpdateService-->>MainWindow: UpdateInfo record
        MainWindow->>MainWindow: Show UpdateBanner
        Note over MainWindow: User clicks banner
        MainWindow->>UpdateService: DownloadInstallerAsync()
        UpdateService->>UpdateService: Stream download<br/>to temp .exe,<br/>report progress
        UpdateService-->>MainWindow: installerPath
        MainWindow->>UpdateService: LaunchInstallerAndExit(path)
        UpdateService->>Installer: Start /SILENT
        UpdateService->>UpdateService: Environment.Exit(0)
    else No update
        UpdateService-->>MainWindow: null
    end
Loading
sequenceDiagram
    participant User as User
    participant NoteWindow as NoteWindow Editor
    participant Flyout as Note Link Flyout
    participant NotesManager as NotesManager
    participant App as MainWindow

    User->>NoteWindow: Type [[
    NoteWindow->>NoteWindow: Detect [[ pattern
    NoteWindow->>NotesManager: SearchByTitle(query)
    NotesManager-->>NoteWindow: List of notes (up to 10)
    NoteWindow->>Flyout: Show suggestions
    User->>Flyout: Select note
    Flyout->>NoteWindow: Insert note link
    NoteWindow->>NoteWindow: Format: visible title +<br/>hidden noteID<br/>(zero-width chars)
    NoteWindow->>NoteWindow: Save note

    Note over User: Later...
    User->>NoteWindow: Ctrl+Click on link
    NoteWindow->>NoteWindow: Scan adjacent green text
    NoteWindow->>NoteWindow: Extract hidden noteID
    NoteWindow->>NoteWindow: Raise NoteLinkClicked(noteId)
    NoteWindow-->>App: NoteLinkClicked event
    App->>App: OpenNote(noteId)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • PR #3: Modifies AppSettings and App.xaml.cs startup behavior for minimized window state, directly overlapping with startup-related changes in this PR.
  • PR #2: Extends ActionPanel.CreateSettings() signature with additional optional parameters for new settings UI sections, sharing the same method expansion pattern.
  • PR #5: Adds further optional parameters to ActionPanel.CreateSettings() for settings panel customization, related through the same settings UI construction pathway.

Poem

🐰 Hops through clipboards, tracks the source,
Icons cached—a swift, swift course!
Notes link up with [[ syntax grand,
Folders organize, updates on hand,
Inline editing makes hearts sing,
What a feature-packed spring!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 0.3.0

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 and usage tips.

Comment on lines +131 to +142
using var stream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied,
(uint)w, (uint)h, 96, 96, bgra);
await encoder.FlushAsync();

stream.Seek(0);
var bytes = new byte[stream.Size];
var reader = new DataReader(stream);
await reader.LoadAsync((uint)bytes.Length);
reader.ReadBytes(bytes);
return bytes;

Choose a reason for hiding this comment

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

Action required

1. Icon cache compile error 🐞 Bug ✓ Correctness

IconHelper.EncodePngAsync allocates new byte[stream.Size], but stream.Size is not an int, so
this code does not compile and blocks the build.
Agent Prompt
### Issue description
`EncodePngAsync` uses `new byte[stream.Size]`, which fails to compile because the array length must be an `int`.

### Issue Context
This prevents the entire project from building.

### Fix Focus Areas
- IconHelper.cs[127-145]

### Suggested fix
- Convert `stream.Size` to an `int` safely (e.g., `var len = checked((int)stream.Size); var bytes = new byte[len];`).
- Prefer `using var reader = new DataReader(stream.GetInputStreamAt(0));` and dispose/detach reader if needed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +97 to 118
<!-- Update banner -->
<Button x:Name="UpdateBanner" Grid.Row="3" Visibility="Collapsed"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
Background="{ThemeResource AccentFillColorDefaultBrush}"
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
BorderThickness="0" CornerRadius="6" Margin="12,0,12,8"
Padding="8,6" MinHeight="0" FontSize="12"
Click="UpdateBanner_Click">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon Glyph="&#xE896;" FontSize="12"
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"/>
<TextBlock x:Name="UpdateBannerText" Text="" VerticalAlignment="Center"
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"/>
</StackPanel>
</Button>

<!-- Row 3: Notes list -->
<ScrollViewer x:Name="NotesScroll" Grid.Row="3" Padding="12,0,12,12"
VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="NotesList" Spacing="8"/>
VerticalScrollBarVisibility="Auto"
Background="Transparent" PointerPressed="NotesScroll_PointerPressed">
<StackPanel x:Name="NotesList" Spacing="8" Background="Transparent"/>
</ScrollViewer>

Choose a reason for hiding this comment

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

Action required

2. Update banner z-order overlap 🐞 Bug ✓ Correctness

UpdateBanner and NotesScroll are both placed in Grid.Row="3", and NotesScroll is declared after
the banner, so it will render on top and intercept input, making the banner effectively
hidden/un-clickable when shown.
Agent Prompt
### Issue description
`UpdateBanner` shares the same `Grid.Row` as `NotesScroll` and is declared before it, so it will be covered by the ScrollViewer.

### Issue Context
This likely prevents users from seeing/clicking the update prompt.

### Fix Focus Areas
- MainWindow.xaml[97-118]

### Suggested fix
Choose one:
1) Add a dedicated grid row for the banner and move `NotesScroll` to the next row.
2) Keep same row but declare `UpdateBanner` after `NotesScroll` and/or set `Canvas.ZIndex` (or equivalent) so the banner is on top, and add top margin/padding to `NotesScroll` to avoid content being obscured.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +80 to +127
/// <summary>Downloads the installer to a temp file and returns the path.</summary>
public static async Task<string?> DownloadInstallerAsync(string downloadUrl, IProgress<double>? progress = null)
{
try
{
using var response = await Http.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode)
return null;

var totalBytes = response.Content.Headers.ContentLength ?? -1;
var tempPath = Path.Combine(Path.GetTempPath(), $"NoteUI-Setup-{Guid.NewGuid():N}.exe");

await using var contentStream = await response.Content.ReadAsStreamAsync();
await using var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);

var buffer = new byte[81920];
long totalRead = 0;
int bytesRead;

while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0)
{
await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead));
totalRead += bytesRead;
if (totalBytes > 0)
progress?.Report((double)totalRead / totalBytes);
}

progress?.Report(1.0);
return tempPath;
}
catch
{
return null;
}
}

/// <summary>Launches the downloaded installer with /SILENT and exits the app.</summary>
public static void LaunchInstallerAndExit(string installerPath)
{
Process.Start(new ProcessStartInfo
{
FileName = installerPath,
Arguments = "/SILENT",
UseShellExecute = true
});

Environment.Exit(0);
}

Choose a reason for hiding this comment

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

Action required

3. Updater runs unverified exe 🐞 Bug ⛨ Security

The auto-update flow downloads an installer EXE and executes it without verifying Authenticode
signature or a known checksum, so a compromised download could lead to arbitrary code execution.
Agent Prompt
### Issue description
Downloaded update installers are executed without integrity/authenticity verification.

### Issue Context
The download URL comes from GitHub API, but defense-in-depth still requires verifying the downloaded binary before executing.

### Fix Focus Areas
- UpdateService.cs[80-127]

### Suggested fix
Implement at least one verification layer before `Process.Start`:
- Verify Authenticode signature matches an expected publisher certificate.
- And/or ship an expected SHA-256 checksum (or fetch checksums from release assets) and validate the downloaded file.
- Refuse to execute and surface an error state if verification fails.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant