Skip to content

Commit d939953

Browse files
tigclaude
andauthored
Adds doc snippet compile validation; fixes v1 API rot in agent docs (#5480)
* Add doc snippet compile validation with obsolete-API rejection Roslyn-based validator that compiles every fenced C# block in the agent docs (ai-v2-primer.md, build-app.md, common-patterns.md) against the built Terminal.Gui assembly, so example rot fails CI instead of misleading readers. Statement fragments are wrapped in a Runnable<string?> harness; blocks marked WRONG / `snippet: ignore` are skipped. Obsolete-API use (CS0618/CS0612) is treated as a failure, not a warning, so v1 rot like the legacy static Application.Init cannot pass as "compiled". testdata/obsolete-api.md + a CI negative-test step assert the validator rejects obsolete APIs. The check caught real rot fixed here: two cookbook snippets used the obsolete TextView (now a plain content View, pointing at gui-cs/Editor's EditorView). Result: 29 compiled, 1 skipped, 0 failed; negative test green. Rebased onto develop after #5478 merged (squash), keeping only this PR's files; no overlap with the merged agent-docs lint changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix Validate Doc Snippets CI: full history for GitVersion The workflow built Terminal.Gui without fetch-depth:0, so GitVersion.MsBuild failed on the shallow clone ("Repository is a shallow clone"). Match the build-validation workflow's checkout (fetch-depth: 0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent d66794b commit d939953

10 files changed

Lines changed: 527 additions & 94 deletions

File tree

.claude/cookbook/common-patterns.md

Lines changed: 90 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public sealed class FormWindow : Runnable<FormData?>
4646
X = 1,
4747
Y = 7,
4848
Width = Dim.Fill (1),
49-
ColorScheme = Colors.ColorSchemes ["Error"]
49+
SchemeName = "Error"
5050
};
5151

5252
Button submitButton = new ()
@@ -96,11 +96,13 @@ public record FormData (string Name, string Email, int Age);
9696
A scrollable list with item selection and actions.
9797

9898
```csharp
99+
using System.Collections.ObjectModel;
100+
99101
public sealed class ListWindow : Runnable
100102
{
101103
private readonly ListView _listView;
102104
private readonly Label _detailLabel;
103-
private readonly List<string> _items = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];
105+
private readonly ObservableCollection<string> _items = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];
104106

105107
public ListWindow ()
106108
{
@@ -131,21 +133,21 @@ public sealed class ListWindow : Runnable
131133
Y = Pos.AnchorEnd (1)
132134
};
133135

134-
_listView.SelectedItemChanged += (_, e) =>
136+
// ListView is IValue<int?> — the selected index. SelectedItem is int?.
137+
_listView.ValueChanged += (_, e) =>
135138
{
136-
if (e.Value >= 0 && e.Value < _items.Count)
139+
if (e.NewValue is int index && index < _items.Count)
137140
{
138-
_detailLabel.Text = $"Selected: {_items [e.Value]}";
141+
_detailLabel.Text = $"Selected: {_items [index]}";
139142
}
140143
};
141144

142-
selectButton.Accepting += (_, e) =>
145+
selectButton.Accepted += (_, _) =>
143146
{
144-
if (_listView.SelectedItem >= 0)
147+
if (_listView.SelectedItem is int selected)
145148
{
146-
MessageBox.Query (App!, "Selection", $"You selected: {_items [_listView.SelectedItem]}", "OK");
149+
MessageBox.Query (App!, "Selection", $"You selected: {_items [selected]}", "OK");
147150
}
148-
e.Handled = true;
149151
};
150152

151153
Add (_listView, _detailLabel, selectButton);
@@ -166,43 +168,33 @@ public sealed class MenuApp : Runnable
166168
{
167169
Title = "Menu Demo";
168170

169-
MenuBar menuBar = new ()
170-
{
171-
Menus =
172-
[
173-
new MenuBarItem (
174-
"File",
175-
[
176-
new MenuItem ("New", "", () => NewFile (), null, null, KeyCode.N | KeyCode.CtrlMask),
177-
new MenuItem ("Open...", "", () => OpenFile (), null, null, KeyCode.O | KeyCode.CtrlMask),
178-
new MenuItem ("Save", "", () => SaveFile (), null, null, KeyCode.S | KeyCode.CtrlMask),
179-
null, // Separator
180-
new MenuItem ("Exit", "", () => App!.RequestStop (), null, null, KeyCode.Q | KeyCode.CtrlMask)
181-
]),
182-
new MenuBarItem (
183-
"Edit",
184-
[
185-
new MenuItem ("Cut", "", null, null, null, KeyCode.X | KeyCode.CtrlMask),
186-
new MenuItem ("Copy", "", null, null, null, KeyCode.C | KeyCode.CtrlMask),
187-
new MenuItem ("Paste", "", null, null, null, KeyCode.V | KeyCode.CtrlMask)
188-
]),
189-
new MenuBarItem (
190-
"Help",
191-
[
192-
new MenuItem ("About...", "", () => ShowAbout ())
193-
])
194-
]
195-
};
196-
197-
TextView editor = new ()
171+
MenuBar menuBar = new ();
172+
173+
menuBar.Add (new MenuBarItem ("_File",
174+
[
175+
new MenuItem { Title = "_New", Key = Key.N.WithCtrl, Action = NewFile },
176+
new MenuItem { Title = "_Open...", Key = Key.O.WithCtrl, Action = OpenFile },
177+
new MenuItem { Title = "_Save", Key = Key.S.WithCtrl, Action = SaveFile },
178+
new Line (), // Separator
179+
new MenuItem { Title = "_Quit", Key = Key.Q.WithCtrl, Action = () => App!.RequestStop () }
180+
]));
181+
182+
menuBar.Add (new MenuBarItem ("_Help",
183+
[
184+
new MenuItem { Title = "_About...", Action = ShowAbout }
185+
]));
186+
187+
// Main content area below the menu bar. (For a real multi-line editor,
188+
// use gui-cs/Editor's EditorView — the core TextView is deprecated.)
189+
View content = new ()
198190
{
199191
X = 0,
200-
Y = 1,
192+
Y = Pos.Bottom (menuBar),
201193
Width = Dim.Fill (),
202194
Height = Dim.Fill ()
203195
};
204196

205-
Add (menuBar, editor);
197+
Add (menuBar, content);
206198
}
207199

208200
private void NewFile () => MessageBox.Query (App!, "New", "New file created", "OK");
@@ -216,7 +208,7 @@ public sealed class MenuApp : Runnable
216208

217209
## Split View Layout
218210

219-
Horizontal or vertical split layout with resizable panes.
211+
Side-by-side panes positioned with `Pos`/`Dim`. (`TileView` does not exist in v2 — use plain views and `ViewArrangement` for user-resizable panes.)
220212

221213
```csharp
222214
public sealed class SplitWindow : Runnable
@@ -225,27 +217,27 @@ public sealed class SplitWindow : Runnable
225217
{
226218
Title = "Split View Demo";
227219

228-
TileView tileView = new ()
220+
FrameView leftPane = new ()
229221
{
230-
X = 0,
231-
Y = 0,
232-
Width = Dim.Fill (),
222+
Title = "Left Pane",
223+
Width = Dim.Percent (50),
233224
Height = Dim.Fill (),
234-
Orientation = Orientation.Vertical // or Horizontal
235-
};
236225

237-
// Left pane
238-
FrameView leftPane = new () { Title = "Left Pane" };
226+
// Let the user resize this pane with mouse or keyboard (optional)
227+
Arrangement = ViewArrangement.RightResizable
228+
};
239229
leftPane.Add (new Label { Text = "Left content", X = 1, Y = 1 });
240230

241-
// Right pane
242-
FrameView rightPane = new () { Title = "Right Pane" };
231+
FrameView rightPane = new ()
232+
{
233+
Title = "Right Pane",
234+
X = Pos.Right (leftPane),
235+
Width = Dim.Fill (),
236+
Height = Dim.Fill ()
237+
};
243238
rightPane.Add (new Label { Text = "Right content", X = 1, Y = 1 });
244239

245-
tileView.Tiles.ElementAt (0).ContentView.Add (leftPane);
246-
tileView.Tiles.ElementAt (1).ContentView.Add (rightPane);
247-
248-
Add (tileView);
240+
Add (leftPane, rightPane);
249241
}
250242
}
251243
```
@@ -254,7 +246,7 @@ public sealed class SplitWindow : Runnable
254246

255247
## Tab View
256248

257-
Tabbed interface with multiple content pages.
249+
Tabbed interface with multiple content pages. (v1's `TabView` is now `Tabs`: each added `View` becomes a tab, and its `Title` is the tab label.)
258250

259251
```csharp
260252
public sealed class TabbedWindow : Runnable
@@ -263,29 +255,25 @@ public sealed class TabbedWindow : Runnable
263255
{
264256
Title = "Tabbed Interface";
265257

266-
TabView tabView = new ()
267-
{
268-
X = 0,
269-
Y = 0,
270-
Width = Dim.Fill (),
271-
Height = Dim.Fill ()
272-
};
258+
Tabs tabs = new ();
273259

274-
// Tab 1: Settings
275-
View settingsTab = new ();
260+
// Tab 1: Settings — Title becomes the tab label
261+
View settingsTab = new () { Title = "Settings" };
276262
settingsTab.Add (
277263
new Label { Text = "Enable Feature:", X = 1, Y = 1 },
278264
new CheckBox { X = 20, Y = 1, Text = "Enabled" }
279265
);
280266

281267
// Tab 2: About
282-
View aboutTab = new ();
268+
View aboutTab = new () { Title = "About" };
283269
aboutTab.Add (new Label { Text = "Version 1.0.0", X = 1, Y = 1 });
284270

285-
tabView.AddTab (new Tab { DisplayText = "Settings", View = settingsTab }, false);
286-
tabView.AddTab (new Tab { DisplayText = "About", View = aboutTab }, false);
271+
tabs.Add (settingsTab, aboutTab);
272+
273+
// Tabs is IValue<View?> — set Value to switch tabs programmatically
274+
tabs.Value = settingsTab;
287275

288-
Add (tabView);
276+
Add (tabs);
289277
}
290278
}
291279
```
@@ -340,9 +328,14 @@ public sealed class ProgressWindow : Runnable
340328
Add (_statusLabel, _progressBar, cancelButton);
341329
}
342330

343-
public override void OnLoaded ()
331+
protected override void OnIsRunningChanged (bool newIsRunning)
344332
{
345-
base.OnLoaded ();
333+
base.OnIsRunningChanged (newIsRunning);
334+
335+
if (!newIsRunning)
336+
{
337+
return;
338+
}
346339

347340
// Start simulated work
348341
App!.AddTimeout (TimeSpan.FromMilliseconds (100), () =>
@@ -376,7 +369,7 @@ private void OpenFile ()
376369
{
377370
Title = "Open File",
378371
AllowsMultipleSelection = false,
379-
DirectoryPath = Environment.CurrentDirectory
372+
Path = Environment.CurrentDirectory // Starting directory
380373
};
381374

382375
// Optional: filter by extension
@@ -397,15 +390,15 @@ private void SaveFile ()
397390
SaveDialog dialog = new ()
398391
{
399392
Title = "Save File",
400-
DirectoryPath = Environment.CurrentDirectory,
401-
FileName = "document.txt"
393+
Path = Environment.CurrentDirectory // Starting directory
402394
};
403395

404396
App!.Run (dialog);
405397

398+
// FileName is the name portion of the chosen Path (null if canceled)
406399
if (!dialog.Canceled && !string.IsNullOrEmpty (dialog.FileName))
407400
{
408-
string path = dialog.FileName;
401+
string path = dialog.Path;
409402
// Save to path...
410403
}
411404
}
@@ -438,6 +431,8 @@ private void OpenMultipleFiles ()
438431
Display tabular data with TableView.
439432

440433
```csharp
434+
using System.Data;
435+
441436
public sealed class DataTableWindow : Runnable
442437
{
443438
public DataTableWindow ()
@@ -474,12 +469,21 @@ public sealed class DataTableWindow : Runnable
474469
Text = "Select a row"
475470
};
476471

477-
tableView.SelectedCellChanged += (_, e) =>
472+
// TableView is IValue<TableSelection?> — ValueChanged fires when the selection moves.
473+
// SelectedCell is a Point: X = column, Y = row.
474+
tableView.ValueChanged += (_, e) =>
478475
{
479-
if (e.NewRow >= 0 && e.NewRow < table.Rows.Count)
476+
if (e.NewValue is not { } selection)
477+
{
478+
return;
479+
}
480+
481+
int row = selection.SelectedCell.Y;
482+
483+
if (row >= 0 && row < table.Rows.Count)
480484
{
481-
DataRow row = table.Rows [e.NewRow];
482-
statusLabel.Text = $"Selected: {row ["Name"]} - ${row ["Price"]}";
485+
DataRow dataRow = table.Rows [row];
486+
statusLabel.Text = $"Selected: {dataRow ["Name"]} - ${dataRow ["Price"]}";
483487
}
484488
};
485489

@@ -495,6 +499,8 @@ public sealed class DataTableWindow : Runnable
495499
Hierarchical data display.
496500

497501
```csharp
502+
using System.IO;
503+
498504
public sealed class TreeWindow : Runnable
499505
{
500506
public TreeWindow ()
@@ -568,7 +574,9 @@ public sealed class StatusBarApp : Runnable
568574
{
569575
Title = "Status Bar Demo";
570576

571-
TextView editor = new ()
577+
// Main content area. (For a real multi-line editor, use gui-cs/Editor's
578+
// EditorView — the core TextView is deprecated.)
579+
View content = new ()
572580
{
573581
Width = Dim.Fill (),
574582
Height = Dim.Fill (1) // Leave 1 row for StatusBar
@@ -604,7 +612,7 @@ public sealed class StatusBarApp : Runnable
604612
};
605613

606614
statusBar.Add (saveShortcut, quitShortcut);
607-
Add (editor, statusBar);
615+
Add (content, statusBar);
608616
}
609617
}
610618
```
@@ -652,7 +660,7 @@ if possible.
652660

653661
## Tips for All Patterns
654662

655-
1. **Always use `e.Handled = true`** in `Accepting` event handlers
663+
1. **Use `-ed` events (`Accepted`) for side effects**; use `Accepting` + `e.Handled = true` only to inspect or cancel
656664
2. **Use `Dim.Fill()` and `Pos.Center()`** instead of hardcoded values
657665
3. **Call `App!.RequestStop()`** to close the current window
658666
4. **Use `MessageBox.Query`** for simple dialogs

0 commit comments

Comments
 (0)