drawnui/drawnui.net/drawnui-net-harness
Claude Code and agent skill by drawnui/drawnui.net
- community
Checking that the SKILL.md file is still in the repository.
Use for headless, pure-.NET (no device/GPU) testing and runtime debugging of DrawnUi controls — render frames, simulate gestures, settle background measurement, detect blank/empty renders, reproduce SkiaScroll/SkiaLayout virtualization + windowed-list bugs. Trigger on "test in .net harness", "headless", "repro without device", DrawnUi.Testing, HeadlessCanvasHost, GestureRobot, VirtualizationProbe.
Install
npx vskill@latest install drawnui/drawnui.net/drawnui-net-harnessSKILL.md
DrawnUi .NET Headless Harness
Pure managed DrawnUi runs WITHOUT a window, device, or GPU. Render to an offscreen SkiaSharp surface on a deterministic synthetic clock, drive real gestures, inspect structure + pixels. Ideal for unit tests AND for reproducing/fixing layout/scroll/virtualization bugs the way the device does — fast, deterministic, scriptable. Prefer this over OpenTk for measurement/scroll/windowing bugs.
Where it lives (repo DrawnUi)
- Infra:
src/Net/DrawnUi/Testing/—HeadlessCanvasHost.cs,GestureRobot.cs,VirtualizationProbe.cs,VirtualizationScene.cs. - Runnable harness sample:
src/Net/Samples/VirtualizationHarnessDemo/—Program.cs(top-level entry),ChatLikeScene.cs(inverted+windowed chat repro),BlankJumpRepro.cs. - Also on this host:
src/Net/Samples/GestureHarnessDemo/and the xUnit projectsrc/Net/Tests/DrawnUi.Net.Tests/(assert-style regression tests).src/Net/Samples/SkiaEditorHarness/carries its own private copy of the host. - Namespaces:
DrawnUi.Testing,DrawnUi.Draw. Project targetsnet9.0, refsDrawnUi.Netby project → editing enginesrc/Shared/**recompiles into the harness directly.
Core API
- Clock is synthetic + monotonic: gestures (flushed in
ExecuteBeforeDraw) and fling/animation (ticked offFrameTimeNanos) advance only when you callRenderFrame. Nothing happens "between" frames. Super.Init()runs once inside the host ctor.
Gestures — really wired
Virtualization probe (any templated SkiaScroll+SkiaLayout)
Building a faithful scene (CRITICAL)
A harness only proves something if it mimics the target's DEFINING conditions. For ChatPage-class bugs that means: inverted (SkiaScroll.Rotation=180, ReverseGestures=true, TrackIndexPosition=Start), windowed ItemsSource (ObservableRangeCollection, slice newest-first Items[i]==All[End-1-i], cap resident count), bidirectional LoadMore (LoadMoreCommand=older/append-tail, LoadMoreTopCommand=newer/head-insert), variable heights, MeasureVisible strategy, jump buttons (WindowedSource.ScrollToIndex(global, align, animate): ReplaceRange rebase + scroll.ScrollToIndex(local, animate, align, clamp:true); every ScrollToIndex is an ordered scroll, held until it arrives). ChatLikeScene.cs is the reference; copy it.
Windowing is now a LIBRARY primitive — no subclass needed: DrawnUi.Draw.WindowedSource<T> (sliding window over a big backing list) + SkiaScrollWindowHost (built-in IWindowHost adapter) over a plain SkiaLayout. Base SkiaLayout provides SuppressLoadMore, ordered-scroll LoadMore gating (inside ShouldTriggerLoadMore), and the MeasurementApplied event (trim-after-measure hook). ChatLikeScene drives these directly. Do NOT validate on a simplified static scene (uniform heights, non-inverted, non-windowed) — it proves nothing.
Detecting a blank/empty render headlessly
NonBackgroundFraction(bg) ~ 0 on a scene that should be full = blank. Pair with structure dump from a SkiaLayout subclass (StackStructure is protected): print each cell ControlIndex / Destination.Top / Bottom / Measured.Height. A non-monotonic Top sequence or h0 cells = corrupt structure (overlap). Content height is computed O(1) as last.Destination.Bottom - first.Destination.Top (UpdateProgressiveContentSize), so a wrong LAST-cell position collapses content → offset exceeds it → blank.
Run it
For engine Debug.WriteLine on stdout, add Trace.Listeners.Add(new ConsoleTraceListener()) in the entry (remove after). Gate temp engine probes behind a public static bool DebugX flag and REMOVE when done — keep instrumentation out of committed engine code.
Gotchas learned
- Pixel-asserting an
ImageDoubleBufferedcontrol: the offscreen bake runs on a REAL worker thread while the harness clock is synthetic — NAdvanceFramesalone can snapshot before the bake lands → canvas all-background, flaky. Settle first:for(...40){ if (host.NonBackgroundFraction(bg) > 0.001) break; Thread.Sleep(20); host.AdvanceFrames(3); }. Property/structure asserts don't need this. (Verified 2026-07, SkiaSlider style probes.) - Background-measured cells must set BOTH
cell.Areaandcell.Destination.ComputeBottomOfRow(which positions the NEXT batch) readscell.Area.Top— leaving Area default (Top=0) makes every cell report bottom=height, so the next batch stacks at the top → overlap → collapsed content → blank. (Root cause of the 2026 item-measurement-memo blank-jump bug.) _measuredItemsis keyed by LOCAL index → a full-collectionReplaceRange(window jump) clears it (ResetMeasurementForReplace). An item-keyed measurement memo (MeasurementCacheCapacity, default 1000) survives swaps; it must seedAreatoo.- MEMO/fast paths complete background measurement in fewer frames → can expose latent multi-batch ordering races that the slow path hid. Always A/B the harness with the optimization ON vs OFF (e.g.
MeasurementCacheCapacity: 0) to attribute a regression. - Transient mid-settle collapse (incomplete frontier) is benign and self-heals; a STICKY collapse is the bug. Settle fully before asserting.
VirtualizationProbe.SettleBackgroundreturns on frontier-STABLE, which a stalled-but-incomplete frontier also satisfies → false-blank snapshots / flaky runs. For jump assertions settle untilFrontier >= ItemsCount-1 && LastVisible >= 0(background measurement re-triggers each rendered frame). Killed the blank-jump flakiness.- Background measurement runs on real threadpool threads (
Task.Run) withThread.Sleeppacing in settle → naive single-run assertions are flaky. Run the scenario N times and assert 0 failures. - xUnit runs test classes in parallel in ONE process: a test that writes process-wide engine state (
Super.Screen.Density,Super.Multithreaded,Super.EnableRendering/IsRtl/FontSubPixelRendering, clipboard, font registration) goes in a[CollectionDefinition(..., DisableParallelization = true)]collection. SettingSuper.Screen.DensityresetsRenderingScaleon every live canvas, so canvases of other test classes running at the same time draw a frame at that density (0 gives infinite sizes). Sign of a missing collection: failures only in full parallel runs, a different test each time, every test passes alone. - A static list in a test class (binds, events) also collects from controls of earlier cases still measuring in the background: filter by the current control.
- Diagnose such failures with a per-frame trace inside the failing test (frame time, animator start / last frame time,
MeasuredSize) rather than by reading code. - Reach for this harness first for measurement/scroll/windowing work.
AdvanceFramesdraws EVERY frame, a real host draws only when a frame was asked for. A bug that waits for a frame nobody requests passes headless and fails in the app (2026-10-02: a ScrollToIndex into a rebased window stalled on WPF while every headless test passed). For anything that holds, retries or animates, also run it the way an app does:for (...; host.NeedsFrame; ...) host.RenderFrame(16);On WPF / OpenTK / Wasm,Update()raised while a frame is being drawn is dropped; ask withDrawnView.RequestNextFrame()instead.- Proving a fix in the real WPF app without the mouse: UI Automation. The canvas exposes its accessibility snapshot as UIA children (buttons
Invoke, labels, rects), so a PowerShell UIA script can press drawn buttons by name (-ceq: PowerShell-eqis case-insensitive, "HOME" matched the navbar "Home") and read which cells are on screen. Launch your OWN instance; a Release window's GPU surface comes out blank with PrintWindow, a Debug one captures.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingdocs/skills/drawnui-net-harness/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/drawnui/drawnui.net/drawnui-net-harness)