barbatos-templates
The Barbatos.Wpf dotnet new templates under templates/ - item templates aq-view and aqv-form, the barbatos-wpf-app starter project with its --Include* module flags and AquariusEffective, template.json
SKILL.md.claude/skills/barbatos-templates/SKILL.md
GitHubBarbatos.Wpf dotnet new templates
Three templates scaffold consumer code: aq-view (View + ViewModel, the Vue single-file
component analogue), aqv-form (a validated form) and barbatos-wpf-app (a starter app, the
npm create vue@latest analogue). They are installed from their folders in this repo; nothing
packs them into a template NuGet package. The generated code is the user's code - the APIs it
calls belong to the feature skills, and the version bump itself to barbatos-package-authoring.
Sources of truth
templates/README.md- the user-facing description of every template - plus the two copies of its commands: the root README Templates section (install and IDE notes) and the scaffolding note in the Aquarius README Quick Start (src/Barbatos.Wpf.Aquarius/README.md). All three must move with any template change.- Each template's
.template.config/template.json(andide.host.jsonfor the project template) plus the content files beside it. The files win over the READMEs. - barbatos-wpf-app.md - read when editing the project template or answering what one flag generates: every file, symbol, conditional seam and identity setting.
- APIs the templates mirror:
samples/Barbatos.Wpf.Core.Sample/WpfProgram.cs(theConfigureXxxcalls),samples/Barbatos.Wpf.AquariusRouter.Sample/WpfProgram.cs(theAddAquarius/UseAquariuswiring),samples/Barbatos.Wpf.AquariusValee.Sample/(i18n). - No test and no CI step builds a template. The template folders are not in
Barbatos.Wpf.slnx, andbarbatos-wpf-app'sMainWindow.xamlis not even well-formed XAML before the template engine runs. The only check is instantiating and building the output, which works on any OS with-p:EnableWindowsTargeting=true(see the loop below); running it needs Windows.
The map
Layout is package, then kind, then template: templates/<Package or Combined>/{item-templates, project-templates}/<template>/.template.config/template.json. An empty kind folder holds a
.gitkeeper so git keeps it (Core, AquariusRouter and Combined item-templates, and every
project-templates except Combined's, are placeholders).
| shortName | Folder | identity | sourceName | Symbols |
|---|---|---|---|---|
aq-view |
Barbatos.Wpf.Aquarius/item-templates/aquarius-view |
Barbatos.Wpf.Aquarius.Templates.View |
AquariusComponent |
namespace (replaces AquariusTemplateNamespace) |
aqv-form |
Barbatos.Wpf.AquariusValee/item-templates/valee-form |
Barbatos.Wpf.AquariusValee.Templates.Form |
ValeeForm |
namespace (replaces ValeeTemplateNamespace) |
barbatos-wpf-app |
Combined/project-templates/barbatos-wpf-app |
Barbatos.Wpf.Templates.CombinedApp |
BarbatosWpfApp |
PackageVersion, 15 Include* bools, computed AquariusEffective |
Item templates set "preferNameDirectory": false (files land in the current folder or -o);
the project template sets true (-n MyApp creates MyApp/).
Using them
From the repo root, once per template:
dotnet new install ./templates/Barbatos.Wpf.Aquarius/item-templates/aquarius-view
dotnet new install ./templates/Barbatos.Wpf.AquariusValee/item-templates/valee-form
dotnet new install ./templates/Combined/project-templates/barbatos-wpf-app
dotnet new uninstall <same folder> removes one; dotnet new uninstall with no argument lists
what is installed and from where.
aq-view and aqv-form
dotnet new aq-view -n Dashboard --namespace MyApp.Features.Dashboard
dotnet new aqv-form -n Checkout --namespace MyApp.Features.Checkout
- Always pass
--namespace. The CLI cannot read the target project'sRootNamespace: amsbuild:-bound symbol is silently skipped on the CLI path (confirmed by testing, pertemplates/README.md). The defaultChangeMe.Namespaceis deliberately fake so a forgotten flag is visible instead of landing in a plausible wrong namespace. - Pass the bare feature name to
-n.sourceNameis replaced inside file and type names, so-n DashboardyieldsDashboardView/DashboardViewModel;-n DashboardViewyieldsDashboardViewView. - Keep View and ViewModel in one namespace. Both rely on
aq:Setup.Enable="True", whose defaultSetup.Resolvermaps the View's full nameX.YViewtoX.YViewModel- moving the ViewModel to another namespace leavesDataContextnull, silently unlessSetup.ThrowOnUnresolved(orAquariusOptions.ThrowOnUnresolvedViewModels, applied only byUseAquarius()) is on. aq-viewoutput: aUserControlwithaq:Setup.Enableandaq:Lifecycle.Enable(so the ViewModel's lifecycle hooks fire), a code-behind with onlyInitializeComponent(), and asealed partialObservableObjectwith one[ObservableProperty]. The ViewModel is parameterless, soSetup.Resolve'sActivatorfallback builds it; register it in DI only once it gains constructor dependencies.aqv-formoutput: schema mode (Form.FromModel(new XModel())) over a DataAnnotations model with[Compare]onConfirmPassword- schema mode is the only mode where[Compare]can read its sibling - plusConfirmPassword.DependsOn(Password), because onlyIDependsOnPropertiesattributes get automatic re-validation and without it fixing the password leaves a stale "don't match" error.HandleSubmit(onValid, onInvalid)gates only onIsSubmitting, never validity, so an untouched submit surfaces every error.- The consuming project needs only the package (
Barbatos.Wpf.Aquarius/Barbatos.Wpf.AquariusValee).CommunityToolkit.Mvvmand its[ObservableProperty]/[RelayCommand]generators arrive transitively:aq-viewandaqv-formoutput generated into abarbatos-wpf-appproject (which has no directCommunityToolkit.Mvvmreference) builds with 0 warnings, and the generated members are in the assembly.
barbatos-wpf-app
dotnet new barbatos-wpf-app -n MyApp
dotnet new barbatos-wpf-app -n MyApp --IncludeAquariusRouter true --IncludeValee true --IncludeMcp true
dotnet new barbatos-wpf-app -n MyApp --help # every flag, description and default
dotnet new barbatos-wpf-app -n MyApp --dry-run # the file list, nothing written
- Defaults:
IncludeAquariusandIncludeI18non, everything else off. Turn a default off with--IncludeAquarius false. - Flags: Aquarius-family
IncludeAquarius,IncludeAquariusRouter,IncludeValee;IncludeI18n; eleven Core featuresIncludeSingleInstance,IncludeRunOnStartup,IncludeTrayIcon,IncludeKeepAwake,IncludeNotifications,IncludePushNotifications,IncludePeriodicServices,IncludeInputSystem,IncludeDialogs,IncludeMcp,IncludeSplashScreen. What each one generates: barbatos-wpf-app.md. - Router or Valee alone still references Aquarius, through
AquariusEffective- the Aquarius checkbox can stay unticked while its content is included. --PackageVersionpicks theBarbatos.Wpf.*version; it must be one that exists on the feed the generated project restores from.- Visual Studio's new-project wizard renders each
Include*symbol as a checkbox, driven byide.host.jsonsymbolInfo[].isVisibleplus the symbol'sdisplayName/description. Rider shows only a raw extra-parameters field, so pass the same flags there by hand. IncludeTrayIconshows the icon from launch:TrayIconOptions.Enableddefaults tofalse, so the generatedConfigureTrayIconsetsoptions.Enabled = true(Barbatos:TrayIcon:Enabledin configuration still overrides it).IncludeRunOnStartupdeliberately leavesRunOnStartupOptions.Enabledoff - setting it would register the app with Windows startup on its first run, unasked - and adds a "Launch when Windows starts"CheckBoxbound toMainViewModel.RunOnStartupEnabled, which callsIRunOnStartupService.SetEnabled.IncludeKeepAwakeis registered but off, with no toggle.
Editing a template
Template-engine syntax used here:
- C#:
#if (IncludeXxx)/#else/#endif, expressions allowed (#if (IncludeValee && !IncludeAquariusRouter)). - XAML and
.csproj:<!--#if (IncludeXxx) -->/<!--#else -->/<!--#endif -->, each on its own line like the existing ones. - Whole files:
template.jsonsources[0].modifiersentries of the form{ "condition": "(!IncludeXxx)", "exclude": [ "File.cs", ... ] }.
Rules that bite:
- Replacement is plain text across every file.
sourceName(BarbatosWpfApp,AquariusComponent,ValeeForm) is replaced in file names and contents, so it also renames strings like"BarbatosWpfAppAssistant"- never write the token where you mean it literally.PackageVersionreplacesthe literal current version string: any other occurrence of that string in any template file (a comment, another package's version) is rewritten too. - The template engine owns every
#ifin a template.csfile. A real compiler directive such as#if DEBUGis evaluated by the engine, not left for the compiler; none exist today, so look up the engine's//-:cnd:noEmitescape before adding one. - Gate
usinglines with the code that needs them, and keep the MainViewModel constructor's comma ladder and MainWindow's split open/close layout consistent - see the seams section in barbatos-wpf-app.md. - Generated code is the user's code (the
templatesandcsharp-stylerules): no MIT header, no repo-only conventions, comments that explain decisions to an app author. The project template setsNullable/ImplicitUsingsitself because it has noDirectory.Build.props. - The project template references published packages -
PackageReferencewith an explicitVersion, never aProjectReferenceintosrc/- so a generated app restores anywhere. It is the one.csprojin the repo allowed to carryVersion=.
Testing a change (the loop)
A WPF app runs on Windows only, but it compiles anywhere the .NET SDK does: in a Linux session,
steps 1, 2 and 4 work as written with dotnet build -p:EnableWindowsTargeting=true (XAML
included). Only step 3's dotnet run and the click-through need Windows - say so when you
could not do them.
dotnet new uninstall <folder>thendotnet new install <folder>after every edit -dotnet newcaches installed templates, so an edit is not reliably picked up otherwise. On a machine other sessions share, add--debug:custom-hive <scratch folder>to everydotnet newcall (install and instantiate) so the install stays out of the user-wide cache.- Instantiate outside the repo clone. Inside it, MSBuild picks up the repo's
Directory.Build.propsandDirectory.Packages.props: central package management rejects the template'sVersion=attributes (NU1008) and the app inherits the library's packaging settings. dotnet build, thendotnet runand click through what the change touched.- For
barbatos-wpf-app, cover the combinations the conditionals branch on, at least: the defaults;--IncludeAquarius false --IncludeI18n false(the no-Setupconstructor path);--IncludeAquarius false --IncludeAquariusRouter true(AquariusEffectivevia Router);--IncludeValee truealone (inlineSignUpView); Router + Valee + Dialogs with i18n on (the/signuproute);--IncludeRunOnStartup truealone with--IncludeAquarius false --IncludeI18n falseand again with Router or Dialogs (the last rung ofMainViewModel's constructor ladder); every flag on;--dry-runfor file lists. - Against unreleased
src/changes: pack each package to a local folder under a fresh prerelease version (dotnet pack src/<Package> -c Release -o <feed> -p:Version=<next>-dev.<n>), add anuget.configbeside the generated project listing that folder (and mappingBarbatos.Wpf.*to it if the config usespackageSourceMapping, as the repo's does), and instantiate with--PackageVersion <next>-dev.<n>. Do not pass-p:SourceLinkEnabled=truelocally: it turns on strong-name signing withsrc/barbatos.snk, which only CI writes. - Never re-pack the same version number. NuGet resolves an already-extracted package from
the global cache (
~/.nuget/packages) by id + version and never re-checks the local feed. Bump-dev.<n>, or delete~/.nuget/packages/barbatos.wpf.*/<version>/before restoring.
Keeping templates in step with the API and the release
Templates are documentation that compiles, so they rot silently - nothing builds them.
- API changes: before changing or obsoleting a public member,
grep -rn "<Member>" templates/. Move the template to the replacement in the same change (a generated app must not start life with[Obsolete]warnings), and updatetemplates/README.mdif the scaffolded shape changed. WpfProgram.cswires Aquarius and the router through the library's two-call pattern:builder.Services.AddAquarius()+AddAquariusRouter(_ => AppRoutes.Build())beforeBuild(),app.Services.UseAquarius()+UseAquariusRouter()after, mirroringsamples/Barbatos.Wpf.AquariusRouter.Sample/WpfProgram.cs. Keep it that way - never go back to assigningSetup.ServiceProviderorRouter.Currentby hand - and when that wiring changes, update thetemplates/README.mdparagraph that describes the bridge.- Versions the project template pins -
grep -rn "<old version>" templates/ README.mdbefore committing:- the four
Barbatos.Wpf.*Version="..."attributes inBarbatosWpfApp.csproj, thePackageVersionsymbol'sreplacesanddefaultValue, and the default quoted intemplates/README.md- all equal to<Version>inDirectory.Build.props. They move in the release commit, which touches exactly these four files (Directory.Build.props,template.json,BarbatosWpfApp.csproj,templates/README.md); a generated app can restore that version only after the release workflow has pushed it to NuGet.org. Barbatos.i18n.Wpf/Barbatos.i18n.DependencyInjection: a separate literal (not covered byPackageVersion) that should match theBarbatos.i18n*entries inDirectory.Packages.props.- The
NuGet.Mcp.Serverversion inWpfProgram.cs'sIncludeMcpblock - an external tool run throughdotnet dnx. TargetFrameworkstays SDK-suffixed (net10.0-windows10.0.17763.0).
- the four
Adding a module flag to barbatos-wpf-app
template.json: a"type": "parameter","datatype": "bool"symbol namedIncludeXxxwithdefaultValue"false", adisplayNameand adescriptionsaying what it adds.ide.host.json:{ "id": "IncludeXxx", "isVisible": true }- without it Visual Studio shows no checkbox.- Gate every touch point:
#if (IncludeXxx)in C# (code and itsusing),<!--#if (IncludeXxx) -->in XAML and the.csproj(aPackageReferenceif it needs a new package), and asources[0].modifiersexcludeentry for each file only it needs. If it depends on Aquarius, add it to theAquariusEffectiveexpression. templates/README.md: the module list, the flag example if useful, and the Core feature count ("11 independently-optional features") when it is a Core flag.- Test the flag on and off, alone and combined with the flags whose code it shares a file with.
Adding a new template
- Folder
templates/<Package>/<item-templates|project-templates>/<kebab-name>/; delete that kind folder's.gitkeeperonce it holds a real template. .template.config/template.jsonmodelled on the closest existing one: same$schemaandauthor,classifications,identityBarbatos.Wpf.<Package>.Templates.<Name>(stable - changing it later installs as a different template),shortNameprefixed like the XAML namespace (aq-,aqv-, and by that conventionaqr-for the router),tagslanguage/type, a distinctivesourceNameused in the file names, andpreferNameDirectory.- An item template copies the
namespacesymbol: a uniquereplacestoken anddefaultValueChangeMe.Namespace. - Content follows the current API and the repo's own samples; comments explain decisions to the app author.
- Docs in the same change:
templates/README.md(layout tree, install and uninstall lists, a section for the template), the rootREADME.mdTemplates section, the package README's scaffolding note if it has one (Aquarius's Getting Started does), and the template list in CLAUDE.md's repository map. - Run the test loop above.
Symptom -> cause -> fix
| Symptom | Cause | Fix |
|---|---|---|
dotnet new aq-view reports no such template |
Not installed (installs are per folder, per machine) | dotnet new install <folder> |
Generated files are in ChangeMe.Namespace |
--namespace omitted |
Regenerate with --namespace, or fix both files by hand |
Generated View shows nothing, DataContext is null |
ViewModel moved to another namespace or renamed away from XView → XViewModel |
Keep them paired, or use aq:Setup.ViewModel="{x:Type ...}" |
| Edited a template, output unchanged | Stale install | Uninstall, then install the folder again |
| A new flag has no checkbox in Visual Studio | Missing ide.host.json symbolInfo entry |
Add { "id": ..., "isVisible": true } |
| A flag is off but its file or code is still generated | Code not wrapped in #if, or the file has no modifiers exclude |
Gate it; whole files need the exclude |
NU1008 when building a generated app |
Instantiated inside the repo; central package management applies | Instantiate outside the clone |
Restore fails to find Barbatos.Wpf.* |
--PackageVersion (or the pinned default) not on any configured feed |
Use a published version, or the local-feed loop |
The tag 'RouteTo' does not exist in XML namespace ... against a local pack |
Global packages cache still holds an older build of the same version | New -dev.<n> version, or delete the cached folder |
--IncludeTrayIcon true but no tray icon |
Barbatos:TrayIcon:Enabled is false in configuration (it overrides code), or options.Enabled = true was removed from ConfigureTrayIcon - the option defaults to false |
Remove the configuration value or restore the line; ITrayIconService.SetVisible(true) shows it at runtime |
| "Launch when Windows starts" is ticked but the app does not start at sign-in | The user switched it off in Task Manager's (or Settings') Startup apps list: Windows records that apart from the HKCU\...\Run value, which IRunOnStartupService.IsEnabled reads, so the box still shows ticked |
Switch it back on there; the box cannot see or change that switch |
Clicking a search result throws InvalidOperationException ("is searchable but its RouteRecord has no Name") |
A route added to AppRoutes.cs with Search entries but no Name |
Give every searchable route a Name |
Related skills
barbatos-aquarius-view- what goes inside anaq-viewoutput;Setupresolution.barbatos-valee-form- theaqv-formand SignUp forms.barbatos-aquarius-router-AppRoutes.cs,RouterView, search.barbatos-wpf-host- theConfigureXxxfeatures behind the Core flags and the splash screen.barbatos-wpf-essentials- the csproj identity block (AppId,Company, version properties).barbatos-mcp-ai- whatIncludeMcpsets up.barbatos-package-authoring- the release and version bump the template follows.barbatos-docs-sync- the README updates a template change carries.