Skip to content

feat: consumer DX : assembly scan, aliases, shelldocs add, inline-slot indent fix - #13

Merged
Shewart merged 7 commits into
mainfrom
feat/consumer-registration-dx
Jul 23, 2026
Merged

feat: consumer DX : assembly scan, aliases, shelldocs add, inline-slot indent fix#13
Shewart merged 7 commits into
mainfrom
feat/consumer-registration-dx

Conversation

@Shewart

@Shewart Shewart commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Bundles four consumer-DX wins into one branch. The pitch: get a ShellUI docs site from empty to authoring in one line of Program.cs and one CLI command.

1. Assembly-scan registration (ShellDocs.Components)

Kills the "hand-type RegisterComponent<T>() for every ShellUI component" tax:

builder.Services.AddShellDocs(o =>
{
    o.RegisterComponentsFromAssembly<ShellUI.Components.Button>();
    // every public ComponentBase subclass in the ShellUI assembly is now
    // referenceable from any razor:preview block or <ComponentPreview>.
});

New API on ShellDocsOptions:

  • RegisterComponentsFromAssembly<TMarker>(Func<Type, bool>? filter = null) — scans the marker's assembly for public, concrete, non-generic ComponentBase subclasses and registers each. Handles ReflectionTypeLoadException gracefully.

  • RegisterComponentsFromAssembly(Assembly, Func<Type, bool>? filter = null) — explicit form with a filter predicate:

    o.RegisterComponentsFromAssembly(typeof(ShellUI.Components.Button).Assembly,
        t => t.Namespace?.StartsWith("ShellUI.Components") == true);
    
  • RegisterComponent(Type) — runtime overload alongside the existing generic form; validates ComponentBase assignability with a clear ArgumentException.

  • [ShellDocsIgnore] attribute — opt-out marker for public components that shouldn't be reachable from markdown authoring.

Dogfooded on ourselves: AddShellDocs used to register each shipped primitive with an explicit thirteen-line block; now it's:

options.RegisterComponentsFromAssembly<Callout>(t => t.Namespace == "ShellDocs.Components.Content");

A new primitive dropped into Content/ auto-appears in the registry without editing ServiceCollectionExtensions.cs. MarkdownContent and PreviewFrame opt out via @attribute [ShellDocsIgnore].

2. Alias overloads (ShellDocs.Components)

For consumers who want a component registered under a different markdown-facing tag (e.g. <Btn> renders ShellUI.Button):

  • RegisterComponent<T>(string tagName) and RegisterComponent(Type, string tagName) — writes to a new ComponentAliases dictionary on ShellDocsOptions.
  • BuildTypeRegistry consults ComponentAliases before falling back to type.Name. Last-write-wins if the same type is registered under multiple aliases.
  • Blank tag names rejected with ArgumentException.

3. shelldocs add <template> <name> CLI (ShellDocs.Templates + ShellDocs.CLI)

Scaffolds a starter .md page into content/. Three templates:

shelldocs add component MyBigCard
# -> content/docs/components/my-big-card.md
#    frontmatter + intro + razor:preview block + <TypeTable> skeleton + Notes

shelldocs add guide getting-started
# -> content/docs/guides/getting-started.md
#    frontmatter + intro + <Steps> skeleton + Next section

shelldocs add page faq
# -> content/docs/faq.md
#    blank frontmatter + H1

Slugifies PascalCase inputs (MyBigCardmy-big-card.md) and TitleCases kebab inputs (getting-started → "Getting Started"). Refuses to overwrite unless --force. Fails clean if no content/ directory is found.

PageTemplates static class in ShellDocs.Templates holds the three template bodies — same access pattern as the existing StarterPageTemplate. Program.cs's placeholder new command stub was repurposed into the wired-up add.

4. Inline-slot indent fix (ShellDocs.Markdown)

SlotExtractor.ReplaceComponentTags no longer .Trim()s the raw child content of inline component tags. The Trim was stripping the first line's indent and defeating SlotRenderer.Dedent — Markdig then re-interpreted the remaining 4-space-indented lines as an indented code block. Symptom was the same "literal <pre> around placeholder divs" bug that had already been fixed for razor:preview fences; the inline-tag code path was still hitting it. One-line change + prose comment explaining why.

5. TypeTable polish

  • Required badge: text changed from requiredRequired; text-transform: uppercase and matching letter-spacing dropped; font-size bumped 0.68rem → 0.72rem to compensate for lowercase reading smaller
  • Column headers: same treatment — dropped text-transform: uppercase + letter-spacing; font-size 0.75rem → 0.78rem. Headers now render as "Prop / Type / Default / Description" instead of "PROP / TYPE / DEFAULT / DESCRIPTION"

Roadmap updates

  • Marked feat/content-primitives and feat/api-reference-primitives shipped with the actual delivered scope
  • Added this branch (feat/consumer-registration-dx) as a shipped Phase 2 entry with the multi-package touch list
  • Refined the Phase 2 milestone note — the primitive suite is complete; remaining Phase 2 work is feat/animation-polish and the 0.2.0-alpha NuGet cut

Test plan

  • dotnet build shelldocs.slnx — clean, 0 warnings, 0 errors
  • dotnet test shelldocs.slnx119 / 119 passing (+12 new: 8 AssemblyScanTests, 4 ComponentAliasTests, 8 AddCommandTests via reflection to match the existing InitCommandTests pattern; net new tests = 20 minus the 8 assembly-scan already counted in the last PR draft = 12)
  • CLI smoke: dotnet run --project src/ShellDocs.CLI -- add component MyBigCard --dir <sandbox> writes content/docs/components/my-big-card.md with correct slug, PascalCase title, and template body
  • Preview app: every primitive page (callout, card, steps, filetree, code-group, type-table, component-preview) renders with zero errors — proves neither the dogfooded assembly scan nor the SlotExtractor Trim fix regressed anything

New test coverage

AssemblyScanTests (8):

  • Generic marker picks up concrete public components
  • Skips abstract types
  • Skips generic type definitions
  • Skips non-ComponentBase types
  • Respects [ShellDocsIgnore]
  • Filter overload composes on top of system rules
  • Registered types flow into TypeRegistry end-to-end
  • RegisterComponent(Type) overload rejects non-ComponentBase

ComponentAliasTests (4):

  • Generic alias overload registers under tag name (and short name no longer resolves)
  • Type + tag alias overload registers under tag name
  • Blank tag name rejected
  • Last-alias-wins for repeated registration of the same type

AddCommandTests (8):

  • component writes to content/docs/components/ with slugged filename
  • guide writes to content/docs/guides/ with title-cased frontmatter title
  • page writes to content/docs/ with blank body (no razor:preview)
  • Unknown template returns non-zero
  • Missing content/ dir returns non-zero
  • Existing file without --force fails
  • Existing file with --force overwrites
  • Empty name returns non-zero

Shewart added 7 commits July 22, 2026 23:43
…, including improved component registration and authoring templates
…ion pages, introducing template options and file handling logic
…rame components, and update TypeTable styling for consistency
…nd assembly scanning for automatic registration of public components
…mponent alias registration to ensure functionality and correctness
@Shewart
Shewart merged commit 9100216 into main Jul 23, 2026
1 check passed
@Shewart
Shewart deleted the feat/consumer-registration-dx branch July 23, 2026 00:24
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.

1 participant