Skip to content

Commit 8d44bb3

Browse files
committed
feat(gui): add filter improvements, admin dimming, tree auto-sizing, HwInfo dialog
- Fix filter combo: add "Default" and "Errors" filter options with proper logic - Dim admin-only tweaks when running without elevation (NeedsAdmin + Machine scope) - Auto-size category tree splitter based on longest node text width - Add "Hardware Info..." menu item under Help with detailed system info dialog - Add UpdateAction delegate to TweakDef for package manager update support - Add Update() method to TweakEngine (falls back to Apply if no UpdateAction) - Add IsScoopInstalled() to HardwareInfo, extend IsApplicable for Scoop/M365 - Configure CSharpier formatter (.csharpierrc.json, VS Code settings) - Enhance .env.ps1 with PSReadLine prediction and custom git-aware prompt - Fix GUI launch.json: switch to integratedTerminal for .env.ps1 sourcing
1 parent 3caa2e7 commit 8d44bb3

11 files changed

Lines changed: 372 additions & 26 deletions

File tree

.csharpierrc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"$schema": "https://csharpier.com/schemas/v0.29.2/csharpier.schema.json",
3+
"printWidth": 150,
4+
"useTabs": false,
5+
"tabWidth": 4,
6+
"endOfLine": "lf"
7+
}

.env.ps1

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,59 @@ Add-PathEntry "$env:USERPROFILE\.dotnet\tools"
9797
# ── Mark as loaded ──────────────────────────────────────────────────────────
9898
$env:REGILATTICE_ENV_LOADED = '1'
9999

100+
# ── Convenience aliases for common project tasks ────────────────────
101+
if (-not (Get-Alias -Name 'rlbuild' -ErrorAction SilentlyContinue)) {
102+
Set-Alias -Name 'rlbuild' -Value { dotnet build RegiLattice.sln }.GetSteppablePipeline -Option ReadOnly -ErrorAction SilentlyContinue
103+
}
104+
function Invoke-RLBuild { dotnet build RegiLattice.sln @args }
105+
function Invoke-RLTest { dotnet test RegiLattice.sln --logger "console;verbosity=normal" @args }
106+
function Invoke-RLGui { dotnet run --project src/RegiLattice.GUI @args }
107+
function Invoke-RLCli { dotnet run --project src/RegiLattice.CLI -- @args }
108+
109+
# ── Tab completion for common dotnet arguments ──────────────────────
110+
$_rlTestProjects = @(
111+
'tests/RegiLattice.Core.Tests'
112+
'tests/RegiLattice.CLI.Tests'
113+
'tests/RegiLattice.GUI.Tests'
114+
)
115+
116+
Register-ArgumentCompleter -CommandName 'dotnet' -ScriptBlock {
117+
param($wordToComplete, $commandAst, $cursorPosition)
118+
$cmdText = $commandAst.ToString()
119+
if ($cmdText -match 'dotnet\s+test\s+') {
120+
$_rlTestProjects | Where-Object { $_ -like "$wordToComplete*" } |
121+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
122+
}
123+
}
124+
125+
# ── PSReadLine enhancements (prediction, history search) ────────────
126+
if (Get-Module -ListAvailable PSReadLine -ErrorAction SilentlyContinue) {
127+
Set-PSReadLineOption -PredictionSource HistoryAndPlugin -ErrorAction SilentlyContinue
128+
Set-PSReadLineOption -PredictionViewStyle ListView -ErrorAction SilentlyContinue
129+
Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward
130+
Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward
131+
Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete
132+
}
133+
134+
# ── Custom prompt with git branch + command timing ──────────────────
135+
function global:prompt {
136+
$exitCode = $LASTEXITCODE
137+
$duration = ''
138+
if ((Get-History -Count 1 -ErrorAction SilentlyContinue) -is [Microsoft.PowerShell.Commands.HistoryInfo]) {
139+
$last = Get-History -Count 1
140+
$ms = ($last.EndExecutionTime - $last.StartExecutionTime).TotalMilliseconds
141+
if ($ms -ge 1000) { $duration = " ($([math]::Round($ms / 1000, 1))s)" }
142+
elseif ($ms -ge 100) { $duration = " ($([int]$ms)ms)" }
143+
}
144+
$branch = ''
145+
if (Get-Command git -ErrorAction SilentlyContinue) {
146+
$b = git rev-parse --abbrev-ref HEAD 2>$null
147+
if ($b) { $branch = " ($b)" }
148+
}
149+
$cwd = (Get-Location).Path -replace [regex]::Escape($HOME), '~'
150+
$arrow = if ($exitCode -eq 0 -or $null -eq $exitCode) { "`e[32m❯`e[0m" } else { "`e[31m❯`e[0m" }
151+
"`e[36m$cwd`e[33m$branch`e[90m$duration`e[0m`n$arrow "
152+
}
153+
100154
Write-Host "[.env.ps1] RegiLattice dev environment loaded — $(($env:PATH -split ';').Count) PATH entries" -ForegroundColor DarkCyan
155+
Write-Host " Helpers: Invoke-RLBuild, Invoke-RLTest, Invoke-RLGui, Invoke-RLCli" -ForegroundColor DarkGray

.github/instructions/lessons-learned.instructions.md

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ applyTo: "**/*.cs,**/tests/**,**/*Tests/**"
44

55
# Lessons Learned — RegiLattice Development
66

7-
> Accumulated hard-won insights from the Python → C# migration and iterative test coverage sprints.
7+
> Accumulated hard-won insights from the Python → C# migration, test coverage sprints,
8+
> and the 453-tweak restoration campaign.
89
> These rules are **as important as the coding standards** — they prevent recurring mistakes.
9-
> Last updated: 2025-07-20 (v3.0.0, C# 13 / .NET 10.0-windows)
10+
> Last updated: 2025-07-21 (v3.1.5, C# 13 / .NET 10.0-windows, 1 882 tweaks, 72 categories)
1011
1112
---
1213

@@ -42,7 +43,7 @@ WinForms controls require a message pump and are fragile in xUnit. Focus tests o
4243
## Unique TweakDef IDs — Global Uniqueness Required
4344

4445
`TweakEngine.Register()` throws `ArgumentException` on duplicate IDs.
45-
Every tweak ID across ALL 71 modules must be globally unique.
46+
Every tweak ID across ALL 72 modules must be globally unique.
4647

4748
```csharp
4849
// ❌ BAD — duplicate ID across modules will throw at registration
@@ -209,6 +210,109 @@ All references to the GitHub account must use `RajwanYair`:
209210

210211
## Version & Metadata
211212

212-
- Version lives in `.csproj` files — `<Version>3.0.0</Version>`
213+
- Version lives in `.csproj` files — `<Version>3.1.5</Version>`
213214
- Do not duplicate version strings — single source of truth per project
214215
- GitHub URLs: `https://github.com/RajwanYair/RegiLattice`
216+
217+
---
218+
219+
## String Escape Sequences — Always Use Verbatim `@""`
220+
221+
Registry paths and file paths must use verbatim strings to avoid escape sequence errors:
222+
223+
```csharp
224+
// ✅ GOOD — verbatim string, no escape issues
225+
RegOp.SetDword(@"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender", "DisableAntiSpyware", 1)
226+
227+
// ❌ BAD — \S, \P, \W are invalid escape sequences → CS1009
228+
RegOp.SetDword("HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender", "DisableAntiSpyware", 1)
229+
```
230+
231+
**Common trap**: When batch-editing tweak files, forgetting the `@` prefix on registry path strings.
232+
The C# compiler treats `\S`, `\P`, `\W`, `\D`, `\M` etc. as invalid escape sequences.
233+
234+
---
235+
236+
## Duplicate ID Detection — Check Before Adding
237+
238+
When adding tweaks in bulk across multiple modules, duplicates can sneak in:
239+
240+
```csharp
241+
// chrome.cs already has chrome-disable-translate
242+
// firefox.cs accidentally also defines chrome-disable-translate → CRASH at RegisterBuiltins()
243+
```
244+
245+
**Prevention**: Before adding a tweak ID, search the Tweaks/ directory:
246+
247+
```powershell
248+
Select-String -Pattern '"new-tweak-id"' -Path src/RegiLattice.Core/Tweaks/*.cs
249+
```
250+
251+
During the 453-tweak restoration, 5 duplicate IDs were found across Chrome.cs and Firefox.cs.
252+
253+
---
254+
255+
## Tuple Deconstruction — Match Return Types Exactly
256+
257+
When calling helper methods, verify the return tuple arity:
258+
259+
```csharp
260+
// ShellRunner.RunPowerShell returns (int exitCode, string stdout, string stderr)
261+
262+
// ✅ GOOD — deconstruct all 3 values
263+
var (exitCode, stdout, stderr) = ShellRunner.RunPowerShell(script, dryRun);
264+
265+
// ❌ BAD — CS8132: only deconstructing 2 of 3 values
266+
var (exitCode, output) = ShellRunner.RunPowerShell(script, dryRun);
267+
```
268+
269+
---
270+
271+
## Batch Editing Workflow — Verify File Anchors
272+
273+
When making bulk edits across many files (e.g., restoring 453 tweaks):
274+
275+
1. Read the target file first to confirm exact content
276+
2. Use `multi_replace_string_in_file` for independent edits
277+
3. After each batch, verify the build: `dotnet build`
278+
4. Commit per logical phase (e.g., per 10 modules)
279+
280+
**Common trap**: File contents change between sessions (e.g., formatter runs, manual edits).
281+
Always re-read before editing — never assume content matches what was seen earlier.
282+
283+
---
284+
285+
## SetExpandString and SetQword — Less Common RegOp Factories
286+
287+
`RegOp.SetExpandString` and `RegOp.SetQword` exist but are rarely used.
288+
When a registry value contains `%SystemRoot%` or other environment variables,
289+
use `SetExpandString` (REG_EXPAND_SZ), not `SetString` (REG_SZ).
290+
291+
```csharp
292+
// ✅ GOOD — REG_EXPAND_SZ preserves environment variable expansion
293+
RegOp.SetExpandString(@"HKLM\...", "ImagePath", @"%SystemRoot%\System32\svchost.exe -k netsvcs")
294+
295+
// ❌ BAD — REG_SZ won't expand %SystemRoot% at runtime
296+
RegOp.SetString(@"HKLM\...", "ImagePath", @"%SystemRoot%\System32\svchost.exe -k netsvcs")
297+
```
298+
299+
---
300+
301+
## IsApplicable — Hardware Gating for Tweaks
302+
303+
Tweaks that target specific software (Chrome, Firefox, Java, Docker) or hardware
304+
(NVIDIA GPU, WSL, Hyper-V) should set `IsApplicable` to grey them out in the GUI:
305+
306+
```csharp
307+
new TweakDef
308+
{
309+
Id = "chrome-disable-translate",
310+
IsApplicable = () => HardwareInfo.IsChromeInstalled(),
311+
ApplicabilityNote = "Google Chrome is not installed",
312+
...
313+
}
314+
```
315+
316+
`TweakEngine.IsApplicableOnHardware()` checks custom predicates first,
317+
then auto-detects from category (WSL, Virtualization) and tags (nvidia).
318+
MainForm caches results in `_inapplicableIds` at startup.

.vscode/extensions.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"recommendations": [
33
"ms-dotnettools.csharp",
44
"ms-dotnettools.csdevkit",
5+
"csharpier.csharpier-vscode",
56
"ms-vscode.powershell",
67
"github.copilot-chat",
78
"redhat.vscode-yaml",

.vscode/launch.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,31 @@
66
"type": "coreclr",
77
"request": "launch",
88
"preLaunchTask": "build: Solution (Debug)",
9-
"program": "${workspaceFolder}/src/RegiLattice.GUI/bin/x64/Debug/net10.0-windows/RegiLattice.GUI.exe",
9+
"program": "${workspaceFolder}/src/RegiLattice.GUI/bin/Debug/net10.0-windows/RegiLattice.GUI.exe",
1010
"args": [],
1111
"cwd": "${workspaceFolder}",
1212
"stopAtEntry": false,
13-
"console": "internalConsole",
13+
"console": "integratedTerminal",
1414
"justMyCode": true
1515
},
1616
{
1717
"name": "RegiLattice: GUI (Release)",
1818
"type": "coreclr",
1919
"request": "launch",
2020
"preLaunchTask": "build: Solution (Release)",
21-
"program": "${workspaceFolder}/src/RegiLattice.GUI/bin/x64/Release/net10.0-windows/RegiLattice.GUI.exe",
21+
"program": "${workspaceFolder}/src/RegiLattice.GUI/bin/Release/net10.0-windows/RegiLattice.GUI.exe",
2222
"args": [],
2323
"cwd": "${workspaceFolder}",
2424
"stopAtEntry": false,
25-
"console": "internalConsole",
25+
"console": "integratedTerminal",
2626
"justMyCode": true
2727
},
2828
{
2929
"name": "RegiLattice: CLI (list)",
3030
"type": "coreclr",
3131
"request": "launch",
3232
"preLaunchTask": "build: Solution (Debug)",
33-
"program": "${workspaceFolder}/src/RegiLattice.CLI/bin/x64/Debug/net10.0-windows/RegiLattice.dll",
33+
"program": "${workspaceFolder}/src/RegiLattice.CLI/bin/Debug/net10.0-windows/RegiLattice.dll",
3434
"args": ["list"],
3535
"cwd": "${workspaceFolder}",
3636
"stopAtEntry": false,
@@ -42,7 +42,7 @@
4242
"type": "coreclr",
4343
"request": "launch",
4444
"preLaunchTask": "build: Solution (Debug)",
45-
"program": "${workspaceFolder}/src/RegiLattice.CLI/bin/x64/Debug/net10.0-windows/RegiLattice.dll",
45+
"program": "${workspaceFolder}/src/RegiLattice.CLI/bin/Debug/net10.0-windows/RegiLattice.dll",
4646
"args": ["apply", "${input:tweakId}", "--yes"],
4747
"cwd": "${workspaceFolder}",
4848
"stopAtEntry": false,

.vscode/settings.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// ── C# / .NET ───────────────────────────────────────────────────────
33
"dotnet.defaultSolution": "RegiLattice.sln",
44
"[csharp]": {
5-
"editor.defaultFormatter": "ms-dotnettools.csharp",
5+
"editor.defaultFormatter": "csharpier.csharpier-vscode",
66
"editor.formatOnSave": true
77
},
88
// ── PowerShell ──────────────────────────────────────────────────────
@@ -51,9 +51,16 @@
5151
}
5252
},
5353
"terminal.integrated.shellIntegration.enabled": true,
54+
"terminal.integrated.shellIntegration.decorationsEnabled": "both",
55+
"terminal.integrated.shellIntegration.history": 500,
5456
"terminal.integrated.cwd": "${workspaceFolder}",
5557
"terminal.integrated.scrollback": 10000,
5658
"terminal.integrated.enablePersistentSessions": true,
59+
"terminal.integrated.commandsToSkipShell": [
60+
"workbench.action.terminal.focusFind",
61+
"workbench.action.togglePanel"
62+
],
63+
"terminal.integrated.enableImages": true,
5764
// Ensure Copilot chat agent terminals also see dotnet tools
5865
"github.copilot.chat.terminalChatLocation": "terminal",
5966
// ── Git ─────────────────────────────────────────────────────────────

src/RegiLattice.Core/Models/TweakDef.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ public sealed class TweakDef
8888
public Action<bool>? RemoveAction { get; init; }
8989
public Func<bool>? DetectAction { get; init; }
9090

91+
/// <summary>
92+
/// Optional update action for package-manager tweaks (e.g. scoop update, pip upgrade).
93+
/// Only meaningful for <see cref="TweakKind.PackageManager"/> tweaks.
94+
/// </summary>
95+
public Action<bool>? UpdateAction { get; init; }
96+
9197
/// <summary>Override the auto-detected TweakKind (set on tweaks with ApplyAction).</summary>
9298
public TweakKind? KindHint { get; init; }
9399

src/RegiLattice.Core/Services/HardwareInfo.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,83 @@ public static bool HasAmdGpu() =>
9898
/// <summary>Returns true if Hyper-V is available.</summary>
9999
public static bool HasHyperVAvailable() => DetectHardware().HasHyperV;
100100

101+
// ── Software detection (for TweakDef.IsApplicable) ─────────────────
102+
103+
/// <summary>Returns true if Google Chrome is installed.</summary>
104+
public static bool IsChromeInstalled() =>
105+
Microsoft.Win32.Registry.GetValue(
106+
@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe",
107+
null, null) is not null;
108+
109+
/// <summary>Returns true if Mozilla Firefox is installed.</summary>
110+
public static bool IsFirefoxInstalled() =>
111+
Microsoft.Win32.Registry.GetValue(
112+
@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\firefox.exe",
113+
null, null) is not null;
114+
115+
/// <summary>Returns true if Microsoft Edge is installed.</summary>
116+
public static bool IsEdgeInstalled() =>
117+
Microsoft.Win32.Registry.GetValue(
118+
@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe",
119+
null, null) is not null;
120+
121+
/// <summary>Returns true if Java (JRE/JDK) is installed.</summary>
122+
public static bool IsJavaInstalled() =>
123+
Microsoft.Win32.Registry.GetValue(
124+
@"HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment",
125+
"CurrentVersion", null) is not null ||
126+
Microsoft.Win32.Registry.GetValue(
127+
@"HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JDK",
128+
"CurrentVersion", null) is not null;
129+
130+
/// <summary>Returns true if Docker Desktop is installed.</summary>
131+
public static bool IsDockerInstalled() =>
132+
File.Exists(Path.Combine(
133+
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
134+
"Docker", "Docker", "Docker Desktop.exe"));
135+
136+
/// <summary>Returns true if Adobe Creative Cloud is installed.</summary>
137+
public static bool IsAdobeInstalled() =>
138+
Microsoft.Win32.Registry.GetValue(
139+
@"HKEY_LOCAL_MACHINE\SOFTWARE\Adobe\Adobe Creative Cloud",
140+
null, null) is not null ||
141+
Directory.Exists(Path.Combine(
142+
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
143+
"Adobe"));
144+
145+
/// <summary>Returns true if LibreOffice is installed.</summary>
146+
public static bool IsLibreOfficeInstalled() =>
147+
Microsoft.Win32.Registry.GetValue(
148+
@"HKEY_LOCAL_MACHINE\SOFTWARE\LibreOffice",
149+
null, null) is not null;
150+
151+
/// <summary>Returns true if Microsoft Office is installed.</summary>
152+
public static bool IsOfficeInstalled() =>
153+
Microsoft.Win32.Registry.GetValue(
154+
@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun",
155+
"InstallPath", null) is not null;
156+
157+
/// <summary>Returns true if RealVNC is installed.</summary>
158+
public static bool IsRealVncInstalled() =>
159+
Microsoft.Win32.Registry.GetValue(
160+
@"HKEY_LOCAL_MACHINE\SOFTWARE\RealVNC",
161+
null, null) is not null;
162+
163+
/// <summary>Returns true if VS Code is installed.</summary>
164+
public static bool IsVsCodeInstalled() =>
165+
Microsoft.Win32.Registry.GetValue(
166+
@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\code.cmd",
167+
null, null) is not null ||
168+
File.Exists(Path.Combine(
169+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
170+
"Programs", "Microsoft VS Code", "Code.exe"));
171+
172+
/// <summary>Returns true if Scoop package manager is installed.</summary>
173+
public static bool IsScoopInstalled() =>
174+
Directory.Exists(Path.Combine(
175+
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
176+
"scoop", "shims"));
177+
101178
public static CpuInfo DetectCpu()
102179
{
103180
var name = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER") ?? "Unknown";

0 commit comments

Comments
 (0)