Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b7f8f78
docs: add Uno Platform port checklist plans (docs/uno/)
michaelstonis Jun 6, 2026
0cdedd4
chore(uno): verify prerequisites & tooling (Phase 01)
michaelstonis Jun 6, 2026
e6f985b
feat(uno): scaffold KumikoUI.Uno library (Phase 02)
michaelstonis Jun 6, 2026
a7beaf9
feat(uno): implement DataGridView host control (Phase 03)
michaelstonis Jun 6, 2026
b6c8514
feat(uno): wire pointer, keyboard & focus input (Phase 04)
michaelstonis Jun 6, 2026
43ef18b
feat(uno): add reusable font-registration helper (Phase 05)
michaelstonis Jun 6, 2026
14cecd4
feat(uno): add SampleApp.Uno demonstrating the grid on all heads (Pha…
michaelstonis Jun 6, 2026
a3fefa3
test(uno): Core suite green + InputMapping unit tests (Phase 07)
michaelstonis Jun 6, 2026
3f8b3c3
ci(uno): build, test & publish KumikoUI.Uno (Phase 08)
michaelstonis Jun 6, 2026
d5884bb
chore(uno): retarget SampleApp.Uno to .NET 10
michaelstonis Jun 6, 2026
32d8571
fix(uno): only scroll on left-drag/touch/wheel, not on hover
michaelstonis Jun 7, 2026
e62f68c
fix(uno): enable cell editing on iOS/Android via hidden TextBox soft-…
michaelstonis Jun 7, 2026
1507da4
fix(uno): horizontal scroll wheel now scrolls horizontally, not verti…
michaelstonis Jun 7, 2026
d695223
fix(sample): Rider 'Desktop' launch profile no longer runs the WASM head
michaelstonis Jun 7, 2026
e78087e
feat(core): pin Position=Bottom table summary rows to the viewport (f…
michaelstonis Jun 7, 2026
53b2d04
fix(uno): make keyboard input work on WASM (and uniformly across heads)
michaelstonis Jun 7, 2026
4938125
fix(uno): keyboard input genuinely works now — proxy was never focusable
michaelstonis Jun 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,59 @@ jobs:
- name: Pack — Maui (dry run)
run: dotnet pack src/KumikoUI.Maui/KumikoUI.Maui.csproj
--no-restore -c Release -p:Version=0.0.0-ci -o C:\ci-pack

# ─── Build KumikoUI.Uno (multi-head Uno library — all heads cross-compile on Windows) ─
build-uno:
name: Build & Test (KumikoUI.Uno — all heads)
runs-on: windows-latest
needs: build-core

steps:
- uses: actions/checkout@v4

- name: Setup .NET 9 + 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
9.x
10.x

- name: Cache Uno workloads
uses: actions/cache@v4
id: workload-cache
with:
path: |
${{ env.USERPROFILE }}\.dotnet\toolResolverCache
${{ env.PROGRAMFILES }}\dotnet\packs
${{ env.PROGRAMFILES }}\dotnet\metadata\workloads
${{ env.PROGRAMFILES }}\dotnet\sdk-manifests
key: uno-workloads-win-${{ hashFiles('**/KumikoUI.Uno.csproj') }}
restore-keys: uno-workloads-win-

- name: Restore Uno workloads
if: steps.workload-cache.outputs.cache-hit != 'true'
run: dotnet workload restore src/KumikoUI.Uno/KumikoUI.Uno.csproj

- name: Restore
run: dotnet restore src/KumikoUI.Uno/KumikoUI.Uno.csproj

- name: Build — Uno
run: dotnet build src/KumikoUI.Uno/KumikoUI.Uno.csproj
--no-restore -c Release

# Validate packability (dry run — exercises the full multi-head pack)
- name: Pack — Uno (dry run)
run: dotnet pack src/KumikoUI.Uno/KumikoUI.Uno.csproj
--no-restore -c Release -p:Version=0.0.0-ci -o C:\ci-pack

# Uno input-mapping unit tests (net9.0-desktop head)
- name: Test — Uno
run: dotnet test tests/KumikoUI.Uno.Tests/KumikoUI.Uno.Tests.csproj
-c Release --logger "trx" --results-directory TestResults

- name: Upload Uno test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-uno
path: TestResults/
68 changes: 67 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,72 @@ jobs:
path: artifacts/*.snupkg
if-no-files-found: warn

# ─────────────────────────────────────────────────────────────────────────────
# 3b. Pack KumikoUI.Uno (multi-head Uno library — all heads pack on a Windows runner)
# ─────────────────────────────────────────────────────────────────────────────
pack-uno:
name: Pack KumikoUI.Uno (all heads)
runs-on: windows-latest
needs: resolve-version
env:
VERSION: ${{ needs.resolve-version.outputs.version }}

steps:
- uses: actions/checkout@v4

- name: Setup .NET 9 + 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
9.x
10.x

- name: Cache Uno workloads
uses: actions/cache@v4
id: workload-cache
with:
path: |
${{ env.USERPROFILE }}\.dotnet\toolResolverCache
${{ env.PROGRAMFILES }}\dotnet\packs
${{ env.PROGRAMFILES }}\dotnet\metadata\workloads
${{ env.PROGRAMFILES }}\dotnet\sdk-manifests
key: uno-workloads-win-${{ hashFiles('**/KumikoUI.Uno.csproj') }}
restore-keys: uno-workloads-win-

- name: Restore Uno workloads
if: steps.workload-cache.outputs.cache-hit != 'true'
run: dotnet workload restore src/KumikoUI.Uno/KumikoUI.Uno.csproj

- name: Restore
run: dotnet restore src/KumikoUI.Uno/KumikoUI.Uno.csproj

- name: Pack — KumikoUI.Uno
run: |
dotnet pack src/KumikoUI.Uno/KumikoUI.Uno.csproj `
--no-restore `
-c Release `
-p:Version=${{ env.VERSION }} `
--include-symbols `
-p:SymbolPackageFormat=snupkg `
-o ./artifacts

- name: List artifacts
run: Get-ChildItem artifacts -Recurse | Format-Table Name, Length

- name: Upload Uno package
uses: actions/upload-artifact@v4
with:
name: nuget-uno
path: artifacts/*.nupkg
if-no-files-found: error

- name: Upload Uno symbol package
uses: actions/upload-artifact@v4
with:
name: snupkg-uno
path: artifacts/*.snupkg
if-no-files-found: warn

# ─────────────────────────────────────────────────────────────────────────────
# 4. Run tests before publishing (gate publish on green tests)
# ─────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -195,7 +261,7 @@ jobs:
publish:
name: Publish to NuGet.org
runs-on: ubuntu-latest
needs: [ pack-core, pack-maui, test ]
needs: [ pack-core, pack-maui, pack-uno, test ]
environment: nuget-publish # optional: require manual approval in repo settings

steps:
Expand Down
45 changes: 45 additions & 0 deletions KumikoUI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KumikoUI.SkiaSharp", "src\K
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KumikoUI.Maui", "src\KumikoUI.Maui\KumikoUI.Maui.csproj", "{529B41A2-C3BD-4CE7-93C5-0804CF591AC8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KumikoUI.Uno", "src\KumikoUI.Uno\KumikoUI.Uno.csproj", "{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp.Maui", "samples\SampleApp.Maui\SampleApp.Maui.csproj", "{4F540791-EA67-4539-842A-B9C7BF925F9A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp.Uno", "samples\SampleApp.Uno\SampleApp.Uno.csproj", "{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KumikoUI.Core.Tests", "tests\KumikoUI.Core.Tests\KumikoUI.Core.Tests.csproj", "{5565DAEA-A170-462E-B2F5-0BDD6878319C}"
Expand All @@ -32,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{DFDD4E5D
.github\workflows\publish.yml = .github\workflows\publish.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KumikoUI.Uno.Tests", "tests\KumikoUI.Uno.Tests\KumikoUI.Uno.Tests.csproj", "{57855B3A-D369-4D2D-8B43-FA09549FF955}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -78,6 +84,18 @@ Global
{529B41A2-C3BD-4CE7-93C5-0804CF591AC8}.Release|x64.Build.0 = Release|Any CPU
{529B41A2-C3BD-4CE7-93C5-0804CF591AC8}.Release|x86.ActiveCfg = Release|Any CPU
{529B41A2-C3BD-4CE7-93C5-0804CF591AC8}.Release|x86.Build.0 = Release|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Debug|x64.ActiveCfg = Debug|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Debug|x64.Build.0 = Debug|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Debug|x86.ActiveCfg = Debug|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Debug|x86.Build.0 = Debug|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Release|Any CPU.Build.0 = Release|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Release|x64.ActiveCfg = Release|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Release|x64.Build.0 = Release|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Release|x86.ActiveCfg = Release|Any CPU
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467}.Release|x86.Build.0 = Release|Any CPU
{4F540791-EA67-4539-842A-B9C7BF925F9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4F540791-EA67-4539-842A-B9C7BF925F9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F540791-EA67-4539-842A-B9C7BF925F9A}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand All @@ -90,6 +108,18 @@ Global
{4F540791-EA67-4539-842A-B9C7BF925F9A}.Release|x64.Build.0 = Release|Any CPU
{4F540791-EA67-4539-842A-B9C7BF925F9A}.Release|x86.ActiveCfg = Release|Any CPU
{4F540791-EA67-4539-842A-B9C7BF925F9A}.Release|x86.Build.0 = Release|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Debug|x64.ActiveCfg = Debug|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Debug|x64.Build.0 = Debug|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Debug|x86.ActiveCfg = Debug|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Debug|x86.Build.0 = Debug|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Release|Any CPU.Build.0 = Release|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Release|x64.ActiveCfg = Release|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Release|x64.Build.0 = Release|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Release|x86.ActiveCfg = Release|Any CPU
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913}.Release|x86.Build.0 = Release|Any CPU
{5565DAEA-A170-462E-B2F5-0BDD6878319C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5565DAEA-A170-462E-B2F5-0BDD6878319C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5565DAEA-A170-462E-B2F5-0BDD6878319C}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand All @@ -102,6 +132,18 @@ Global
{5565DAEA-A170-462E-B2F5-0BDD6878319C}.Release|x64.Build.0 = Release|Any CPU
{5565DAEA-A170-462E-B2F5-0BDD6878319C}.Release|x86.ActiveCfg = Release|Any CPU
{5565DAEA-A170-462E-B2F5-0BDD6878319C}.Release|x86.Build.0 = Release|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Debug|x64.ActiveCfg = Debug|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Debug|x64.Build.0 = Debug|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Debug|x86.ActiveCfg = Debug|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Debug|x86.Build.0 = Debug|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Release|Any CPU.Build.0 = Release|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Release|x64.ActiveCfg = Release|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Release|x64.Build.0 = Release|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Release|x86.ActiveCfg = Release|Any CPU
{57855B3A-D369-4D2D-8B43-FA09549FF955}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -110,8 +152,11 @@ Global
{6535D98A-3CBA-49C0-856A-18D3324DDB8E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{79969B24-4293-4D4F-82E1-27E61EFA152C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{529B41A2-C3BD-4CE7-93C5-0804CF591AC8} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{9C72BF6C-75B7-4F92-9B3A-B45AB43EC467} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{4F540791-EA67-4539-842A-B9C7BF925F9A} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
{70A42A2C-0C67-4D56-9D61-AF5A6A11B913} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
{5565DAEA-A170-462E-B2F5-0BDD6878319C} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{DFDD4E5D-1418-41C2-A6CE-2A6397026499} = {17B595E7-34C1-4B92-9FD3-316E5A57E051}
{57855B3A-D369-4D2D-8B43-FA09549FF955} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
EndGlobal
110 changes: 110 additions & 0 deletions docs/uno/01-prerequisites.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 01 — Prerequisites & Tooling

Goal: a machine (and CI runner) that can scaffold and build every Uno head. Do this once per
environment before touching project files.

> The Uno port targets `net9.0-*` heads (see [ADR-4](README.md#2-architecture-decision-record)). The
> `.NET 10` SDK can build `net9.0` target frameworks, so a single recent SDK covers the whole `src/`
> stack.

> **Environment status — verified 2026-06-06 (this macOS dev machine):**
> - ✅ .NET SDK `10.0.x` present (builds `net9.0` TFMs) · ✅ Uno templates (`unoapp`, `unolib`, `unoapp-uitest`) · ✅ `uno-check` 1.33.1
> - ✅ Workloads `ios`, `macos` installed · ❌ `android`, `wasm-tools`, `maccatalyst` **not installable here** — `dotnet workload install` returns *"Inadequate permissions. Run the command with elevated privileges."* (the SDK lives in `/usr/local/share/dotnet`; no non-interactive `sudo` is available in this session).
> - ➡️ **Local build/verify target: `net9.0-desktop`** (Skia desktop — no mobile workload required). The Android / WebAssembly / Apple / Windows heads are built & verified in **CI** ([08](08-ci-cd-packaging.md)), where runners install workloads with the needed privileges. To build them locally, run `sudo dotnet workload install android wasm-tools maccatalyst` yourself, then re-run the per-head builds.

---

## 1. .NET SDK

- [x] Install the **.NET 10 SDK** (or .NET 9 SDK). Verify: — *`10.0.100`–`10.0.300` present*
```bash
dotnet --list-sdks
```
- [x] Decide the library TFM. Default plan: `net9.0` heads (matches `KumikoUI.Core` / `KumikoUI.SkiaSharp`).

## 2. Uno Platform templates

- [x] Install (or update) the Uno templates: — *already installed*
```bash
dotnet new install Uno.Templates
```
- [x] Confirm the templates are available: — *`unoapp`, `unolib`, `unoapp-uitest` present*
```bash
dotnet new list | grep -i uno
# Expect: unoapp, unolib, unomauilib, unoapp-uitest
```
- [x] (Optional) Inspect template options so the scaffold commands in [02](02-library-scaffold.md) and
[06](06-sample-app.md) match your installed version: — *captured (`unolib`/`unoapp` `-tfm`, `-platforms`, `-renderer` …)*
```bash
dotnet new unolib -h
dotnet new unoapp -h
```

## 3. `uno-check` — environment doctor

`uno-check` validates and installs the workloads/SDK bits Uno needs. It is the canonical way to get
a machine build-ready.

- [x] Install the tool: — *`uno-check` 1.33.1 present at `~/.dotnet/tools/uno-check`*
```bash
dotnet tool install --global uno.check
```
- [ ] Run it and apply fixes: — ⚠️ *applying workload fixes needs elevated privileges here; run with `sudo` to install the missing workloads*
```bash
uno-check
```

## 4. Workloads

Uno heads require these workloads. `uno-check` installs them, or do it manually:

- [ ] `wasm-tools` — WebAssembly (`net9.0-browserwasm`) — ❌ *not installed here (needs elevation); verified in CI*
- [ ] `android` — Android (`net9.0-android`) — ❌ *not installed here (needs elevation); verified in CI*
- [x] `ios` — Apple head — *installed* · [ ] `maccatalyst` — ❌ *not installed here (needs elevation)*
- [x] Verify: — *ran `dotnet workload list` → `ios`, `macos` present*
```bash
dotnet workload list
```

```bash
# manual install (skip if uno-check handled it) — requires elevation on this machine:
sudo dotnet workload install wasm-tools android ios maccatalyst
```

## 5. Per-OS build constraints

A single cross-targeted project can be *opened* anywhere, but some heads only **build** on a specific
OS. Plan CI runners accordingly (see [08](08-ci-cd-packaging.md)).

| Head | macOS | Windows | Linux |
|---|:---:|:---:|:---:|
| `net9.0-desktop` | ✅ | ✅ | ✅ |
| `net9.0-browserwasm` | ✅ | ✅ | ✅ |
| `net9.0-android` | ✅ | ✅ | ✅ |
| `net9.0-ios` | ✅ | ❌ | ❌ |
| `net9.0-maccatalyst` | ✅ | ❌ | ❌ |
| `net9.0-windows10.0.xxxxx` | ❌ | ✅ | ❌ |

> When building a single head locally, pass `-f`/`--framework` (e.g. `dotnet build -f net9.0-desktop`)
> to avoid building unavailable heads on the current OS.

> **Library vs. app:** the table above is for building/running the **app** (`SampleApp.Uno`). A **class
> library** like `KumikoUI.Uno` cross-compiles more permissively — the `-ios`/`-maccatalyst` heads
> compile on Windows/Linux with the workloads installed (no Mac needed for managed library code); only
> the WinAppSDK `-windows10.0.x` head strictly needs Windows. This is why the whole library can be
> packed on one Windows runner — see [08 §1](08-ci-cd-packaging.md#1-packaging-fact-that-simplifies-everything).

## 6. IDE (optional, for running heads)

- [ ] Visual Studio 2022/2026 (Windows head) or JetBrains Rider, with the **Uno Platform** extension
for designer/hot-reload support. The CLI is sufficient for build/pack; an IDE is convenient for
debugging the Windows and mobile heads.

---

## ✅ Exit criteria

- [x] `dotnet --list-sdks` shows .NET 9 or 10. — *.NET 10 SDKs present*
- [x] `dotnet new list` shows `unolib` and `unoapp`. — *present*
- [x] `uno-check` tool installed (1.33.1). — *workload auto-fix requires elevation here; deferred to CI / manual `sudo`*
- [~] `dotnet workload list` shows the workloads for your target heads. — *`ios`/`macos` ✅; `android`/`wasm-tools`/`maccatalyst` ❌ (elevation-blocked → built in CI). **Local verify target: `net9.0-desktop`.***
Loading
Loading