From 69910cbd8115d1ea27b585bf44d7382973d4addb Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Mon, 13 Jul 2026 11:38:54 -0500 Subject: [PATCH] Rename Shell action type to Open The action that launches apps, files, folders, and URLs was surfaced to users as 'Shell', which is developer jargon. Rename the user-facing label and all internal identifiers to 'Open', matching the editor's own description and standard Windows vocabulary. Settings persist Type as an integer (Open is still 2), so existing configurations load unchanged. No behavior change; UseShellExecute launching is untouched. --- AGENTS.md | 2 +- .../Actions/ActionDefaultsServiceTests.cs | 14 +++++------ .../Actions/ActionDropFactoryTests.cs | 16 ++++++------- .../Actions/ActionsSettingsViewModelTests.cs | 4 ++-- ...ltsTests.cs => OpenActionDefaultsTests.cs} | 16 ++++++------- RadialActions.Tests/Actions/PieActionTests.cs | 10 ++++---- .../Settings/SettingsSerializationTests.cs | 4 ++-- RadialActions/Actions/Action.cs | 20 ++++++++-------- .../Actions/ActionDefaultsService.cs | 24 +++++++++---------- RadialActions/Actions/ActionDropFactory.cs | 10 ++++---- ...ctionDefaults.cs => OpenActionDefaults.cs} | 10 ++++---- RadialActions/Properties/Settings.cs | 2 +- RadialActions/Settings/ActionEditorView.xaml | 6 ++--- .../Settings/ActionEditorViewModel.cs | 12 +++++----- 14 files changed, 75 insertions(+), 75 deletions(-) rename RadialActions.Tests/Actions/{ShellActionDefaultsTests.cs => OpenActionDefaultsTests.cs} (57%) rename RadialActions/Actions/{ShellActionDefaults.cs => OpenActionDefaults.cs} (74%) diff --git a/AGENTS.md b/AGENTS.md index 3be9caa..11ef3b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,7 +59,7 @@ For documentation-only changes, explain why build or test commands were skipped. - Radial Actions is a lightweight Windows utility opened by a global hotkey. Startup, hotkey registration, tray behavior, and menu display should stay fast and predictable. - User-configured actions must not execute during tests, settings load, preview rendering, or validation. - Invalid or stale settings should normalize to safe defaults instead of crashing on startup. -- Shell actions must preserve explicit target, arguments, and working directory behavior. +- Open actions (formerly Shell) must preserve explicit target, arguments, and working directory behavior. - Key actions must accept known media/volume actions and validated custom shortcuts; invalid shortcuts should fail clearly. - Update checks must remain controlled by settings and should not introduce surprise network work in deterministic tests. diff --git a/RadialActions.Tests/Actions/ActionDefaultsServiceTests.cs b/RadialActions.Tests/Actions/ActionDefaultsServiceTests.cs index 603d7a3..fb0157a 100644 --- a/RadialActions.Tests/Actions/ActionDefaultsServiceTests.cs +++ b/RadialActions.Tests/Actions/ActionDefaultsServiceTests.cs @@ -31,7 +31,7 @@ public void ApplyKeyDefaults_UpdatesIconOnlyForBlankDefaultOrPreviousAutoValue() } [Fact] - public void ApplyShellDefaults_PreservesManualNameIconAndWorkingDirectory() + public void ApplyOpenDefaults_PreservesManualNameIconAndWorkingDirectory() { Directory.CreateDirectory(_tempRoot); var firstRoot = Path.Combine(_tempRoot, "First"); @@ -42,12 +42,12 @@ public void ApplyShellDefaults_PreservesManualNameIconAndWorkingDirectory() var secondPath = Path.Combine(secondRoot, "Second.exe"); var service = new ActionDefaultsService(); - var action = PieAction.CreateShellAction("Manual Name", firstPath, "\U0001F6E0\uFE0F", workingDirectory: _tempRoot); + var action = PieAction.CreateOpenAction("Manual Name", firstPath, "\U0001F6E0\uFE0F", workingDirectory: _tempRoot); service.TrackExistingDefaults([action]); action.Parameter = secondPath; - service.ApplyShellDefaults(action, action.Parameter); + service.ApplyOpenDefaults(action, action.Parameter); Assert.Equal("Manual Name", action.Name); Assert.Equal("\U0001F6E0\uFE0F", action.Icon); @@ -55,16 +55,16 @@ public void ApplyShellDefaults_PreservesManualNameIconAndWorkingDirectory() } [Fact] - public void ApplyShellDefaults_LegacyStarIcon_UpgradesToSelectedDefaultIcon() + public void ApplyOpenDefaults_LegacyStarIcon_UpgradesToSelectedDefaultIcon() { Directory.CreateDirectory(_tempRoot); var targetPath = Path.Combine(_tempRoot, "First.exe"); var service = new ActionDefaultsService(); - var action = PieAction.CreateShellAction(PieAction.DefaultName, targetPath, ActionDefaultsService.LegacyDefaultIcon); + var action = PieAction.CreateOpenAction(PieAction.DefaultName, targetPath, ActionDefaultsService.LegacyDefaultIcon); - service.ApplyShellDefaults(action, action.Parameter); + service.ApplyOpenDefaults(action, action.Parameter); - Assert.Equal(ShellActionDefaults.FileIcon, action.Icon); + Assert.Equal(OpenActionDefaults.FileIcon, action.Icon); } public void Dispose() diff --git a/RadialActions.Tests/Actions/ActionDropFactoryTests.cs b/RadialActions.Tests/Actions/ActionDropFactoryTests.cs index d540c8f..83b66f0 100644 --- a/RadialActions.Tests/Actions/ActionDropFactoryTests.cs +++ b/RadialActions.Tests/Actions/ActionDropFactoryTests.cs @@ -157,9 +157,9 @@ public void CreateAction_BlankTarget_UsesDefaultNameAndFileIcon() { var action = ActionDropFactory.CreateAction(string.Empty); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.Equal(PieAction.DefaultName, action.Name); - Assert.Equal(ShellActionDefaults.FileIcon, action.Icon); + Assert.Equal(OpenActionDefaults.FileIcon, action.Icon); } [Fact] @@ -167,10 +167,10 @@ public void CreateAction_Url_FillsWebDefaults() { var action = ActionDropFactory.CreateAction("https://example.com/path"); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.Equal("https://example.com/path", action.Parameter); Assert.Equal("example.com", action.Name); - Assert.Equal(ShellActionDefaults.WebIcon, action.Icon); + Assert.Equal(OpenActionDefaults.WebIcon, action.Icon); Assert.Equal(string.Empty, action.WorkingDirectory); } @@ -183,10 +183,10 @@ public void CreateAction_Directory_FillsFolderDefaults() var action = ActionDropFactory.CreateAction(folder); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.Equal(folder, action.Parameter); Assert.Equal("Games", action.Name); - Assert.Equal(ShellActionDefaults.FolderIcon, action.Icon); + Assert.Equal(OpenActionDefaults.FolderIcon, action.Icon); Assert.Equal(folder, action.WorkingDirectory); } @@ -199,10 +199,10 @@ public void CreateAction_File_FillsFileDefaults() var action = ActionDropFactory.CreateAction(file); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.Equal(file, action.Parameter); Assert.Equal("Notes", action.Name); - Assert.Equal(ShellActionDefaults.FileIcon, action.Icon); + Assert.Equal(OpenActionDefaults.FileIcon, action.Icon); Assert.Equal(_tempRoot, action.WorkingDirectory); } diff --git a/RadialActions.Tests/Actions/ActionsSettingsViewModelTests.cs b/RadialActions.Tests/Actions/ActionsSettingsViewModelTests.cs index 0d3ac16..c0aeb74 100644 --- a/RadialActions.Tests/Actions/ActionsSettingsViewModelTests.cs +++ b/RadialActions.Tests/Actions/ActionsSettingsViewModelTests.cs @@ -81,7 +81,7 @@ public void AddDroppedTargets_InsertsAfterSelectionAndSelectsLast() var addedA = viewModel.Actions[2]; var addedB = viewModel.Actions[3]; Assert.Equal([first, second, addedA, addedB, third], viewModel.Actions); - Assert.Equal(ActionType.Shell, addedA.Type); + Assert.Equal(ActionType.Open, addedA.Type); Assert.Equal("https://a.com", addedA.Parameter); Assert.Equal("https://b.com", addedB.Parameter); Assert.Same(addedB, viewModel.SelectedAction); @@ -121,7 +121,7 @@ public void ActionEditorViewModel_ActionTypes_HidesNoneType() { var viewModel = new ActionEditorViewModel(new ActionDefaultsService(), []); - Assert.Equal([ActionType.Key, ActionType.Shell], viewModel.ActionTypes.Select(option => option.Type)); + Assert.Equal([ActionType.Key, ActionType.Open], viewModel.ActionTypes.Select(option => option.Type)); } [Fact] diff --git a/RadialActions.Tests/Actions/ShellActionDefaultsTests.cs b/RadialActions.Tests/Actions/OpenActionDefaultsTests.cs similarity index 57% rename from RadialActions.Tests/Actions/ShellActionDefaultsTests.cs rename to RadialActions.Tests/Actions/OpenActionDefaultsTests.cs index 6d23e95..d6952ec 100644 --- a/RadialActions.Tests/Actions/ShellActionDefaultsTests.cs +++ b/RadialActions.Tests/Actions/OpenActionDefaultsTests.cs @@ -1,21 +1,21 @@ namespace RadialActions.Tests; -public sealed class ShellActionDefaultsTests : IDisposable +public sealed class OpenActionDefaultsTests : IDisposable { private readonly string _tempRoot = Path.Combine(Path.GetTempPath(), "RadialActions.Tests", Guid.NewGuid().ToString("N")); [Fact] public void GetDefaults_EmptyTarget_ReturnsNull() { - Assert.Null(ShellActionDefaults.FromTarget(string.Empty)); + Assert.Null(OpenActionDefaults.FromTarget(string.Empty)); } [Fact] public void GetDefaults_Url_UsesHostNameAndWebIcon() { - var defaults = ShellActionDefaults.FromTarget("https://docs.github.com/en"); + var defaults = OpenActionDefaults.FromTarget("https://docs.github.com/en"); - Assert.Equal(new ShellActionDefaults("docs.github.com", ShellActionDefaults.WebIcon, string.Empty), defaults); + Assert.Equal(new OpenActionDefaults("docs.github.com", OpenActionDefaults.WebIcon, string.Empty), defaults); } [Fact] @@ -25,9 +25,9 @@ public void GetDefaults_FilePath_UsesFileNameIconAndWorkingDirectory() var filePath = Path.Combine(_tempRoot, "Example App.exe"); File.WriteAllText(filePath, "test"); - var defaults = ShellActionDefaults.FromTarget(filePath); + var defaults = OpenActionDefaults.FromTarget(filePath); - Assert.Equal(new ShellActionDefaults("Example App", ShellActionDefaults.FileIcon, _tempRoot), defaults); + Assert.Equal(new OpenActionDefaults("Example App", OpenActionDefaults.FileIcon, _tempRoot), defaults); } [Fact] @@ -36,9 +36,9 @@ public void GetDefaults_FolderUri_UsesFolderNameIconAndWorkingDirectory() var folderPath = Path.Combine(_tempRoot, "Docs"); Directory.CreateDirectory(folderPath); - var defaults = ShellActionDefaults.FromTarget(new Uri(folderPath).AbsoluteUri); + var defaults = OpenActionDefaults.FromTarget(new Uri(folderPath).AbsoluteUri); - Assert.Equal(new ShellActionDefaults("Docs", ShellActionDefaults.FolderIcon, folderPath), defaults); + Assert.Equal(new OpenActionDefaults("Docs", OpenActionDefaults.FolderIcon, folderPath), defaults); } public void Dispose() diff --git a/RadialActions.Tests/Actions/PieActionTests.cs b/RadialActions.Tests/Actions/PieActionTests.cs index 5ab5781..a7fe0b0 100644 --- a/RadialActions.Tests/Actions/PieActionTests.cs +++ b/RadialActions.Tests/Actions/PieActionTests.cs @@ -15,11 +15,11 @@ public void CreateKeyAction_UnknownId_UsesFirstKnownAction() } [Fact] - public void CreateShellAction_SetsExpectedFields() + public void CreateOpenAction_SetsExpectedFields() { - var action = PieAction.CreateShellAction("Docs", "https://example.com", "*", "--foo", "C:\\"); + var action = PieAction.CreateOpenAction("Docs", "https://example.com", "*", "--foo", "C:\\"); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.True(action.IsEnabled); Assert.Equal("Docs", action.Name); Assert.Equal("*", action.Icon); @@ -59,9 +59,9 @@ public void Execute_NoneAction_ThrowsInvalidOperationException() } [Fact] - public void Execute_ShellActionWithoutTarget_ThrowsInvalidOperationException() + public void Execute_OpenActionWithoutTarget_ThrowsInvalidOperationException() { - var action = PieAction.CreateShellAction("Docs", string.Empty); + var action = PieAction.CreateOpenAction("Docs", string.Empty); var ex = Assert.Throws(() => action.Execute()); diff --git a/RadialActions.Tests/Settings/SettingsSerializationTests.cs b/RadialActions.Tests/Settings/SettingsSerializationTests.cs index bb552eb..f0f3890 100644 --- a/RadialActions.Tests/Settings/SettingsSerializationTests.cs +++ b/RadialActions.Tests/Settings/SettingsSerializationTests.cs @@ -69,7 +69,7 @@ public void SerializeToJson_RoundTripsCoreValues() settings.Actions = new System.Collections.ObjectModel.ObservableCollection { PieAction.CreateKeyAction("Mute"), - PieAction.CreateShellAction("Explorer", "explorer.exe") + PieAction.CreateOpenAction("Explorer", "explorer.exe") }; settings.Actions[1].IsEnabled = false; @@ -83,7 +83,7 @@ public void SerializeToJson_RoundTripsCoreValues() Assert.Equal(ActionType.Key, loaded.Actions[0].Type); Assert.Equal("Mute", loaded.Actions[0].Parameter); Assert.True(loaded.Actions[0].IsEnabled); - Assert.Equal(ActionType.Shell, loaded.Actions[1].Type); + Assert.Equal(ActionType.Open, loaded.Actions[1].Type); Assert.Equal("explorer.exe", loaded.Actions[1].Parameter); Assert.False(loaded.Actions[1].IsEnabled); } diff --git a/RadialActions/Actions/Action.cs b/RadialActions/Actions/Action.cs index e1bf1ea..5fa3693 100644 --- a/RadialActions/Actions/Action.cs +++ b/RadialActions/Actions/Action.cs @@ -20,9 +20,9 @@ public enum ActionType Key = 1, /// - /// Launch an app, open a file, or open a URL using shell execution. + /// Open an app, file, folder, or URL using shell execution. /// - Shell = 2, + Open = 2, } /// @@ -114,13 +114,13 @@ public static bool TryGetKeyAction(string id, out KeyActionDefinition definition private string _parameter = string.Empty; /// - /// Additional arguments for shell actions. + /// Additional arguments for Open actions. /// [ObservableProperty] private string _arguments = string.Empty; /// - /// Optional working directory for shell actions. + /// Optional working directory for Open actions. /// [ObservableProperty] private string _workingDirectory = string.Empty; @@ -157,12 +157,12 @@ public static PieAction CreateKeyAction(string keyActionId) } /// - /// Creates a shell action. + /// Creates an Open action. /// - public static PieAction CreateShellAction(string name, string target, string icon = "📁", string arguments = "", string workingDirectory = "") + public static PieAction CreateOpenAction(string name, string target, string icon = "📁", string arguments = "", string workingDirectory = "") => new(name, icon) { - Type = ActionType.Shell, + Type = ActionType.Open, Parameter = target, Arguments = arguments, WorkingDirectory = workingDirectory @@ -182,8 +182,8 @@ public void Execute() case ActionType.Key: ExecuteKey(); return; - case ActionType.Shell: - ExecuteShell(); + case ActionType.Open: + ExecuteOpen(); return; default: throw new NotSupportedException("Action type is not supported"); @@ -207,7 +207,7 @@ private void ExecuteKey() ActionUtil.SimulateKeyboardShortcut(Parameter); } - private void ExecuteShell() + private void ExecuteOpen() { if (string.IsNullOrWhiteSpace(Parameter)) throw new InvalidOperationException("Launch target not configured"); diff --git a/RadialActions/Actions/ActionDefaultsService.cs b/RadialActions/Actions/ActionDefaultsService.cs index 6a1f264..f2a087b 100644 --- a/RadialActions/Actions/ActionDefaultsService.cs +++ b/RadialActions/Actions/ActionDefaultsService.cs @@ -5,20 +5,20 @@ public sealed class ActionDefaultsService public const string LegacyDefaultIcon = "\u2B50"; private readonly Dictionary _autoKeyDefaults = []; - private readonly Dictionary _autoShellDefaults = []; + private readonly Dictionary _autoOpenDefaults = []; - public ShellActionDefaults? GetShellDefaults(string target) => ShellActionDefaults.FromTarget(target); + public OpenActionDefaults? GetOpenDefaults(string target) => OpenActionDefaults.FromTarget(target); public void Forget(PieAction action) { _autoKeyDefaults.Remove(action); - _autoShellDefaults.Remove(action); + _autoOpenDefaults.Remove(action); } public void TrackExistingDefaults(IEnumerable actions) { _autoKeyDefaults.Clear(); - _autoShellDefaults.Clear(); + _autoOpenDefaults.Clear(); foreach (var action in actions) { @@ -29,12 +29,12 @@ public void TrackExistingDefaults(IEnumerable actions) _autoKeyDefaults[action] = definition; } } - else if (action.Type == ActionType.Shell) + else if (action.Type == ActionType.Open) { - var defaults = GetShellDefaults(action.Parameter); + var defaults = GetOpenDefaults(action.Parameter); if (defaults.HasValue) { - _autoShellDefaults[action] = defaults.Value; + _autoOpenDefaults[action] = defaults.Value; } } } @@ -69,16 +69,16 @@ public void ApplyKeyDefaults(PieAction action, KeyActionDefinition definition) _autoKeyDefaults[action] = definition; } - public void ApplyShellDefaults(PieAction action, string target) + public void ApplyOpenDefaults(PieAction action, string target) { - if (action.Type != ActionType.Shell) + if (action.Type != ActionType.Open) return; - var defaults = GetShellDefaults(target); + var defaults = GetOpenDefaults(target); if (!defaults.HasValue) return; - _autoShellDefaults.TryGetValue(action, out var previous); + _autoOpenDefaults.TryGetValue(action, out var previous); var next = defaults.Value; if (ShouldApplyDefault(action.Name, PieAction.DefaultName, previous.Name ?? string.Empty)) @@ -98,7 +98,7 @@ public void ApplyShellDefaults(PieAction action, string target) action.WorkingDirectory = next.WorkingDirectory; } - _autoShellDefaults[action] = next; + _autoOpenDefaults[action] = next; } private static bool ShouldApplyDefault(string currentValue, string defaultValue, string previousValue) diff --git a/RadialActions/Actions/ActionDropFactory.cs b/RadialActions/Actions/ActionDropFactory.cs index 733b91b..f83c17d 100644 --- a/RadialActions/Actions/ActionDropFactory.cs +++ b/RadialActions/Actions/ActionDropFactory.cs @@ -5,7 +5,7 @@ namespace RadialActions; /// -/// Turns data dropped onto the actions list (files, folders, apps, or links) into prefilled shell actions. +/// Turns data dropped onto the actions list (files, folders, apps, or links) into prefilled Open actions. /// public static class ActionDropFactory { @@ -57,14 +57,14 @@ void Add(string candidate) } /// - /// Builds a shell action for a single dropped target, prefilling its name, icon, and working directory from . + /// Builds an Open action for a single dropped target, prefilling its name, icon, and working directory from . /// public static PieAction CreateAction(string target) { - var defaults = ShellActionDefaults.FromTarget(target) - ?? new ShellActionDefaults(PieAction.DefaultName, ShellActionDefaults.FileIcon, string.Empty); + var defaults = OpenActionDefaults.FromTarget(target) + ?? new OpenActionDefaults(PieAction.DefaultName, OpenActionDefaults.FileIcon, string.Empty); - return PieAction.CreateShellAction(defaults.Name, target, defaults.Icon, workingDirectory: defaults.WorkingDirectory); + return PieAction.CreateOpenAction(defaults.Name, target, defaults.Icon, workingDirectory: defaults.WorkingDirectory); } // Reads the most specific link text the drop advertises, preferring the dedicated URL formats over plain text. diff --git a/RadialActions/Actions/ShellActionDefaults.cs b/RadialActions/Actions/OpenActionDefaults.cs similarity index 74% rename from RadialActions/Actions/ShellActionDefaults.cs rename to RadialActions/Actions/OpenActionDefaults.cs index cacc6e2..f5a32ab 100644 --- a/RadialActions/Actions/ShellActionDefaults.cs +++ b/RadialActions/Actions/OpenActionDefaults.cs @@ -2,13 +2,13 @@ namespace RadialActions; -public readonly record struct ShellActionDefaults(string Name, string Icon, string WorkingDirectory) +public readonly record struct OpenActionDefaults(string Name, string Icon, string WorkingDirectory) { public const string WebIcon = "\U0001F310"; public const string FolderIcon = "\U0001F4C2"; public const string FileIcon = "\U0001F4C1"; - public static ShellActionDefaults? FromTarget(string target) + public static OpenActionDefaults? FromTarget(string target) { if (string.IsNullOrWhiteSpace(target)) return null; @@ -21,13 +21,13 @@ public readonly record struct ShellActionDefaults(string Name, string Icon, stri } var name = string.IsNullOrWhiteSpace(uri.Host) ? uri.AbsoluteUri : uri.Host; - return new ShellActionDefaults(name, WebIcon, string.Empty); + return new OpenActionDefaults(name, WebIcon, string.Empty); } return FromPath(target); } - private static ShellActionDefaults FromPath(string path) + private static OpenActionDefaults FromPath(string path) { var isDirectory = Directory.Exists(path); var name = Path.GetFileNameWithoutExtension(path); @@ -44,6 +44,6 @@ private static ShellActionDefaults FromPath(string path) var icon = isDirectory ? FolderIcon : FileIcon; var workingDirectory = isDirectory ? path : Path.GetDirectoryName(path) ?? string.Empty; - return new ShellActionDefaults(name, icon, workingDirectory); + return new OpenActionDefaults(name, icon, workingDirectory); } } diff --git a/RadialActions/Properties/Settings.cs b/RadialActions/Properties/Settings.cs index 23b5df7..d739700 100644 --- a/RadialActions/Properties/Settings.cs +++ b/RadialActions/Properties/Settings.cs @@ -69,7 +69,7 @@ public static ObservableCollection CreateDefaultActions() PieAction.CreateKeyAction("PreviousTrack"), PieAction.CreateKeyAction("NextTrack"), PieAction.CreateKeyAction("Mute"), - PieAction.CreateShellAction("File Explorer", "explorer.exe", "📁"), + PieAction.CreateOpenAction("File Explorer", "explorer.exe", "📁"), ]; } diff --git a/RadialActions/Settings/ActionEditorView.xaml b/RadialActions/Settings/ActionEditorView.xaml index a066c41..e404d45 100644 --- a/RadialActions/Settings/ActionEditorView.xaml +++ b/RadialActions/Settings/ActionEditorView.xaml @@ -51,7 +51,7 @@ - + Visibility="{Binding SelectedActionType, Converter={local:MatchToVisibilityConverter}, ConverterParameter={x:Static local:ActionType.Open}}"> @@ -118,7 +118,7 @@ Text="{Binding SelectedAction.Parameter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />