Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 27 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,44 @@ different things.

### Added

- **`dotnet cui docs`** prints the guide, the reference and the behaviour pages that ship *inside*
the tool: no repository, no network, nothing built and nothing started. `docs read <topic|doc|doc#section>`
reads one of fourteen task topics or any page whole, `docs search <words>` ranks the sections that
answer a question and names the read that opens each one, and `--json` makes any of them
machine-readable. The topics are pointers into the shipped pages, so the tool cannot say anything
the documentation does not.
- **`dotnet cui session`** relays one protocol command per line from stdin against a single build,
replacing the `--stdin` flag.
- **A demo icon in both templates.** `dotnet new cui-app` ships `<Project>.ico` wired in with
`ApplicationIcon`, and `dotnet new cui-mini` generates `<name>.cs` plus a matching `<name>.ico`;
replacing the file and rebuilding changes the exe icon and the icon on the title bar, taskbar
button and `Alt+Tab` card. The shipped icon is a black square reading CUI.

### Changed

- **The command line is declared with System.CommandLine**, so `dotnet cui <command> --help` and the
parser cannot disagree. What the old hand-written help promised and the parser ignored now works:
`-a <pid|title>` takes its value, `-t/--target` is gone in its favour, `--version` answers with a
version, and a mistyped verb is answered with the verb that was meant (`clik` → ``Did you mean
`dotnet cui click`?``). Usage errors exit 2 with the reason on stderr; a failed launch exits 1 with
one sentence instead of a stack trace.
- **Options belong to the commands that honour them.** `docs`, `api`, `icons` and `ps` answer
offline and declare no `--project`, `--app`, `--attach`, `--no-build`, `--frames` or `--wait`, so
they cannot be given one that would be ignored. Options follow the command they belong to:
`dotnet cui tree --frames 2`, not `dotnet cui --frames 2 tree`.
- **A query is one quoted argument.** `dotnet cui click "Button Save"` is a click on the button
labelled *Save*; the unquoted `dotnet cui click Button Save` was two words and is now a usage
error rather than a different query. `dotnet cui click "editor > Save"` quotes the path separator
that the shell would otherwise read as redirection.
- **`dwell` and `waittext` require their duration**, which the protocol always needed: bare `dwell`
used to start an app and then fail with an unknown command.

### Fixed

- **The embedded icon reaches the window.** An apphost embeds `ApplicationIcon` as `IDI_APPLICATION`,
which Explorer reads off the exe but the CUI window class does not find, so a published app wore
the system's generic logo in the title bar and taskbar. `CuiApp` now loads that resource and sends
`WM_SETICON` itself when the window opens, small and large, at the system's icon metrics.

- **`dotnet new cui-mini` no longer opens a console window.** The generated file-based app now
sets `#:property OutputType=WinExe`, so `dotnet publish` produces a GUI-subsystem executable
like the one `dotnet new cui-app` builds; `tools/verify-template.ps1` reads that subsystem out
Expand Down
65 changes: 57 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,46 @@ internal static class MyControls
ui.Spark(Monthly.Revenue);
```

## Give an agent the whole thing

An assistant needs two commands, and the project's instruction file needs one paragraph — never a
copy of this README:

```sh
dotnet tool install -g CuiSharp.Tools # exposes `dotnet cui`
dotnet cui docs # the topics, the documents, and what to run next
```

Then this is the whole of what a project's `AGENTS.md` has to say about CuiSharp:

> This project uses CuiSharp. Run `dotnet cui docs` before writing UI code. Use
> `dotnet cui docs search "<question>"` and `dotnet cui docs read <topic>` for the rules and the
> pitfalls, `dotnet cui api <Name>` for an exact signature, and `dotnet cui tree` / `find` / `click` /
> `png` to see what a build actually draws. Anything that must keep holding belongs in the test
> project, not in a shell history.

`dotnet cui docs` answers from the guide, the reference and the behaviour pages **inside the tool**:
no repository, no NuGet cache, no network, nothing built and nothing started. Fourteen topics cover
the tasks an assistant arrives with — `getting-started`, `writing-ui`, `custom-controls`, `hosting`,
`testing`, `debugging`, `layout`, `live-debugging`, `performance`, `theming`, `translation`,
`publishing`, `signatures`, `widgets` — and each one is a named set of sections of the shipped pages,
so the tool cannot say anything the documentation does not.

```sh
dotnet cui docs # topics, documents, and the commands that come next
dotnet cui docs read testing # one topic, whole
dotnet cui docs read SKILL # the guide
dotnet cui docs read SKILL#pitfalls # one section of it
dotnet cui docs search "decimate chart" # ranked sections, each with the read that opens it
dotnet cui api ChartOpts # exact declarations
dotnet cui icons chart # the glyph index
```

Every answer names the command that goes deeper, an unknown topic names the nearest one, and
`--json` turns any of them into machine-readable output. That is why a beginner's project file can be
short: the framework's rules are versioned with the tool that ships them, instead of being re-explained
in every repository that uses it.

## Test it, and let an agent drive it

`CuiSharp.Testing` runs the real component with no window: find a control by what it says, click it,
Expand All @@ -179,14 +219,21 @@ Assert.True(app.PaintedAnything());
`dotnet cui` does the same thing to a real app process from a shell, with nothing added to the app:

```sh
dotnet cui tree # the controls of the last frame, nested
dotnet cui click Save # click a control, print the frame it produced
dotnet cui click Button "Save" # …when a heading says the same word
dotnet cui click editor > Save # …or name the path to the control
dotnet cui type Milk # type into the focused field
dotnet cui png shot.png # the frame as a PNG, for a person or a model
dotnet cui tree # the controls of the last frame, nested
dotnet cui click Save # click a control, print the frame it produced
dotnet cui click "Button Save" # …when a heading says the same word
dotnet cui click "editor > Save" # …or the path to the control
dotnet cui type Milk # type into the focused field
dotnet cui png shot.png # the frame as a PNG, for a person or a model
dotnet cui tree -a 15036 # the same, on a live window a person is watching
dotnet cui session < script.txt # many commands, one build, one app
```

The query is **one quoted argument** — `click "Button Save"`, not `click Button Save` — because the
app parses that little grammar and this tool hands it over untouched. The tool's own options follow
the command they belong to (`dotnet cui tree --frames 2`, not `dotnet cui --frames 2 tree`), and
`dotnet cui <command> --help` lists exactly the options that command honours.

```
Frame
Card "editor"
Expand Down Expand Up @@ -257,7 +304,7 @@ src/CuiSharp/ the library: Interop (generated), Ui, Runtime, Semantic
src/CuiSharp.Analyzers/ CUI001-CUI008, packed into CuiSharp
src/CuiSharp.CodeFixes/ IDE quick fixes, packed beside the analyzers
src/CuiSharp.Testing/ TestApp: drive a component without a window
src/CuiSharp.Tools/ dotnet cui
src/CuiSharp.Tools/ dotnet cui: the guide and the API offline, plus build, run headless, relay one command
src/CuiSharp.Templates/ dotnet new cui-app / cui-mini
samples/CuiSharp.Gallery/ twenty-eight pages, one file per page
samples/CuiSharp.Gallery.Browser/ the same gallery on a software canvas in WebAssembly
Expand Down Expand Up @@ -288,7 +335,9 @@ below works in either place.
| RIDs carried, glibc floor, provenance | [skills/cui-dotnet/native-abi.md](skills/cui-dotnet/native-abi.md) |
| Rules and decisions, before changing the framework | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |

An assistant needs the first two: point it at the folder and say "read `SKILL.md`". `widgets.md` and
An assistant needs the first two: point it at the folder and say "read `SKILL.md`", or — with
nothing but the tool installed — tell it to run `dotnet cui docs`, which carries that same folder.
`widgets.md` and
`native-abi.md` are copies of CUI's documentation and of the ABI promise, regenerated with
`pwsh tools/regen.ps1`; the rest of the folder is hand-written or generated from the headers.

Expand Down
21 changes: 19 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Settled. Reopening one is a design change, not a refactor.
| D8 | **A packaged native library declares the ABI it was built against.** Windows x64/ARM64 is `/MT` MSVC, Linux x64/ARM64 is glibc 2.31 built inside `debian:11`, `osx-arm64` targets macOS 12. `native/build-native.sh` fails rather than emit a `libcui.so` that needs more, and the Linux build runs inside that image so it cannot inherit a newer floor; the package records the revision, compiler and hashes it shipped (`runtimes/native-provenance.txt`), and `build/CuiSharp.targets` matches the RID exactly and errors when the archive for it is missing. See [native-abi.md](../skills/cui-dotnet/native-abi.md). |
| D9 | **Window behaviour the framework needs is a hook over CUI's own window procedure, not a native patch.** A drag on Windows makes Win32 send `WM_SIZE` per mouse sample and then a `WM_PAINT` that invalidates the whole client area, and CUI draws in both, inside the modal loop, with vsync on: one mouse step cost two frames and two display waits. `Hosting/LiveResize` subclasses the window procedure (the one place Windows lets a framework intervene), lets the coalesced paint draw once, and stops waiting for the display for the length of the drag — driving CUI's own live-frame path rather than replacing it. The same hook is where the window handle is recovered for `CuiWindow`, because the app loop keeps its `cui_app*` private and the window's user data holds it. A fix that needed `native/CUI/` would be upstream's, per D2. |
| D10 | **A published binary is a product, and the package decides what goes in it.** `build/CuiSharp.Size.targets` applies the switches that decide size (`CuiSharpSizeProfile`, `CuiSharpHeadless`, `CuiSharpVerboseErrors`) to a self-contained AOT publish and to nothing else, because that is the only build whose size matters: `dotnet run` keeps the headless channel and the framework's exception messages. `PublishAot` alone is not the signal — the file-based app shape sets it for `dotnet run` too. Measured in [SIZE.md](SIZE.md), which is what a size decision has to cite. |
| D11 | **The tool carries the documentation, and a topic names sections instead of repeating them.** `dotnet cui docs` answers from `skills/cui-dotnet/**` embedded in `CuiSharp.Tools`, so an assistant that has installed one tool can read the guide in a directory with no repository, no network and no app — the reason a project's instruction file can say `dotnet cui docs` instead of restating the framework's rules. A *topic* (`testing`, `debugging`, `publishing`) is a named list of `doc#section` pointers resolved at load, so every sentence has exactly one home and a renamed heading fails a test rather than leaving a topic that reads but misleads. The bundle is embedded, not copied, and a test compares the bytes with the checked-in files. |
| D12 | **The command line is declared, not parsed by hand, and every option lives on a command that can honour it.** `Grammar` builds a System.CommandLine tree: every verb is a `Command` whose arguments carry their own type and arity, and every option is an `Option` on the commands that use it. The help, the arity, the value types and the error messages then come from a single declaration, which is what stops them disagreeing — the hand-rolled parser had already shipped a help line promising `-a, --attach [id]` while `-a` took no value, so `dotnet cui click "中文" -a 15036` searched for a widget called `中文 15036`. Options are deliberately *not* inherited from the root: `docs`, `api` and `icons` answer offline and name no app, so `--project`, `--attach` and `--frames` do not exist there and cannot be silently ignored. The verbs are still the app's wire protocol: `Grammar.Resolve` re-serialises one command's arguments into exactly one protocol line, so the parser changed and the protocol did not. `dotnet cui` keeps that shape because an agent reads a grammar it can discover (`--help` per verb) more reliably than a man page it has to remember. |

## Layout

Expand All @@ -37,7 +39,7 @@ src/CuiSharp/ the library (package: CuiSharp)
src/CuiSharp.Analyzers/ CUI001-CUI008, packed into CuiSharp at analyzers/dotnet/cs
src/CuiSharp.CodeFixes/ IDE-only fixes, packed beside the analyzers (no runtime dependency)
src/CuiSharp.Testing/ TestApp: drive a component without a window
src/CuiSharp.Tools/ dotnet cui: build, run headless, relay one command
src/CuiSharp.Tools/ dotnet cui: the docs and API offline, then build, run headless, relay one command
src/CuiSharp.Templates/ dotnet new cui-app / cui-mini
src/CuiSharp.Tests/ ABI, enums, marshalling, frame behaviour, hosting, tools, docs drift
src/CuiSharp.Benchmarks/ BenchmarkDotNet suites + --fps and --scale harnesses
Expand Down Expand Up @@ -264,6 +266,21 @@ an explicitly enabled Windows live window over a user-only local named pipe inst
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.

The three commands that are not a relay — `docs`, `api`, `icons` — return before any project is
resolved, so they answer in a directory that holds no app and start no process (D11). That is what
lets one installed tool serve both halves of an assistant's job: read the rules, then drive a build.

The grammar itself is declared with System.CommandLine and the verbs are re-serialised into the
protocol lines above (D12), so `--help` — per verb, generated — is the manual, and it lists only
the options that verb honours. Three rules survive from the hand-rolled parser because the protocol
wants them: everything after a standalone `--` is handed to the app untouched, a value is a value
even when it begins with a dash (`--wait-text -loading`), and a query is one quoted argument
(`click "Button Save type"`), because the app parses the query grammar and this tool does not.

A session is a command (`dotnet cui session < commands.txt`), not a flag. As a flag it could be
written beside a verb, and the verb would then be silently dropped; as a command the line says
which of the two things is happening.

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
Expand Down Expand Up @@ -293,5 +310,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=dotnet-cui` |
| `CuiSharp.Tools` | `dotnet cui`, with `skills/cui-dotnet/**` and the API catalog embedded | `PackAsTool`, `ToolCommandName=dotnet-cui`. The documentation and the catalog are resources, not files beside it: `docs`, `api` and `icons` answer offline, before any project is resolved |
| `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 |
3 changes: 3 additions & 0 deletions docs/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ in [ARCHITECTURE.md](ARCHITECTURE.md). Every item here passes the same gates as

- **Accessibility.** CUI publishes no accessibility tree, so there is no UIA, AT-SPI or
NSAccessibility. The semantic tree is the foundation to build on, not a substitute.
- **Installing a skill into a particular agent harness.** `dotnet cui docs` is the portable form of
the guide: it works in any shell, for any tool. Per-harness registration (a `skills/` folder, a
plugin manifest, a system prompt) stays a local convention, and none of it is generated.
- **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
Expand Down
Loading
Loading