Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7f42ec7
Fix task status enum collision
contributor Nov 11, 2026
55a1a08
WIP issue5: named boxes and scroll areas record their own native id
contributor Nov 11, 2026
8271f50
issue5: layout view that prints what CUI proved and names the rest un…
contributor Nov 11, 2026
1788b3d
Correct virtual layout identity and report observed overflow
contributor Nov 11, 2026
5c05c72
Add concise option constructors for hot layout and text fields (#9)
contributor Nov 11, 2026
0e7ce14
Strengthen task status consumer and rendering tests
contributor Nov 11, 2026
98adf88
Support file-based apps and install dotnet-cui
contributor Nov 11, 2026
9b992ad
Add ChartNormalizer: baseline rules for Index100/Percent with explici…
Nov 11, 2026
020dc90
Add chart baseline normalization modes
contributor Nov 11, 2026
37942b1
Add readiness-aware waits for headless apps
contributor Nov 11, 2026
48aa568
Track layout rectangle provenance and floating layers
contributor Nov 11, 2026
51f8681
Generate a managed API catalog for offline lookup
contributor Nov 11, 2026
6a9a196
Document new CLI, readiness, chart and layout capabilities
contributor Nov 11, 2026
9801c32
Expose offline API and icon catalog commands
contributor Nov 11, 2026
0a6f762
Merge branch 'main' into agent/issues-integration
Nov 11, 2026
df1b748
Harden tool subprocesses, UTF-8 stdin, and template verification
contributor Nov 11, 2026
699c173
Add NamedPipe live window control and unify CuiSharp.Tools CLI
contributor Nov 11, 2026
3816998
Fix live window disconnect, keyboard cleanup, callback ownership, shu…
contributor Nov 11, 2026
fe8a654
Merge origin/main into pr/issues-integration
contributor Nov 11, 2026
41a0734
Regenerate the API catalog after origin/main added CuiSurface.CreateGpu
contributor Nov 11, 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ nested loops and while/do loops need explicit keys. The other rules still requir
```sh
dotnet add package CuiSharp # library + analyzers + cui.dll/cui.lib
dotnet add package CuiSharp.Testing # tests only
dotnet tool install -g CuiSharp.Tools # the `cui` command
dotnet tool install -g CuiSharp.Tools # exposes `dotnet cui`
```

```xml
Expand Down
8 changes: 4 additions & 4 deletions bindings/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -12074,7 +12074,7 @@
{
"CName": "status",
"CsName": "Status",
"CsType": "TaskStatus",
"CsType": "CuiTaskStatus",
"CType": "cui_task_status",
"Offset": 4,
"Type": {
Expand Down Expand Up @@ -12207,7 +12207,7 @@
{
"CName": "status",
"CsName": "Status",
"CsType": "TaskStatus",
"CsType": "CuiTaskStatus",
"CType": "cui_task_status",
"Offset": 16,
"Type": {
Expand Down Expand Up @@ -12274,7 +12274,7 @@
{
"CName": "status",
"CsName": "Status",
"CsType": "TaskStatus",
"CsType": "CuiTaskStatus",
"CType": "cui_task_status",
"Offset": 4,
"Type": {
Expand Down Expand Up @@ -25514,7 +25514,7 @@
},
{
"CName": "cui_task_status",
"CsName": "TaskStatus",
"CsName": "CuiTaskStatus",
"Values": [
{
"CName": "CUI_TASK_PENDING",
Expand Down
26 changes: 25 additions & 1 deletion bindings/overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"cui_size_kind": "SizeKind",
"cui_renderer_kind": "Renderer",
"cui_sw_format": "SwFormat",
"cui_token": "Token"
"cui_token": "Token",
"cui_task_status": "CuiTaskStatus"
},
"memberRenames": {
"CUI_RENDERER_OPENGL": "OpenGL"
Expand Down Expand Up @@ -252,6 +253,24 @@
"clicked": true,
"why": "The bool return reports clicked, not openness; cui_card_begin always opens its layout container."
},
"cui_scroll_begin": {
"end": "cui_scroll_end",
"clicked": false,
"deriveNamedId": true,
"why": "cui_scroll_begin returns void although it derives a named area's id as cui_make_id(id); the recorder recomputes that pure hash only while recording, so a named Scroll reports its own rect instead of the last widget's."
},
"cui_virtual_list_begin": {
"end": "cui_virtual_list_end",
"clicked": false,
"deriveNamedId": true,
"why": "The viewport is laid out under cui_make_id(id), but begin returns void and does not set last_id; the recorder derives that same id without changing native identity."
},
"cui_split_begin": {
"end": "cui_split_end",
"clicked": false,
"deriveNamedId": true,
"why": "The split container uses cui_make_id(id) without setting last_id; derive its own recorded id rather than borrow the preceding widget's rectangle."
},
"cui_kanban_column_begin_ex": {
"end": "cui_kanban_column_end",
"clicked": true,
Expand Down Expand Up @@ -1258,6 +1277,11 @@
"name": "MaxPoints",
"type": "int",
"why": "Upper bound on drawn points. 0 derives twice the viewport width; longer series are min/max decimated into the frame arena."
},
{
"name": "Normalize",
"type": "NormalizeMode",
"why": "Scale each series against its own first finite nonzero sample so series of different magnitudes compare by shape; see ChartNormalizer."
}
]
},
Expand Down
57 changes: 44 additions & 13 deletions docs/API-SURFACE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ enum CuiSharp.Accent
struct CuiSharp.AgentPlanOpts
field String Title
struct CuiSharp.AgentStatusOpts
field CuiTaskStatus Status
field Single Seconds
field String Icon
field TaskStatus Status
class CuiSharp.AiInterfacesUiExtensions
static Boolean Citation(Ui, Int32, in Source)
static Boolean ModelPicker(Ui, UiText, ReadOnlySpan<Model>, ref Int32, ModelPickerOpts)
Expand Down Expand Up @@ -116,6 +116,7 @@ struct CuiSharp.AvatarOpts
field String Status
field UInt64 Image
struct CuiSharp.BadgeOpts
ctor(Tone)
field Boolean Dot
field Boolean Solid
field ControlSize Size
Expand Down Expand Up @@ -154,6 +155,7 @@ class CuiSharp.BlameUiExtensions+<G>$2E78AC6C4589E2D26F56ACB2C0F932B4
BlameResult Blame(UiText, ReadOnlySpan<String>, ReadOnlySpan<Int32>, ReadOnlySpan<BlameCommit>, BlameOpts)
class CuiSharp.BlameUiExtensions+<G>$2E78AC6C4589E2D26F56ACB2C0F932B4+<M>$D152016828D0F2CB0A8B78500F4F70D3
struct CuiSharp.Box
ctor(Single, Single, Align, Size, Size)
field Align Align
field Boolean Clip
field Boolean ScrollX
Expand Down Expand Up @@ -269,6 +271,7 @@ class CuiSharp.CaptureUiExtensions+<G>$2E78AC6C4589E2D26F56ACB2C0F932B4
Void SignatureClear(UiText)
class CuiSharp.CaptureUiExtensions+<G>$2E78AC6C4589E2D26F56ACB2C0F932B4+<M>$D152016828D0F2CB0A8B78500F4F70D3
struct CuiSharp.Card
ctor(Single, Single, Dir, Size, Size)
field Align Align
field Boolean Hoverable
field Boolean Muted
Expand All @@ -295,13 +298,17 @@ struct CuiSharp.CarouselOpts
field Single AutoAdvance
field Single Gap
field Single Height
class CuiSharp.ChartNormalizer
static Int32 Normalize(ReadOnlySpan<Single>, Span<Single>, NormalizeMode)
static Single FindBaseline(ReadOnlySpan<Single>)
struct CuiSharp.ChartOpts
field Boolean Area
field Boolean NoGrid
field Boolean NoLegend
field Boolean Smooth
field Boolean Stacked
field Int32 MaxPoints
field NormalizeMode Normalize
field Single Height
field Single Max
field Single Min
Expand Down Expand Up @@ -598,6 +605,7 @@ class CuiSharp.CuiAppOptions
property Boolean FixedSize { get; set; }
property Boolean FollowSystemTheme { get; set; }
property Boolean Hidden { get; set; }
property Boolean LiveAttach { get; set; }
property Boolean NoVsync { get; set; }
property IFileDialogProvider FileDialog { get; set; }
property Int32 Height { get; set; }
Expand Down Expand Up @@ -675,6 +683,7 @@ class CuiSharp.CuiSurface
Void Type(UiText)
property Boolean FileDragActive { get; }
property Boolean Incremental { get; set; }
property Boolean LastFrameReady { get; }
property Boolean RecordWidgets { get; set; }
property FrameSample LastFrame { get; set; }
property IReadOnlyList<Exception> Faults { get; }
Expand All @@ -692,6 +701,12 @@ class CuiSharp.CuiSurface
property Ui Ui { get; }
static CuiSurface Create(Int32, Int32, Single, Int32, Theme, Accent)
static CuiSurface CreateGpu(Int32, Int32, in NativeRendererIface, Single, Theme, Accent)
enum CuiSharp.CuiTaskStatus
Done
Failed
Pending
Running
Skipped
class CuiSharp.CuiTree
static Int32 Sort(TreeNode[], Int32, Comparison<Int32>, String)
class CuiSharp.CuiWindow
Expand Down Expand Up @@ -1457,16 +1472,20 @@ class CuiSharp.Hosting.HeadlessApp
HeadlessApp Type(String)
Int32 Settle(Int32, Nullable<TimeSpan>)
String Print(Boolean)
String PrintLayout()
String SavePng(String, Boolean)
String ToString()
Void Dispose()
WaitOutcome WaitFor(String, TimeSpan, CancellationToken)
WaitOutcome WaitReady(TimeSpan, CancellationToken)
WidgetTarget Find(String)
WidgetTarget Find(String, String)
WidgetTarget FindButton(String)
WidgetTarget FindById(String)
WidgetTarget FindInput(String)
WidgetTarget FindItem(String, Int32)
property Boolean Animating { get; }
property Boolean Ready { get; }
property Component Root { get; }
property CuiSurface Surface { get; }
property IReadOnlyList<SemanticWidget> Widgets { get; }
Expand All @@ -1477,6 +1496,11 @@ class CuiSharp.Hosting.HeadlessApp
property SemanticNode Tree { get; }
static HeadlessApp Start(Component, Int32, Int32, Single, Theme, Accent)
static HeadlessApp Start(DrawFn, Int32, Int32, Single, Theme, Accent)
struct CuiSharp.Hosting.HeadlessApp+WaitOutcome
String ToString()
property Boolean Satisfied { get; }
property Int32 Frames { get; }
property TimeSpan Elapsed { get; }
class CuiSharp.Hosting.HeadlessLoop
field String EnvironmentVariable
property IReadOnlyList<String> Known { get; }
Expand Down Expand Up @@ -2243,10 +2267,10 @@ struct CuiSharp.Interop.NativeAgentPlanOpts
field Int32 Sentinel
field IntPtr Title
struct CuiSharp.Interop.NativeAgentStatusOpts
field CuiTaskStatus Status
field Int32 Sentinel
field IntPtr Icon
field Single Seconds
field TaskStatus Status
struct CuiSharp.Interop.NativeAlertOpts
field Byte Dismissible
field Int32 Sentinel
Expand Down Expand Up @@ -3427,9 +3451,9 @@ struct CuiSharp.Interop.NativePipelineResult
field Int32 Clicked
field Int32 Hovered
struct CuiSharp.Interop.NativePlanStep
field CuiTaskStatus Status
field IntPtr Detail
field IntPtr Title
field TaskStatus Status
struct CuiSharp.Interop.NativePlatformIface
field IntPtr DrawEmoji
field IntPtr GetClipboard
Expand Down Expand Up @@ -4050,13 +4074,13 @@ struct CuiSharp.Interop.NativeToggleGroupOpts
field Int32 Sentinel
field IntPtr Icons
struct CuiSharp.Interop.NativeToolCallOpts
field CuiTaskStatus Status
field Int32 Sentinel
field IntPtr Detail
field IntPtr Icon
field IntPtr Input
field IntPtr Output
field Single Seconds
field TaskStatus Status
struct CuiSharp.Interop.NativeToolOpts
field Byte Disabled
field Int32 Sentinel
Expand Down Expand Up @@ -4389,6 +4413,9 @@ struct CuiSharp.KvEditorOpts
field Size Width
field String KeyPlaceholder
field String ValuePlaceholder
class CuiSharp.LayoutFormatter
field String Header
static String Print(SemanticNode)
struct CuiSharp.LayoutScope
Void Dispose()
class CuiSharp.LayoutUiExtensions
Expand Down Expand Up @@ -4843,6 +4870,10 @@ struct CuiSharp.NodeView
field Single X
field Single Y
field Single Zoom
enum CuiSharp.NormalizeMode
Index100
None
Percent
struct CuiSharp.NowPlayingOpts
field Boolean Compact
field Size Width
Expand Down Expand Up @@ -4941,10 +4972,10 @@ enum CuiSharp.Placement
TopEnd
TopStart
struct CuiSharp.PlanStep
ctor(String, String, TaskStatus)
ctor(String, String, CuiTaskStatus)
field CuiTaskStatus Status
field String Detail
field String Title
field TaskStatus Status
class CuiSharp.PlanningUiExtensions
static Boolean Kanban(Ui, UiText, out KanbanMove, DrawFn)
static Int32 Gantt(Ui, UiText, Span<GanttTask>, Date, Int32, GanttOpts)
Expand Down Expand Up @@ -5277,6 +5308,8 @@ class CuiSharp.SemanticNode
SemanticNode Find(String)
String Print(Boolean)
String ToString()
property Boolean Detached { get; }
property Boolean HasOwnRect { get; }
property Boolean IsContainer { get; set; }
property IReadOnlyList<SemanticNode> Children { get; }
property Int32 Depth { get; }
Expand All @@ -5289,6 +5322,8 @@ class CuiSharp.SemanticNode
struct CuiSharp.SemanticWidget
Boolean Contains(Single, Single)
String ToString()
field Boolean Detached
field Boolean HasOwnRect
field Boolean IsContainer
field Int32 Depth
field RectF Rect
Expand Down Expand Up @@ -5484,12 +5519,6 @@ struct CuiSharp.TagOpts
field Size Width
field String Label
field String Placeholder
enum CuiSharp.TaskStatus
Done
Failed
Pending
Running
Skipped
class CuiSharp.Terminal
Void Clear()
Void Dispose()
Expand Down Expand Up @@ -5578,6 +5607,7 @@ struct CuiSharp.TextMetrics
field Single LineHeight
field Single Width
struct CuiSharp.TextOpts
ctor(Single, Color)
field Boolean NoWrap
field Boolean Selectable
field Boolean Tabular
Expand Down Expand Up @@ -5778,12 +5808,12 @@ enum CuiSharp.Tone
Violet
Warning
struct CuiSharp.ToolCallOpts
field CuiTaskStatus Status
field Single Seconds
field String Detail
field String Icon
field String Input
field String Output
field TaskStatus Status
struct CuiSharp.ToolOpts
field Boolean Disabled
field String Label
Expand Down Expand Up @@ -5977,6 +6007,7 @@ struct CuiSharp.Ui
Void Icon(UiText, Single, Color)
Void LineChart(UiText, ReadOnlySpan<Single>, ChartOpts, Color)
Void LineChart(UiText, Series[], ChartOpts)
Void MarkReady()
Void PopClip()
Void PushClip(RectF)
Void ReorderBegin(UiText, Int32[])
Expand Down
20 changes: 15 additions & 5 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,20 @@ anything round-trip. The reader re-synchronizes on any line that is not a header
print whatever it likes: its output goes to stderr and the protocol stays clean.

The tool stays a relay: it builds (or reuses a published build), starts the app headless, sends
one command, prints one answer, and gets out of the way. It is deliberately not a test framework,
not a session manager and not a hot reload integration — `CuiSharp.Testing` is the fast, repeatable
way to drive an app from an agent, and `dotnet watch` is how a human edits in place. Anything that
would need a stateful daemon belongs in the testing driver, not here.
one command, prints one answer, and gets out of the way. With `--attach`, the same protocol drives
an explicitly enabled Windows live window over a user-only local named pipe instead. There is no
HTTP/MCP server or separate daemon. `CuiSharp.Testing` remains the fast, repeatable way to drive an
app from an agent, and `dotnet watch` is how a human edits in place.

Live work runs on the owning window thread, between completed frames in the pinned Win32 backend's
message pump. Callback nesting guards are thread/context-owned: an unrelated surface cannot change
that window's guard. A connection has a bounded asynchronous reader that detects EOF even during
command execution. Disconnects cancel outstanding work; injected keys/buttons are released and a
cleanup frame consumes the release before the next client is accepted. Already-consumed actions
cannot be rolled back. Capture-only waits enforce their wall-clock deadline even if drawing stalls
and never accept a late success. Teardown closes the active pipe and joins its worker after pending
UI tasks are failed, without requiring another frame. `CuiSharpLive=false` removes the live channel
from an ordinary AOT publish; retaining it requires `-p:CuiSharpLive=true` and runtime opt-in.

An app opts in by running headless (`CUISHARP_HEADLESS=1` or `--headless`). `CuiApp.Run` is the only
place that decides, and a hosted app reaches it with its host's draw callback, so a tool drives the
Expand All @@ -283,5 +293,5 @@ timeout says which wait it gave up on — the banner or the answer.
|---|---|---|
| `CuiSharp` | the library, each RID's `runtimes/<rid>/native/` files, `runtimes/native-provenance.txt`, analyzers and IDE fixes, build targets | AOT links the RID's static archive through `build/CuiSharp.targets`; the RID is matched exactly and a missing archive is an error, not a warning |
| `CuiSharp.Testing` | `TestApp` (a `HeadlessApp` under the name a test wants) | test-only dependency |
| `CuiSharp.Tools` | `dotnet cui` | `PackAsTool`, `ToolCommandName=cui` |
| `CuiSharp.Tools` | `dotnet cui` | `PackAsTool`, `ToolCommandName=dotnet-cui` |
| `CuiSharp.Templates` | `dotnet new cui-app`, `dotnet new cui-mini` | a solution (app + tests + `AGENTS.md`) or a file-based app named with `-n` in the current directory (optional `-o`), with a package directive |
10 changes: 10 additions & 0 deletions docs/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,14 @@ in [ARCHITECTURE.md](ARCHITECTURE.md). Every item here passes the same gates as
NSAccessibility. The semantic tree is the foundation to build on, not a substitute.
- **One window per app.** CUI creates one window; multi-window is an upstream question.
- **IME coverage** beyond what upstream implements.
- **Chart time axes and gaps.** The managed `NormalizeMode` is a y-value transform; CUI still spaces
line/bar points uniformly by index and has no explicit gap segments. True timestamps, irregular
calendars, aligned hover/labels and NaN-gap semantics need native API and renderer work upstream.
- **Complete layout introspection.** `cui layout` can report verified named rectangles and
lower-bound unions, but CUI exposes neither id-0 container rectangles nor authoritative
scroll/content extents. A read-only layout walk and scroll-info API belong upstream.
- **Cross-platform live-window control.** Windows live attach is opt-in and uses the pinned
backend's between-frame message pump, thread/context-owned callback guards and destruction
hooks. Other backends still need an upstream post-at-frame-boundary/wake and destroy-lifecycle
API; do not substitute a background render loop or silently start a headless app.
- **macOS signing and notarization** for a distributed `.app`.
Loading
Loading