From 8ea4e65f45360be45e58ef6dd0faaa5d5eee7a7e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 13:11:34 +0000 Subject: [PATCH 1/2] perf: pre-compile regexes in redaction helpers and UI pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace 11 on-demand Regex.Replace/Match calls (in CommandCenterTextHelper, WizardPage, SchemaConfigEditor, and QuickSendDialog) with static readonly pre-compiled Regex fields using RegexOptions.Compiled. Each field is compiled once at startup and reused on every invocation, eliminating the overhead of per-call regex compilation or .NET cache lookups. Affected call sites: - CommandCenterTextHelper.RedactSupportPath: 2 patterns - CommandCenterTextHelper.RedactSupportValue: 6 patterns - WizardPage.Render: 2 patterns (URL detection + device code) - SchemaConfigEditor.GetLabel: 1 pattern (camelCase → label) - QuickSendDialog.TryExtractMissingScope: 1 pattern No behaviour change; all 393 Tray tests and 1219 Shared tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Controls/SchemaConfigEditor.xaml.cs | 6 +- .../Dialogs/QuickSendDialog.cs | 5 +- .../Helpers/CommandCenterTextHelper.cs | 108 +++++++++--------- .../Onboarding/Pages/WizardPage.cs | 15 ++- 4 files changed, 75 insertions(+), 59 deletions(-) diff --git a/src/OpenClaw.Tray.WinUI/Controls/SchemaConfigEditor.xaml.cs b/src/OpenClaw.Tray.WinUI/Controls/SchemaConfigEditor.xaml.cs index c5d69f18..596fefa5 100644 --- a/src/OpenClaw.Tray.WinUI/Controls/SchemaConfigEditor.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Controls/SchemaConfigEditor.xaml.cs @@ -17,6 +17,10 @@ public sealed partial class SchemaConfigEditor : UserControl private JsonElement _config; private readonly Dictionary _changes = new(); + private static readonly Regex CamelCaseSplitPattern = new( + "([a-z])([A-Z])", + RegexOptions.Compiled); + private static readonly SolidColorBrush SecondaryBrush = new(ColorHelper.FromArgb(255, 140, 150, 170)); @@ -378,7 +382,7 @@ private void UpdateArrayChanges(StackPanel itemsPanel, string path) private static string GetLabel(string path, string name) { - var result = Regex.Replace(name, "([a-z])([A-Z])", "$1 $2"); + var result = CamelCaseSplitPattern.Replace(name, "$1 $2"); result = result.Replace("_", " ").Replace(".", " \u203A "); // Title-case the first character if (result.Length > 0) diff --git a/src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs b/src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs index 1c54f202..c91ecaf4 100644 --- a/src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs +++ b/src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs @@ -42,6 +42,9 @@ private static extern bool SetWindowPos( uint uFlags); private static readonly IntPtr HWND_TOPMOST = new(-1); + private static readonly Regex MissingScopePattern = new( + @"missing\s+scope\s*:\s*([A-Za-z0-9._-]+)", + RegexOptions.IgnoreCase | RegexOptions.Compiled); private const int SW_SHOWNORMAL = 1; private const uint SWP_NOMOVE = 0x0002; private const uint SWP_NOSIZE = 0x0001; @@ -280,7 +283,7 @@ private static bool TryExtractMissingScope(string? message, out string scope) return false; } - var match = Regex.Match(message, @"missing\s+scope\s*:\s*([A-Za-z0-9._-]+)", RegexOptions.IgnoreCase); + var match = MissingScopePattern.Match(message); if (!match.Success) { return false; diff --git a/src/OpenClaw.Tray.WinUI/Helpers/CommandCenterTextHelper.cs b/src/OpenClaw.Tray.WinUI/Helpers/CommandCenterTextHelper.cs index d2126c80..622759d7 100644 --- a/src/OpenClaw.Tray.WinUI/Helpers/CommandCenterTextHelper.cs +++ b/src/OpenClaw.Tray.WinUI/Helpers/CommandCenterTextHelper.cs @@ -11,6 +11,48 @@ namespace OpenClawTray.Helpers; internal static class CommandCenterTextHelper { + // Pre-compiled patterns used in RedactSupportPath / RedactSupportValue. + // Compiled once at startup; reused on every diagnostic / support-text build. + private static readonly Regex PathWindowsUserPattern = new( + @"\b[A-Za-z]:\\Users\\[^\\]+", + RegexOptions.IgnoreCase | RegexOptions.Compiled, + TimeSpan.FromMilliseconds(100)); + + private static readonly Regex PathUnixUserPattern = new( + @"/Users/[^/]+", + RegexOptions.Compiled, + TimeSpan.FromMilliseconds(100)); + + private static readonly Regex ValueUrlHostPattern = new( + @"\b[a-z][a-z0-9+.-]*://(?:[^@\s/]+@)?([^:/\s]+)", + RegexOptions.IgnoreCase | RegexOptions.Compiled, + TimeSpan.FromMilliseconds(100)); + + private static readonly Regex ValueIpPattern = new( + @"\b(?:\d{1,3}\.){3}\d{1,3}\b", + RegexOptions.Compiled, + TimeSpan.FromMilliseconds(100)); + + private static readonly Regex ValueEmailPattern = new( + @"\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b", + RegexOptions.IgnoreCase | RegexOptions.Compiled, + TimeSpan.FromMilliseconds(100)); + + private static readonly Regex ValueUserAtHostPattern = new( + @"\b(?[A-Za-z0-9._-]+)@(?[A-Za-z0-9._-]+)(?=[:\s]|$)", + RegexOptions.Compiled, + TimeSpan.FromMilliseconds(100)); + + private static readonly Regex ValueHostAfterToPattern = new( + @"(?<=\bto\s)[A-Za-z0-9._-]+(?=:\d{1,5}\b)", + RegexOptions.IgnoreCase | RegexOptions.Compiled, + TimeSpan.FromMilliseconds(100)); + + private static readonly Regex ValueLeadingHostPattern = new( + @"^\s*[A-Za-z0-9._-]+(?=:\d{1,5}\b)", + RegexOptions.Compiled, + TimeSpan.FromMilliseconds(100)); + internal static string BuildSupportContext(GatewayCommandCenterState state) { var builder = new StringBuilder(); @@ -346,19 +388,9 @@ private static string RedactSupportPath(string? path) } } - redacted = Regex.Replace( - redacted, - @"\b[A-Za-z]:\\Users\\[^\\]+", - "%USERPROFILE%", - RegexOptions.IgnoreCase, - TimeSpan.FromMilliseconds(100)); + redacted = PathWindowsUserPattern.Replace(redacted, "%USERPROFILE%"); - redacted = Regex.Replace( - redacted, - @"/Users/[^/]+", - "$HOME", - RegexOptions.None, - TimeSpan.FromMilliseconds(100)); + redacted = PathUnixUserPattern.Replace(redacted, "$HOME"); return redacted; } @@ -368,47 +400,19 @@ private static string RedactSupportValue(string? value) if (string.IsNullOrWhiteSpace(value)) return "unknown"; - var redacted = Regex.Replace( + var redacted = ValueUrlHostPattern.Replace( value, - @"\b[a-z][a-z0-9+.-]*://(?:[^@\s/]+@)?([^:/\s]+)", - match => match.Value.Replace(match.Groups[1].Value, ""), - RegexOptions.IgnoreCase, - TimeSpan.FromMilliseconds(100)); - - redacted = Regex.Replace( - redacted, - @"\b(?:\d{1,3}\.){3}\d{1,3}\b", - "", - RegexOptions.None, - TimeSpan.FromMilliseconds(100)); - - redacted = Regex.Replace( - redacted, - @"\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b", - "", - RegexOptions.IgnoreCase, - TimeSpan.FromMilliseconds(100)); - - redacted = Regex.Replace( - redacted, - @"\b(?[A-Za-z0-9._-]+)@(?[A-Za-z0-9._-]+)(?=[:\s]|$)", - "@", - RegexOptions.None, - TimeSpan.FromMilliseconds(100)); - - redacted = Regex.Replace( - redacted, - @"(?<=\bto\s)[A-Za-z0-9._-]+(?=:\d{1,5}\b)", - "", - RegexOptions.IgnoreCase, - TimeSpan.FromMilliseconds(100)); - - redacted = Regex.Replace( - redacted, - @"^\s*[A-Za-z0-9._-]+(?=:\d{1,5}\b)", - "", - RegexOptions.None, - TimeSpan.FromMilliseconds(100)); + match => match.Value.Replace(match.Groups[1].Value, "")); + + redacted = ValueIpPattern.Replace(redacted, ""); + + redacted = ValueEmailPattern.Replace(redacted, ""); + + redacted = ValueUserAtHostPattern.Replace(redacted, "@"); + + redacted = ValueHostAfterToPattern.Replace(redacted, ""); + + redacted = ValueLeadingHostPattern.Replace(redacted, ""); return redacted; } diff --git a/src/OpenClaw.Tray.WinUI/Onboarding/Pages/WizardPage.cs b/src/OpenClaw.Tray.WinUI/Onboarding/Pages/WizardPage.cs index 7355f1ec..64d28014 100644 --- a/src/OpenClaw.Tray.WinUI/Onboarding/Pages/WizardPage.cs +++ b/src/OpenClaw.Tray.WinUI/Onboarding/Pages/WizardPage.cs @@ -18,6 +18,13 @@ namespace OpenClawTray.Onboarding.Pages; /// public sealed class WizardPage : Component { + private static readonly Regex UrlInMessagePattern = new( + @"(https?://[^\s\)\"",]+)", + RegexOptions.Compiled); + + private static readonly Regex DeviceCodePattern = new( + @"(?:^|\s)(?:[Cc]ode|user_code|USER_CODE)\s*[:=]\s*([A-Z0-9]{2,8}(?:-[A-Z0-9]{2,8})+|[A-Z0-9]{4,12})\b", + RegexOptions.Compiled); public override Element Render() { // Read persisted wizard state from shared OnboardingState @@ -523,7 +530,7 @@ async void SkipStep() if (!string.IsNullOrEmpty(displayMessage)) { // URL detection — find https:// URLs in the message - var urlMatch = Regex.Match(displayMessage, @"(https?://[^\s\)\"",]+)"); + var urlMatch = UrlInMessagePattern.Match(displayMessage); if (urlMatch.Success) { var detectedUrl = urlMatch.Value; @@ -545,9 +552,7 @@ async void SkipStep() // Capture must contain a digit or hyphen (or be all uppercase) to avoid // matching common English words like "below" that follow "code". // Case-sensitive on the value to require the GitHub-style uppercase code. - var codeMatch = Regex.Match( - displayMessage, - @"(?:^|\s)(?:[Cc]ode|user_code|USER_CODE)\s*[:=]\s*([A-Z0-9]{2,8}(?:-[A-Z0-9]{2,8})+|[A-Z0-9]{4,12})\b"); + var codeMatch = DeviceCodePattern.Match(displayMessage); if (codeMatch.Success) { var code = codeMatch.Groups[1].Value; @@ -581,7 +586,7 @@ async void SkipStep() { if (!string.IsNullOrEmpty(displayMessage)) { - var urlMatch = Regex.Match(displayMessage, @"(https?://[^\s\)\"",]+)"); + var urlMatch = UrlInMessagePattern.Match(displayMessage); if (urlMatch.Success) { try From 0c4a284ce3c2305d770852ea2ebf88608229b97f Mon Sep 17 00:00:00 2001 From: Scott Hanselman Date: Thu, 7 May 2026 14:05:03 -0400 Subject: [PATCH 2/2] chore: drop QuickSend overlap from regex precompile PR Leave QuickSendDialog to the dedicated titlebar/focus PR so this Repo Assist perf change can remain independent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs b/src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs index c91ecaf4..1c54f202 100644 --- a/src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs +++ b/src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs @@ -42,9 +42,6 @@ private static extern bool SetWindowPos( uint uFlags); private static readonly IntPtr HWND_TOPMOST = new(-1); - private static readonly Regex MissingScopePattern = new( - @"missing\s+scope\s*:\s*([A-Za-z0-9._-]+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); private const int SW_SHOWNORMAL = 1; private const uint SWP_NOMOVE = 0x0002; private const uint SWP_NOSIZE = 0x0001; @@ -283,7 +280,7 @@ private static bool TryExtractMissingScope(string? message, out string scope) return false; } - var match = MissingScopePattern.Match(message); + var match = Regex.Match(message, @"missing\s+scope\s*:\s*([A-Za-z0-9._-]+)", RegexOptions.IgnoreCase); if (!match.Success) { return false;