From e4c4a5013f9f59516fe9c7752b736efc876b1ff1 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 11 Oct 2026 16:34:41 -0500 Subject: [PATCH 1/2] feat(template): add repository issue and community Help links Refs #3. Generate owner-specific Help URLs and require Discussions during setup/readback; preserve native About, activity and updates. Co-Authored-By: Codex (GPT-6) Signed-off-by: James Maes --- README.md | 1 + TEMPLATE.md | 7 +++ build/GitHubRepository.Tests.ps1 | 10 +++++ build/GitHubRepository.psm1 | 2 +- src/Kof22App.Core/AppIdentity.cs | 10 +++++ src/Kof22App/MainWindow.Help.cs | 45 +++++++++++++++++++ src/Kof22App/MainWindow.xaml | 6 ++- tests/Kof22App.Core.Tests/AppIdentityTests.cs | 2 + tests/Kof22App.UiTests/AppSession.cs | 9 +++- tests/Kof22App.UiTests/AppTests.cs | 17 +++++++ 10 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/Kof22App/MainWindow.Help.cs diff --git a/README.md b/README.md index 8357752..36be59a 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ The command-line tool ships in each release as a self-contained single-file startup update check on or off. Choices are saved immediately. - **Help → Activity log:** a live view of the app's log, with an errors-only filter and a shortcut to the log folder. +- **Help → View on GitHub / Submit an issue… / Community support…:** this app's repository, bug/feature templates and GitHub Discussions. - **Help → Check for updates** and **Help → About**. - **Command line:** `kof22-app greet --name Ada`, `kof22-app --version`, `kof22-app --help`. diff --git a/TEMPLATE.md b/TEMPLATE.md index f747fdc..ff935f2 100644 --- a/TEMPLATE.md +++ b/TEMPLATE.md @@ -7,6 +7,13 @@ repository files, and the shared CI, security, and release workflows. It impleme This file is not copied into generated apps. +## Default Help menu + +Generated apps include gclo's repository, issue-template and community-support +commands. Their URLs use the generated `AppInfo.RepositoryUrl`, so `--owner` and +`-n` point to the new project. Repository setup enables and validates GitHub +Discussions; this is required for Community support to work. + ## Create an app ```powershell diff --git a/build/GitHubRepository.Tests.ps1 b/build/GitHubRepository.Tests.ps1 index 6a33f1c..1788e4c 100644 --- a/build/GitHubRepository.Tests.ps1 +++ b/build/GitHubRepository.Tests.ps1 @@ -135,6 +135,16 @@ Describe 'Generated repository administration' ` @($script:Writes | Where-Object { $_ -match 'POST|environments/release$' }).Count | Should -Be 0 } + It 'requires community Discussions and detects disabled support without writing' ` + { + Invoke-GitHubRepositorySetup -Plan $script:Plan -Mode Apply -CheckSha ('a' * 40) -Confirm:$false + $script:State[$script:Root].has_discussions | Should -BeTrue + $script:State[$script:Root].has_discussions = $false + $script:Writes.Clear() + { Invoke-GitHubRepositorySetup -Plan $script:Plan -Mode Validate } | Should -Throw '*Configuration mismatch: repos/Example/Notes*' + $script:Writes.Count | Should -Be 0 + } + It 'previews all mutations without writing' ` { Invoke-GitHubRepositorySetup -Plan $script:Plan -Mode Apply -CheckSha ('a' * 40) -WhatIf diff --git a/build/GitHubRepository.psm1 b/build/GitHubRepository.psm1 index 572b03c..542d3c8 100644 --- a/build/GitHubRepository.psm1 +++ b/build/GitHubRepository.psm1 @@ -60,7 +60,7 @@ function Get-GitHubRepositoryPlan @{ Method = 'PATCH'; Path = $root; Body = @{ allow_squash_merge = $true; allow_merge_commit = $false; allow_rebase_merge = $false squash_merge_commit_title = 'PR_TITLE'; squash_merge_commit_message = 'PR_BODY' - delete_branch_on_merge = $true; has_issues = $true; has_wiki = $false + delete_branch_on_merge = $true; has_issues = $true; has_discussions = $true; has_wiki = $false homepage = "https://github.com/$Repository#readme" security_and_analysis = @{ secret_scanning = @{ status = 'enabled' }; secret_scanning_push_protection = @{ status = 'enabled' } diff --git a/src/Kof22App.Core/AppIdentity.cs b/src/Kof22App.Core/AppIdentity.cs index 4b48e70..adaab14 100644 --- a/src/Kof22App.Core/AppIdentity.cs +++ b/src/Kof22App.Core/AppIdentity.cs @@ -52,6 +52,16 @@ public static class AppIdentity + /// The app's bug/feature templates, independent of any upstream project credited in About. + public static Uri IssuesUrl { get; } = new(Info.RepositoryUrl!.AbsoluteUri.TrimEnd('/') + "/issues/new/choose"); + + + + /// Community questions and tips in the app's own repository. + public static Uri SupportUrl { get; } = new(Info.RepositoryUrl!.AbsoluteUri.TrimEnd('/') + "/discussions"); + + + /// Loads and saves at . public static SettingsStore SettingsStore { get; } = new(Paths.SettingsFile, AppSettingsContext.Default.AppSettings); diff --git a/src/Kof22App/MainWindow.Help.cs b/src/Kof22App/MainWindow.Help.cs new file mode 100644 index 0000000..c949866 --- /dev/null +++ b/src/Kof22App/MainWindow.Help.cs @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2026 James Maes + * SPDX-License-Identifier: MIT + */ + +using Microsoft.UI.Xaml; + + +namespace Kof22App; + +/// Opens only the app's fixed public Help destinations after deliberate native menu actions. +public sealed partial class MainWindow +{ + /// Shows this Windows app's repository in the user's default browser. + private async void GitHub_Click(object sender, RoutedEventArgs args) => await OpenHelpPageAsync(AppIdentity.Info.RepositoryUrl!); + + + + /// Offers the app's issue templates without sending private document data. + private async void SubmitIssue_Click(object sender, RoutedEventArgs args) => await OpenHelpPageAsync(AppIdentity.IssuesUrl); + + + + /// Opens community questions and tips in this app's repository. + private async void CommunitySupport_Click(object sender, RoutedEventArgs args) => await OpenHelpPageAsync(AppIdentity.SupportUrl); + + + + /// A missing browser or OS launch failure is visible; no document path/content is included. + private async Task OpenHelpPageAsync(Uri target) + { + try + { + if(!await global::Windows.System.Launcher.LaunchUriAsync(target)) + { + StatusText.Text = "The link could not be opened. Check your default browser."; + } + } + catch(Exception exception) when(exception is System.Runtime.InteropServices.COMException or InvalidOperationException or ArgumentException) + { + StatusText.Text = "The link could not be opened. Check your default browser."; + App.Log.Warning(FormattableString.Invariant($"A Help link could not be opened ({exception.GetType().Name}, 0x{exception.HResult:X8}).")); + } + } +} diff --git a/src/Kof22App/MainWindow.xaml b/src/Kof22App/MainWindow.xaml index d60c3f8..77ae468 100644 --- a/src/Kof22App/MainWindow.xaml +++ b/src/Kof22App/MainWindow.xaml @@ -26,7 +26,11 @@ - + + + + + diff --git a/tests/Kof22App.Core.Tests/AppIdentityTests.cs b/tests/Kof22App.Core.Tests/AppIdentityTests.cs index 361a188..0a17b15 100644 --- a/tests/Kof22App.Core.Tests/AppIdentityTests.cs +++ b/tests/Kof22App.Core.Tests/AppIdentityTests.cs @@ -20,6 +20,8 @@ public class AppIdentityTests Assert.EndsWith("_DATA_DIR", AppIdentity.Info.DataDirectoryVariable, StringComparison.Ordinal); Assert.NotNull(AppIdentity.Info.RepositoryUrl); Assert.NotEmpty(AppIdentity.Info.Attributions); + Assert.Equal("https://github.com/KofTwentyTwo/windows-app-template/issues/new/choose", AppIdentity.IssuesUrl.AbsoluteUri); + Assert.Equal("https://github.com/KofTwentyTwo/windows-app-template/discussions", AppIdentity.SupportUrl.AbsoluteUri); } diff --git a/tests/Kof22App.UiTests/AppSession.cs b/tests/Kof22App.UiTests/AppSession.cs index c9a9ec7..1f11aeb 100644 --- a/tests/Kof22App.UiTests/AppSession.cs +++ b/tests/Kof22App.UiTests/AppSession.cs @@ -151,7 +151,12 @@ public sealed class AppSession : IDisposable /// expand (its flyout opens before the menu is ready), so the menu is re-opened /// until the item appears. /// - public void InvokeMenuItem(string menuName, string itemAutomationId) + public void InvokeMenuItem(string menuName, string itemAutomationId) => Invoke(OpenMenuItem(menuName, itemAutomationId)); + + + + /// Opens an owned native menu and returns its item without launching an external destination. + public AutomationElement OpenMenuItem(string menuName, string itemAutomationId) { MainWindow.SetForeground(); AutomationElement menu = WaitFor( @@ -171,7 +176,7 @@ public sealed class AppSession : IDisposable Wait.UntilInputIsProcessed(); item = Retry.WhileNull(() => FindInApp(cf => cf.ByAutomationId(itemAutomationId)), timeout: TimeSpan.FromSeconds(3), interval: s_pollInterval, ignoreException: true).Result; } - Invoke(item ?? throw new TimeoutException($"Menu item '{itemAutomationId}' never appeared under '{menuName}'. (app exited: {App.HasExited})")); + return item ?? throw new TimeoutException($"Menu item '{itemAutomationId}' never appeared under '{menuName}'. (app exited: {App.HasExited})"); } diff --git a/tests/Kof22App.UiTests/AppTests.cs b/tests/Kof22App.UiTests/AppTests.cs index fbf0498..a9063a8 100644 --- a/tests/Kof22App.UiTests/AppTests.cs +++ b/tests/Kof22App.UiTests/AppTests.cs @@ -36,6 +36,23 @@ public class AppTests + /// The real Help menu exposes the app's repository, issue reporting and community commands without launching a browser. + [Fact] + public void Help_PublicDestinations_AreAvailableInTheNativeMenu() + { + using var session = new AppSession(); + _ = session.OpenMenuItem("Help", "MenuGitHub"); + foreach((string identity, string label) in new[] { ("MenuGitHub", "GitHub"), ("MenuSubmitIssue", "issue"), ("MenuCommunitySupport", "support") }) + { + AutomationElement item = session.WaitForElement(identity); + Assert.True(item.IsEnabled); + Assert.False(item.IsOffscreen); + Assert.Contains(label, item.Name, StringComparison.OrdinalIgnoreCase); + } + } + + + /// About: shows the build version. [Fact] public void About_ShowsTheBuildVersion() -- 2.54.0 From 2688e3fc75bd28f9584ed5325ff1bad54af88a32 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 11 Oct 2026 16:41:03 -0500 Subject: [PATCH 2/2] fix(ci): retain analyzer rules across isolated casing passes Refs #3. Match the verified MarkdownViewr mitigation for the terminal PSScriptAnalyzer 1.25.0 Stack empty crash. All configured rules pass locally on every build script. Co-Authored-By: Codex (GPT-6) Signed-off-by: James Maes --- .github/workflows/scripts.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/scripts.yml b/.github/workflows/scripts.yml index 6987c96..e6d8bb8 100644 --- a/.github/workflows/scripts.yml +++ b/.github/workflows/scripts.yml @@ -54,7 +54,12 @@ jobs: # Analyze each file separately: recursive analysis can crash inside 1.25.0. foreach($script in Get-ChildItem -LiteralPath build -Recurse -File | Where-Object Extension -In '.ps1', '.psm1', '.psd1') { - $findings = @(Invoke-ScriptAnalyzer -Path $script.FullName -Settings ./PSScriptAnalyzerSettings.psd1) + # Run casing separately: the combined analyzer pass crashes in 1.25.0. + # Both passes together retain every configured rule and severity. + $findings = @( + Invoke-ScriptAnalyzer -Path $script.FullName -Settings ./PSScriptAnalyzerSettings.psd1 -IncludeRule PSUseCorrectCasing + Invoke-ScriptAnalyzer -Path $script.FullName -Settings ./PSScriptAnalyzerSettings.psd1 -ExcludeRule PSUseCorrectCasing + ) if($findings.Count -gt 0) { $findings | Format-Table; throw "Script analysis failed: $($script.Name)" } } - name: Verify script boundary tests -- 2.54.0