Skip to content
Merged
18 changes: 14 additions & 4 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,28 @@ jobs:
with:
dotnet-version: 10.0.x

# The example projects (examples/MAUI.Demo) need the MAUI workload and are covered
# by the separate build-examples job in ci.yml. This validate job stays lightweight —
# library projects + tests only, no workload install required.

- name: Check formatting
run: dotnet format src/ --verify-no-changes --verbosity diagnostic
run: dotnet format src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj --verify-no-changes --verbosity diagnostic
continue-on-error: true

- name: Restore
run: dotnet restore src/
run: |
dotnet restore src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj
dotnet restore tests/ShellUI.Native.Tests/ShellUI.Native.Tests.csproj

- name: Build
run: dotnet build src/ --no-restore --configuration Release --warnaserror
run: |
dotnet build src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj --no-restore --configuration Release --warnaserror
dotnet build tests/ShellUI.Native.Tests/ShellUI.Native.Tests.csproj --no-restore --configuration Release --warnaserror

- name: Check for package vulnerabilities
run: dotnet list src/ package --vulnerable --include-transitive
run: |
dotnet list src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj package --vulnerable --include-transitive
dotnet list tests/ShellUI.Native.Tests/ShellUI.Native.Tests.csproj package --vulnerable --include-transitive
continue-on-error: true

# Label PR based on files changed
Expand Down
21 changes: 13 additions & 8 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,22 @@ public class ButtonTemplate
Dependencies = new List<string> { "button-variants" }
};

public static string Content => @"namespace YourProjectNamespace.Components.UI;
// ... component code";
// Template System v2: content is keyed by target NativePlatform. Adding Avalonia
// support to a component means adding one dict entry here — no registry edit needed.
public static IReadOnlyDictionary<NativePlatform, string> Contents { get; } = new Dictionary<NativePlatform, string>
{
[NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI;
// ... MAUI component code",
// [NativePlatform.Avalonia] = @"..." // added in Phase 2
};
}
```

**Gap today (Template System v2 target):** `Content` is a single MAUI-flavored string, and
`ComponentRegistry.GetComponentContent` has no platform parameter — it can't yet return
different code for Avalonia vs. MAUI. Adding Avalonia support requires evolving this to
something like `ButtonTemplate.Content(NativePlatform)` or per-platform static properties
(`ButtonTemplate.MauiContent` / `ButtonTemplate.AvaloniaContent`) selected by
`config.TargetPlatform`. See [PLAN.md](./PLAN.md) and [DEVELOPMENT_PLAN.md](./DEVELOPMENT_PLAN.md).
**Registry lookup (Template System v2):** `ComponentRegistry.GetComponentContent(name, NativePlatform)`
returns the source for the target platform, or `null` if the component doesn't exist OR exists
but has no template for that platform. Callers use `SupportsPlatform(name, platform)` and
`GetSupportedPlatforms(name)` to distinguish those two failure modes. Landed on
`feat/template-system-v2` (2026-07-18).

### 2. Namespace Replacement

Expand Down
47 changes: 46 additions & 1 deletion docs/COMPONENTS_ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,51 @@

Prioritized list of components to create for ShellUI Native, aligned with [ShellUI Components](https://github.com/shellui/shell-ui) patterns. All components should follow **compositional patterns** using Dependencies (parent + sub-components) instead of monolithic ChildContent.

Last revised: **2026-08-29** (on `feat/template-system-v2`).

---

## Form Sizing Contract

Every single-line form control renders at **40px** high (matches shadcn `h-10` / default `ButtonSize.Default`). Multi-line controls use `MinimumHeightRequest`. Horizontal padding is 12px; vertical padding is 0 when `HeightRequest` is fixed (the fixed height owns the vertical rhythm), 8px when it isn't.

| Component | Height | Padding | Notes |
|-----------|--------|---------|-------|
| `Button` (Default) | 40 (from `ButtonStyle.Height`) | `(16, 10)` | Variant-driven: Sm=36, Lg=44, Icon=40×40 |
| `Input` | 40 | `(12, 0)` | Fixed 2026-08-29 — was unset + `(12, 8)`, rendered inconsistent across platforms |
| `Select` | 40 | `(12, 0)` | |
| `DatePicker` | 40 | `(12, 0)` | |
| `TimePicker` | 40 | `(12, 0)` | |
| `Textarea` | `MinimumHeightRequest=80` | `(12, 8)` | Multi-line — grows with content |
| `Checkbox` | 20×20 (box) | — | Icon-shaped, not a field |
| `RadioGroupItem` | 20×20 (dot) | — | Icon-shaped, not a field |

**Rule for new form controls:** if it visually sits in a form row next to `Input`, it MUST be 40px tall. If it's a compositional container that hosts its own field (e.g. `Combobox`, `InputOTP`), the inner field carries the 40. Don't rely on platform default heights — MAUI's `Entry` / `Picker` defaults vary wildly across Android / iOS / Windows.

**Menu / list rows** (`DropdownItem`, `SelectItem` when added, `ContextMenuOption`) use `MinimumHeightRequest = 40` plus symmetric `Padding = (12, 12)` — they need a real touch target (44px iOS / 48dp Android guidance), not just enough space to draw the text.

**Icon-shaped controls** (`Checkbox` box 20×20, `RadioGroupItem` dot 20×20, `Switch` track 44×24) keep their exact pixel dimensions — those numbers are the design, not a fill. Their outer row inherits its hit area from the surrounding `HorizontalStackLayout`, which currently follows the icon height. If a future accessibility pass needs 44px touch targets for these, expand the container's `MinimumHeightRequest`, not the icon size.

---

## Design Token Contract

Templates hardcode ARGB strings today (no shared token file until Phase 2 lands the Avalonia `ResourceDictionary`). While we're still copy-pasting them, they MUST agree — otherwise "primary" reads as two different blues across the demo.

| Token | Value | Used by |
|-------|-------|---------|
| `Primary` | `#2563EB` | Button (Default), Checkbox (fill+border when checked), RadioGroupItem (fill+border when checked, fixed 2026-08-29), Switch (track when on), Progress (Default fill), Input (focus border, fixed 2026-08-29) |
| `Destructive` | `#EF4444` | Button (Destructive), Badge (Destructive), Progress (Destructive), Input (error border), Alert title (Destructive) |
| `Border` | `#E5E7EB` | Input (idle), Card (Default/Bordered), Checkbox (unchecked ring), RadioGroupItem (unchecked ring), Separator, Alert (Default) |
| `Muted background` | `#F3F4F6` | Badge (Secondary bg), Alert (Default bg) |
| `Foreground` | `#1F2937` | Input text, Label (Default), Checkbox label, Switch label, Alert title (Default) |
| `Muted foreground` | `#6B7280` | Progress label, Label (Muted variant) |
| `Placeholder` | `#9CA3AF` | Input placeholder |
| `Success` | `#22C55E` | Badge/Alert/Progress Success |
| `Warning` | `#F59E0B` | Badge/Alert/Progress Warning |

**Rule:** never introduce a new hex for a role that already has a token above. If you need a token that isn't listed, add it here first, then use it — that keeps Phase 2's `ResourceDictionary` extraction mechanical (grep the hex, replace with `{DynamicResource ShellUIPrimary}` in Avalonia XAML).

---

## Architectural Pattern: Dependencies over ChildComponent
Expand Down Expand Up @@ -41,7 +86,7 @@ Prioritized list of components to create for ShellUI Native, aligned with [Shell
| shell | ✅ Done | — | Shell |
| button | ✅ Done | button-variants | Button |
| button-variants | ✅ Done | — | ButtonVariants |
| input | ✅ Done | — | Input |
| input | ✅ Done (height fix 2026-08-29) | — | Input |
| label | ✅ Done | — | Label |
| checkbox | ✅ Done | — | Checkbox |
| switch | ✅ Done | — | Switch |
Expand Down
Loading
Loading