Skip to content

Publish a machine-readable index of the Toolkit samples - #879

Merged
niels9001 merged 7 commits into
CommunityToolkit:mainfrom
Jaylyn-Barbee:sample-index-exporter
Oct 7, 2026
Merged

niels9001 merged 7 commits into
CommunityToolkit:mainfrom
Jaylyn-Barbee:sample-index-exporter

Conversation

@Jaylyn-Barbee

Copy link
Copy Markdown
Contributor

Fixes

No issue number: this repository's issue templates accept bug reports only (blank_issues_enabled: false), and this is a proposal rather than a defect. I opened it as an Idea instead, and would treat that discussion as the place to settle direction before any of this merges:

Discussion: #878

Adds a generated, machine-readable index of every sample in the repository at catalog/toolkit-samples.json, the .NET tool that produces it, and a CI job that fails when the committed file no longer matches the samples.

PR Type

What kind of change does this PR introduce?

  • Build or CI related changes
  • Documentation content changes
  • Other... Please describe: new repository tooling under tools/, plus a generated artifact under catalog/. No component source is touched.

What is the current behavior?

The samples are only addressable by resolving the repository's own conventions: [!SAMPLE] markers in components/*/samples/*.md pointing at [ToolkitSample…]-attributed classes, matched to their .xaml/.xaml.cs pairs. That works for the sample app and leaves every other consumer — documentation sites, search, AI coding assistants — scraping the repository and reimplementing a partial copy of that resolution, which then drifts as the samples change.

Assistants asked for Toolkit markup today commonly emit snippets that do not compile: invented attributes, missing xmlns declarations, or the sample app's x:Class and option-binding scaffolding pasted into a page the user is actually writing.

What is the new behavior?

catalog/toolkit-samples.json is generated from the samples and committed. One entry per documentation page, one sample per [!SAMPLE] marker, in the order the page presents them — currently 72 entries and 125 samples.

Each snippet is meant to be pasted. The <Page> wrapper and x:Class are removed, design-time namespaces dropped, <Page.Resources> relocated onto the surviving element so referenced keys stay in scope, conditional win: prefixes dropped, and the sample app's option bindings replaced with the literal value the gallery opens with. The xmlns declarations the snippet actually uses are published alongside it. Code-behind is reduced to the handlers the markup calls and the types it binds to, with the license header, namespace, page class and InitializeComponent removed.

What changes is the environment, never what the sample demonstrates. An option whose value cannot be written as a literal has its attribute removed rather than guessed at, leaving the control at its own default, and the omission is reported. A sample that cannot be made pasteable is withheld and named rather than published broken.

Pages documenting APIs with no markup — most of Extensions and Helpers — appear with an empty samples array, so the index carries the whole component surface instead of only the parts that have XAML. Everything specific to this repository sits under a toolkit object, leaving the rest of each entry portable across sample sources.

Regenerating is one command, and CI runs the same tool in check mode:

dotnet run --project tools/SampleIndexExporter -- generate

The check job runs on ubuntu-latest and needs no workloads, because the exporter reads the samples as text rather than building them. It reports a stale index in under a minute instead of after the build matrix. catalog/README.md documents the format, the guarantees and the regeneration step; .gitattributes pins the artifact to LF so the file is byte-identical whether it was generated on Windows or Linux.

PR Checklist

Please check if your PR fulfills the following requirements:

  • Created a feature/dev branch in your fork (vs. submitting directly from a commit on main)
  • Based off latest main branch of toolkit
  • Tested code with current supported SDKs
  • Tests for the changes have been added (if applicable)
  • Header has been added to all new source files
  • Contains NO breaking changes

Based on 413892f ("Rename CheckModifierKeys to ModifiersEnabled"), the current main tip at the time of writing. All 19 new .cs files carry the standard .NET Foundation header. No existing file's behaviour changes: the only edits outside new directories are an added job in .github/workflows/build.yml and four added lines in .gitattributes.

Validation

Both commands were run on Windows against this branch, with these results:

> dotnet run --project tools/SampleIndexExporter -- check
catalog/toolkit-samples.json is up to date (72 entries, 125 samples).
# exit code 0

> dotnet test tools/SampleIndexExporter.Tests
Passed!  - Failed: 0, Passed: 86, Skipped: 0, Total: 86

These are the same two commands the new CI job runs.

I did not run the full build matrix locally, as it needs Windows workloads this machine does not have. The change touches no component, sample or app source, so the matrix exercises nothing it affects — but it is CI, not me, that will confirm that.

Other information

The tests are written as the guarantees a consumer depends on rather than as coverage of the implementation: the committed file matches the samples, generation is deterministic, every published snippet parses and declares the prefixes it uses, and anything excluded is listed by name. A fixture deliberately mixes casing so that ordering is checked ordinally rather than by the host filesystem's collation, since the index is generated on Windows and byte-compared on Linux.

Three things worth a reviewer's attention specifically:

  1. The schema is versioned but unreviewed. schemaVersion: 1 should not be committed to until the field names suit whatever consumers you have in mind. I would rather change them now than add a version 2 later.
  2. Dropping unsettleable options is a judgement call. The alternative is guessing a literal, which risks publishing a snippet that misrepresents the sample. Dropping leaves the control at its own default and reports the omission; I think that is the safer default, but it is worth confirming.
  3. A duplicate document file name is a hard error, not something the tool silently disambiguates, because an entry's id is the one thing consumers may reasonably store. Silently qualifying it would move someone's saved reference. There are no duplicates today.

The last two commits are review follow-ups rather than new functionality: the first fixes three defects found reviewing the parser — cast-prefixed x:Bind paths losing their attribute, commented-out markup being read as live, and the id instability described above — and the second rewrites five tests that could not have failed, each verified by mutating the code it covers and confirming the test now fails.

Happy to split this into separate PRs for the tool, the artifact and the CI job if that reviews more easily, or to drop it if the direction in the discussion turns out to be wrong.

Jaylyn-Barbee and others added 5 commits September 28, 2026 14:05
Consumers that want to offer Toolkit samples outside this repository -
documentation sites, search, AI coding assistants - currently have to
scrape components/, parse the sample attributes themselves, and keep a
hand-maintained table of the cases their parser gets wrong. Every one of
them reimplements the same reading of the same files, and each table
drifts silently the moment a sample changes.

This adds a generated index, catalog/toolkit-samples.json, describing
every documentation page and every sample it presents, with markup that
can be pasted into a reader's own app.

The exporter reads the samples the way the sample generator does. It
parses the [ToolkitSample] attributes with Roslyn rather than a regex,
because display names are written as nameof(...) and interpolated
strings; and it reuses the generator's own frontmatter and [!SAMPLE]
marker patterns, so the index and the sample app cannot disagree about
what a page contains.

Making the markup pasteable means removing the sample app from it: the
Page wrapper and its x:Class, the design-time namespaces, the bindings
to the generated options pane - replaced by the value the app starts
with, so the snippet shows what the gallery shows. <Page.Resources>
moves onto the element that survives, since otherwise the snippet fails
to compile the moment it is pasted anywhere that is not a Page. What
changes is the environment, never what the sample demonstrates.

Four gates keep it honest, and CI runs them on Linux in under a minute
because nothing here needs a build: the committed file matches the
samples, generation is deterministic, every published snippet parses and
declares the prefixes it uses, and anything left out is listed by name
rather than disappearing quietly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A sample that wires up handlers teaches nothing from its markup alone -
the reader sees Click="Move_Click" and has to go and find what
Move_Click does. The index already carries the markup, so it should
carry the code that makes it work.

What is published is the sample, not the page that hosts it. The
license header, the sample app's namespace, the page class, the
[ToolkitSample...] attributes and the InitializeComponent call are all
scaffolding for an app the reader is not building. The parser is told
this is WinAppSDK, so the UWP and Uno branches never enter the tree and
the reader is not handed a choice that has already been made for them.

Types declared beside a sample come with it. SwitchPresenterValueSample
switches on an enum in the same file, and publishing the markup without
it would publish a binding to a type the reader does not have.

Most samples have nothing left once the scaffolding is gone, and those
publish no code at all rather than a constructor that says nothing -
37 of 125 carry code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A consumer searching for what a control does, rather than for its name,
has nothing to match on: the index stated each component's category only
inside the toolkit extension object, which a generic reader ignores.

Category and subcategory now also appear in the entry's generated
keywords, kept separate from the author's own keywords line so a consumer
can keep weighting a human's word choice higher than the repository's
filing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve option bindings whose path carries a cast. x:Bind spells a cast as a
parenthesised type ahead of the path, as in (x:Int32)Columns. Reading the cast as
part of the path made the option lookup miss while the whole-word scan still
matched, so a binding with a perfectly readable default was treated as having
none and its attribute was removed. UniformGrid lost its spans and first column,
which is what that sample exists to demonstrate. An attached property path such
as (Grid.Row) is a path rather than a cast and is left alone.

Stop reading commented-out markup as live. A comment ends at --> and a CDATA
section at ]]>, not at the first >. Three scans stopped at the first one and
resumed inside the comment, so an x:Name in a comment counted as a declared
element, an option binding in a comment was rewritten in the published text, and
a prefix used only in a comment was published as a required import. An
apostrophe in comment prose also opened a quote that never closed, abandoning
prefix stripping for the rest of the fragment. Three samples carry comments of
the shape that triggers this today, and are unaffected only because none of them
happens to contain a name or a binding.

Report a duplicate entry id instead of renaming one. The bare slug went to
whichever document was read first and the loser was qualified with its component
name, so adding a component that sorted earlier would silently change the id of
an entry that had already shipped. An id is published as stable, so a collision
is now an error a contributor settles by renaming, and every existing id stays
where it is.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Five tests asserted things that could not come out any other way, so the gate
they belonged to was reporting a pass it had not earned.

Three of them asked the in-memory object a question about the published file:
the schema version and source restated their own field initializers, the
language tag restated the line in IndexGenerator that sets it, and the keyword
check restated the TrimEntries already applied by SplitKeywords. They now read
the committed JSON, which is what a consumer fetches and the only place the
serialized field names can be observed. The keyword gate also covers the
generated keywords, which come from the category frontmatter by a different path
and were not checked at all, and the trimming it relies on is now pinned by unit
tests over messy frontmatter.

The fourth asserted that generating twice in one process produced the same
bytes, which cannot see the divergence it was written to prevent: the index is
generated on Windows and byte-compared by a Linux CI job, and NTFS compares
names case-insensitively where ext4 does not. A fixture whose ordinal and
case-insensitive orderings disagree catches both halves of that — dropping the
sort, and swapping the comparer — on either filesystem. The existing
EntriesAreOrderedByDocumentPath cannot, because every component in this
repository happens to start with a capital letter.

Program.cs had no coverage at all, while CI depends on its exit codes: 2 for a
mistyped command, 1 for a stale or missing index, 0 otherwise. A mistyped
command reported as drift would send someone looking for a change that was never
made. The CRLF normalization is covered too, since without it every Windows
working copy reads as stale.

Main becomes internal so the tests can call it, and the fixture repository they
run against lives in Fixture.cs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Jaylyn-Barbee

Jaylyn-Barbee commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor Author

CI note: build (3, wasdk) is red on run 36871832063. Every other job passes, including the new Sample-Index check.

That job isn't failing on an assertion. 348 tests pass, then the test host dies:

The active test run was aborted. Reason: Unable to communicate with test host process.

This matches #347 ("WinUI 3 tests failing at random in CI"), open since Feb 2024. The named test drifts between runs because it's simply whichever test was executing when the host went down — the runner prints its own disclaimer that the test "may, or may not be the source of the crash." Mine landed on ColorPickerTests.SimpleAsyncUIExamplePageTest; #347 reports StackedNotificationsBehaviorTestClass.

This PR can't be the cause. The diff touches only .gitattributes, .github/workflows/build.yml, catalog/, and tools/ — no component or test source. The .gitattributes file is new and carries exactly one rule, catalog/*.json text eol=lf, so no other file's checkout behavior changes.

I closed/reopened to retrigger the whole workflow, since #347 notes a rerun often clears it.

@Jaylyn-Barbee

Copy link
Copy Markdown
Contributor Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@niels9001
niels9001 enabled auto-merge (squash) October 2, 2026 19:52
The index schema defines a control-level `usings` array and tells consumers
to prepend it as `using X;` lines so a sample compiles standalone, which is
why the samples themselves carry no directives. The exporter never populated
it, so NetworkHelper published code calling `NetworkHelper.Instance` while
the `using CommunityToolkit.WinUI.Helpers;` its source opens with was dropped
during export and recorded nowhere. Every C# sample the index published
failed to compile when pasted.

Rendering discarded the sample file's using directives, so capture them
first. A sample file imports what the whole page needed and most of a page
is scaffolding that is not published, so the directives are narrowed to the
ones the published members actually reference: `using static` and renaming
aliases have no namespace form, the sample app's own namespaces resolve
nowhere but this repository, and a toolkit namespace is kept only when it
supplies a name the published code mentions. ToolkitApi reads the component
sources for that last question, indexing extension method names as well as
types because `using CommunityToolkit.WinUI;` is what `.Debounce()` needs and
that never names its declaring class.

Namespaces the repository does not declare are kept as written. They cannot
be enumerated, and the asymmetry runs one way: a missing import is a compile
error the reader has to diagnose, a spare one is a hint they discard.

21 of 72 controls now carry the field; the rest publish no code and so need
nothing imported.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 47881f31-691e-4cbf-a66f-035dadf947a3
auto-merge was automatically disabled October 5, 2026 19:20

Head branch was pushed to by a user without write access

@Jaylyn-Barbee Jaylyn-Barbee reopened this Oct 6, 2026
@niels9001
niels9001 enabled auto-merge (squash) October 6, 2026 19:28
Two required contexts never reported on 080db55: testspace-analytics was
created and left queued, and WIP posted nothing at all. Both are app-side
delivery failures rather than anything the branch does, and protection on
main is enforced at "everyone", so neither waiting nor an admin override
can clear them. A new commit asks both apps for a fresh status.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 47881f31-691e-4cbf-a66f-035dadf947a3
@niels9001
niels9001 merged commit 34a57f9 into CommunityToolkit:main Oct 7, 2026
24 checks passed
Jaylyn-Barbee added a commit to microsoft/winappCli that referenced this pull request Oct 7, 2026
CommunityToolkit/Windows#879 merged, so catalog/toolkit-samples.json resolves
and the Toolkit corpus can be baked from the published index instead of the
reduced floor it held while that URL 404'd.

Bake deltas: gallery 327 -> 344, toolkit 48 -> 125, reactor 95 -> 235. The
gallery and toolkit figures match the live indexes exactly.

CacheVersion goes to "26". That bump is owed for the usings relocation in
cb06285 under Rules 1 and 3 — Scenario gained a usings field, and the same
upstream sample now yields C# without the glued-on prefix. Without it an
existing cache keeps matching on "25" and the offline fallback ignores the
TTL, so a filtered or offline machine would never stop pasting a snippet that
cannot compile wherever it is put. The bump and the re-bake have to land
together or Manifest_Ships_AndMatchesCurrentCacheVersion fails the build.

Re-measure the serving floors against the sanitized corpus, as the Toolkit
floors' own doc comment instructs, and reset both sets to roughly 90% of
observed:

  gallery  344 scenarios / 118 controls / 309 XAML / 133 C#  -> 309/106/278/119
  toolkit  125 scenarios /  53 controls / 125 XAML /  37 C#  -> 112/47/112/33

The gallery floors are updated too because the same bake moved that corpus,
leaving the measurements recorded in their comment stale and the floors
sitting further below actual than the intended margin. These tests read the
committed snapshot rather than the network, so tightening them cannot
introduce flakiness; the margin exists for the next bake.

Drop the Toolkit floors' "has not been re-baked since the Toolkit switched to
reading its published index" rationale, which described exactly the state this
commit ends.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 92222667-9d95-4dce-ba66-b6db29282316
nmetulev pushed a commit to microsoft/winappCli that referenced this pull request Oct 9, 2026
## Description

`find-ui` learned about Community Toolkit controls by scraping
`CommunityToolkit/Windows`: hundreds of requests to reconstruct which
sample belongs to which control, what it is called, and what its option
bindings default to. Getting that wrong was invisible, so the fetcher
carried hand-written tables correcting 34 samples and mapping 28
documents to controls.

The Toolkit now publishes a sample index, built by the same parser its
own sample browser uses and checked against the samples on every pull
request. This reads that index and deletes the scraper and both tables.
A cold Toolkit fetch goes from ~105 requests to 1, and the index cannot
disagree with the sample app about what a sample is named or what it
defaults to.

What stays on our side is what only a consumer can settle: naming the
sample after the user's own page, and dropping markup wired to handlers
we do not serve. Those removals still run against the index and are
expected to find nothing, so a change on the other side cannot reach a
user's clipboard unnoticed.

Reading the index roughly doubles what `find-ui` can answer, and most of
what it adds is not a control at all — helpers, converters, behaviors
and extensions that the scraper's skip list dropped. That turned out to
need search and output work of its own, which is the rest of this PR.

## Usage Example

Measured against the published index, through the real parse → normalize
→ sanitize path:

| | Before (scraped) | Now (index) |
|---|---|---|
| Scenarios served | 48 | 125 |
| Controls served | 26 | 53 |
| Scenarios carrying C# | 34 | 37 |
| …of those, carrying actual logic | 16 | 37 |

The last row is the one that matters. The scraper got C# by pulling each
sample page's `.cs` file verbatim, and for most Toolkit samples that
file is an empty shell — a constructor calling `InitializeComponent()`
and nothing else. 18 of its 34 C# scenarios were that. The index
publishes `code` only where there is sample logic to publish, so every
one of the 37 is substantive and useful C# roughly doubles.

Eleven controls stop serving C# as a result — `colorpicker`,
`gridsplitter`, `radialgauge`, `segmented`, `uniformgrid` and others.
Every one of those 16 scenarios was an empty shell; no sample logic is
lost. What those shells did carry was a `using
CommunityToolkit.WinUI.Controls;` line, and that information is not lost
either — it is now the `Namespace:` line described below, which is where
it belongs.

27 entries become reachable that `find-ui` could not surface, including
`converters`, `behaviors`, `animations`, `switchpresenter`,
`listviewextensions`, `themelistener` and `networkhelper`. Sample titles
become the ones the Toolkit authors wrote rather than ones we
manufactured — `toolkit-colorpicker-1` is now `ColorPicker`, not `Basic
usage`.

**Finding the non-controls.** Converters, behaviors and triggers arrive
grouped under an umbrella entry whose samples are named for the specific
type, so BM25 saw `filesizetofriendlystringconverter` as one opaque
token and only an exact-name query reached it. Headers and queries are
now indexed as both the original text and a CamelCase split, so all
three of these work:

```bash
winapp find-ui "FileSizeToFriendlyStringConverter" --source toolkit   # the specific type
winapp find-ui "converters" --source toolkit                          # the whole group
winapp find-ui "convert bool to visibility" --source toolkit          # described in words
```

The limit is documented rather than papered over: these are indexed by
the words in their type names, so a description finds one when it shares
those words and misses when it shares none — `check internet connection`
does not reach `NetworkHelper`.

**Namespaces are a field, not a code prefix.** The index publishes
control-level `usings`, and the obvious thing — prepending `using X;` to
each C# sample — produces code that compiles nowhere. The published C#
is a class-body fragment, so the result failed as its own file (CS0106)
*and* failed pasted inside a class (CS1529), which between them are
every placement a reader has. They now render as a line above the
snippet:

```
## NetworkHelper: Network Helper [CommunityToolkit]
**Namespace:** `CommunityToolkit.WinUI.Helpers`
```

Gallery controls publish both `usings` and `apiNamespace` and the two
disagree, so the line is their union. Namespaces a stock `dotnet new
winui` project already resolves are filtered out, leaving the long tail
an agent cannot guess.

## Related Issue

Closes #810. Depended on CommunityToolkit/Windows#879, which has merged
— the index is live and this is no longer blocked.

## Type of Change

- ✨ New feature
- ♻️ Refactoring

## Checklist

- [x] New tests added for new functionality (if applicable)
- [x] Tested locally on Windows
- [x] [docs/usage.md](../docs/usage.md) updated (if CLI commands
changed)
- [x] Shipped skills updated in `plugins/winapp/skills/` (if CLI
commands/workflows changed)

## Additional Notes

**One behavior change worth naming:** the control id `colorpickerbutton`
goes away. The Toolkit documents ColorPicker and ColorPickerButton in
one file, which is the key the index is organized by, so that sample is
served as `toolkit-colorpicker-2` instead. The sample is not lost, but
`--id toolkit-colorpickerbutton-1` stops resolving. This is the one
known granularity loss recorded in #810, and it is upstream's own
grouping rather than a defect in the swap.

`CacheVersion` moves to `26` and the embedded snapshot is re-baked in
the same commit — the two have to land together or
`Manifest_Ships_AndMatchesCurrentCacheVersion` fails the build. Without
the bump an existing cache keeps matching on `25`, and because the
offline fallback ignores the TTL, a filtered or offline machine would
never pick the new data up at all.

The re-bake moved every source, so the serving floors in
`EmbeddedSnapshotTests` were re-measured against the sanitized corpus
and reset to ~90% of observed. The Gallery floors are updated too, since
the same bake left their recorded measurements stale:

| | scenarios | controls | XAML | C# | floors |
|---|---|---|---|---|---|
| gallery | 344 | 118 | 309 | 133 | 309 / 106 / 278 / 119 |
| toolkit | 125 | 53 | 125 | 37 | 112 / 47 / 112 / 33 |

These tests read the committed snapshot rather than the network, so
tightening them cannot introduce flakiness; the margin exists for the
next bake.

**A known gap in `usings`, confirmed with upstream.** The exporter
derives the field from the `using` directives written in each sample's
source file, but the Toolkit's sample projects also inject a large set
of C# global usings that never appear in that syntax tree. Narrowing
them would make the index depend on a restored package graph and break
its determinism gate; publishing them unfiltered would put ~20 entries
on every control. So the field is deliberately the *non-obvious* imports
— the Toolkit namespaces a consumer cannot guess — not a complete import
set. `docs/winui-sample-index.schema.json` is worded to match, and the
ambient-namespace filter means our Namespace line already assumes the
baseline a WinUI page code-behind has.

### Validation

| Command | Result |
|---|---|
| `dotnet build src\winapp-CLI\WinApp.Cli.Tests\WinApp.Cli.Tests.csproj
-c Release` | Succeeded, 0 warnings, 0 errors. Release is the
configuration that enforces `TreatWarningsAsErrors`, so it is the one
that matches CI |
| `dotnet run --project
src\winapp-CLI\WinApp.Cli.Tests\WinApp.Cli.Tests.csproj -c Release
--no-build -- --filter
"FullyQualifiedName~FindUi\|FullyQualifiedName~EmbeddedSnapshot\|FullyQualifiedName~SearchGrouped\|FullyQualifiedName~CacheVersion"`
| Passed, 127/127 |
| `.\scripts\build-cli.ps1 -Bake -SkipTests -SkipNpm -SkipNuGet
-SkipMsix -SkipDocs` | Bake succeeded at cache version 26 |
| `.\scripts\validate-plugin-package.ps1` | Exit 0 |
| `winapp find-ui --id toolkit-networkhelper` (built binary, cold cache)
| Rendered `**Namespace:** CommunityToolkit.WinUI.Helpers`, no `using`
prefix on the C# |
| `winapp find-ui --list --json` (built binary, cold cache) | 475
entries — 344 gallery, 125 toolkit, 6 curated core; matches the bake
exactly |
| Search parity against a built `origin/main` binary, 12 bare
control-name queries, separate cold caches | 37 samples offered on both
branches. An earlier revision of this PR regressed this to 32 by
expanding the query before counting its tokens; the fix is covered by
`SearchGrouped_ExactControlName_ShowsThatControlAloneWithMoreSamples`,
which fails with `Expected:<5>. Actual:<3>` without it |
| Full CI on the pushed head | All required checks green, including both
`validate-tests` CLI shards, `build-and-package` and
`test-samples-result` |

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Zach Teutsch <88554871+zateutsch@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Copilot-Session: 7860854f-180a-4634-865c-c26249097c2c
Copilot-Session: 92222667-9d95-4dce-ba66-b6db29282316
Copilot-Session: 53bbb5c7-2d62-45ba-8714-ad476028bfb1
Copilot-Session: 415c898a-0d29-4429-b6f8-e6428d685e5c
Copilot-Session: 7c5d3197-8a79-4bee-9294-2be43df316f0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants