perf: precompile redaction and UI regexes (#286)

Precompiles reusable regexes in redaction/UI helpers and keeps QuickSend-specific overlap out of this PR.\n\nValidation: local ARM64 build passed; Shared tests 1296 passed / 20 skipped; Tray tests 466 passed; remote CI green.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
github-actions[bot] 2026-05-07 18:12:41 -04:00 committed by GitHub
parent ad120cdf70
commit 6e8a9d72ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 66 additions and 53 deletions

View File

@ -17,6 +17,10 @@ public sealed partial class SchemaConfigEditor : UserControl
private JsonElement _config;
private readonly Dictionary<string, object?> _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 @@ public sealed partial class SchemaConfigEditor : UserControl
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)

View File

@ -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(?<user>[A-Za-z0-9._-]+)@(?<host>[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 @@ internal static class CommandCenterTextHelper
}
}
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 @@ internal static class CommandCenterTextHelper
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, "<host>"),
RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
match => match.Value.Replace(match.Groups[1].Value, "<host>"));
redacted = Regex.Replace(
redacted,
@"\b(?:\d{1,3}\.){3}\d{1,3}\b",
"<ip>",
RegexOptions.None,
TimeSpan.FromMilliseconds(100));
redacted = ValueIpPattern.Replace(redacted, "<ip>");
redacted = Regex.Replace(
redacted,
@"\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b",
"<email>",
RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
redacted = ValueEmailPattern.Replace(redacted, "<email>");
redacted = Regex.Replace(
redacted,
@"\b(?<user>[A-Za-z0-9._-]+)@(?<host>[A-Za-z0-9._-]+)(?=[:\s]|$)",
"<user>@<host>",
RegexOptions.None,
TimeSpan.FromMilliseconds(100));
redacted = ValueUserAtHostPattern.Replace(redacted, "<user>@<host>");
redacted = Regex.Replace(
redacted,
@"(?<=\bto\s)[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
"<host>",
RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
redacted = ValueHostAfterToPattern.Replace(redacted, "<host>");
redacted = Regex.Replace(
redacted,
@"^\s*[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
"<host>",
RegexOptions.None,
TimeSpan.FromMilliseconds(100));
redacted = ValueLeadingHostPattern.Replace(redacted, "<host>");
return redacted;
}

View File

@ -18,6 +18,13 @@ namespace OpenClawTray.Onboarding.Pages;
/// </summary>
public sealed class WizardPage : Component<OnboardingState>
{
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 @@ public sealed class WizardPage : Component<OnboardingState>
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 @@ public sealed class WizardPage : Component<OnboardingState>
// 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 @@ public sealed class WizardPage : Component<OnboardingState>
{
if (!string.IsNullOrEmpty(displayMessage))
{
var urlMatch = Regex.Match(displayMessage, @"(https?://[^\s\)\"",]+)");
var urlMatch = UrlInMessagePattern.Match(displayMessage);
if (urlMatch.Success)
{
try