원클릭으로
doc-writer
// Guidelines for producing accurate and maintainable documentation for the Hex1b TUI library. Use when writing XML API documentation comments, creating end-user guides, or updating existing documentation.
// Guidelines for producing accurate and maintainable documentation for the Hex1b TUI library. Use when writing XML API documentation comments, creating end-user guides, or updating existing documentation.
| name | doc-writer |
| description | Guidelines for producing accurate and maintainable documentation for the Hex1b TUI library. Use when writing XML API documentation comments, creating end-user guides, or updating existing documentation. |
This skill provides guidelines for AI coding agents to help maintainers produce accurate and easy-to-maintain documentation for the Hex1b project. The repository contains two primary types of documentation, each serving different audiences and following different conventions.
Location: C# source files in src/Hex1b/
Audience: Library consumers, API reference generators, IDE intellisense users
Format: XML doc comments (///)
Build Setting: GenerateDocumentationFile is enabled in src/Hex1b/Hex1b.csproj
XML documentation comments provide:
Required for:
Example:
/// <summary>
/// Represents a color that can be used in the terminal.
/// </summary>
public readonly struct Hex1bColor
{
/// <summary>
/// Creates a color from RGB values.
/// </summary>
/// <param name="r">Red component (0-255)</param>
/// <param name="g">Green component (0-255)</param>
/// <param name="b">Blue component (0-255)</param>
/// <returns>A new Hex1bColor instance with the specified RGB values.</returns>
public static Hex1bColor FromRgb(byte r, byte g, byte b) => new(r, g, b);
}
Good:
/// <summary>
/// Creates a Layout that wraps a single child widget with clipping enabled.
/// </summary>
Bad:
/// <summary>
/// This method will take the child widget and wrap it in a layout.
/// The layout uses the clipMode parameter which defaults to Clip.
/// </summary>
Always document:
<param> for each parameter<returns> for non-void methods<exception> for thrown exceptions (when applicable)<typeparam> for generic type parametersExample:
/// <summary>
/// Arranges child widgets vertically within the given constraints.
/// </summary>
/// <param name="constraints">The size constraints for layout.</param>
/// <param name="children">The child widgets to arrange.</param>
/// <returns>The total size required for all children.</returns>
/// <exception cref="ArgumentNullException">Thrown when children is null.</exception>
public Size ArrangeVertical(Constraints constraints, Hex1bWidget[] children)
Use <example> and <code> tags for key APIs within the Hex1b framework. Examples should be consise yet contain sufficient context to help developers understand how they are used. For examples that use Hex1bApp to create sample applications favor the use of the fluent API since that is the intended usage pattern.
/// <summary>
/// The main entry point for building terminal UI applications.
/// </summary>
/// <example>
/// <para>Create a minimal Hex1b application:</para>
/// <code>
/// using Hex1b;
///
/// var app = new Hex1bApp(ctx =>
/// ctx.VStack(v => [
/// v.Text("Hello, Hex1b!"),
/// v.Button("Quit", e => e.Context.RequestStop())
/// ])
/// );
///
/// await app.RunAsync();
/// </code>
/// </example>
Note: HTML-encode special characters in code examples:
< becomes <> becomes >& becomes &Use <remarks> for:
/// <summary>
/// The main entry point for building terminal UI applications.
/// </summary>
/// <remarks>
/// Hex1bApp manages the render loop, input handling, focus management, and reconciliation
/// between widgets (immutable declarations) and nodes (mutable render state).
///
/// State management is handled via closures - simply capture your state variables
/// in the widget builder callback.
/// </remarks>
public class Hex1bApp : IDisposable, IAsyncDisposable
Use <see> and <seealso> to link to related types:
/// <summary>
/// Theme elements for Scroll widgets.
/// </summary>
/// <seealso cref="ScrollPanelWidget"/>
/// <seealso cref="Hex1bTheme"/>
public static class ScrollTheme
Internal APIs (marked with internal) should still be documented but can be less formal:
<NoWarn>$(NoWarn);CS1591</NoWarn>Review existing XML documentation in the codebase to match:
Location: src/content/
Audience: Hex1b library users (developers building TUI applications)
Format: Markdown with VitePress components
Build System: VitePress (static site generator)
This is the curernt documentation structure. As the documentation structure evolves, this should be updated to assist future agent development efforts.
src/content/
├── index.md # Landing page
├── api/
│ └── index.md # API reference overview
├── guide/
│ ├── getting-started.md # Tutorial walkthrough
│ ├── first-app.md # Quick start
│ ├── widgets-and-nodes.md # Core concepts
│ ├── layout.md # Layout system
│ ├── input.md # Input handling
│ ├── testing.md # Testing guide
│ ├── theming.md # Theming guide
│ └── widgets/ # Per-widget documentation
│ ├── text.md
│ ├── button.md
│ ├── textbox.md
│ ├── list.md
│ ├── stacks.md
│ ├── containers.md
│ └── navigator.md
├── deep-dives/ # Advanced topics
└── gallery.md # Examples showcase
Structure documentation from simple to complex:
Example: The getting-started.md file walks through building a todo app in 5 progressive steps, each adding new concepts.
Every code example should:
Good:
using Hex1b;
var state = new CounterState();
var app = new Hex1bApp(ctx =>
ctx.Border(b => [
b.Text($"Button pressed {state.Count} times"),
b.Button("Click me!").OnClick(_ => state.Count++)
], title: "Counter Demo")
);
await app.RunAsync();
class CounterState
{
public int Count { get; set; }
}
Bad (incomplete, won't compile):
var button = new Button();
button.Click = () => count++;
The site uses custom VitePress components for rich content:
<TerminalCommand command="..." /> - Show CLI commands<CodeBlock lang="csharp" :code="variable" /> - Code with syntax highlighting<TerminalDemo example="..." title="..." /> - Interactive terminal demos::: tip / ::: warning / ::: danger - Callout boxesExample:
::: tip Generating Full API Docs
Full API documentation can be generated from XML doc comments using tools like DocFX or xmldocmd.
```bash
dotnet build /p:GenerateDocumentationFile=true
xmldocmd src/Hex1b/bin/Release/net10.0/Hex1b.dll docs/api
:::
##### 4. Live Terminal Demos
The documentation site includes a WebSocket-based terminal that can run live code examples. This gives developers an interactive preview of how their code will behave when run locally.
**Location**: `src/Hex1b.Website/Examples/`
**Component**: `<TerminalDemo example="..." title="..." />`
###### How It Works
1. The Hex1b.Website project hosts WebSocket endpoints that run actual Hex1b applications
2. Each example in `src/Hex1b.Website/Examples/` implements `IGalleryExample`
3. The VitePress documentation uses the `<TerminalDemo>` component to connect to these endpoints
4. Users can interact with the live terminal directly in their browser
###### Code Duplication
Example code in the documentation may be **duplicated** from the main code samples with minor modifications:
- **Why**: The WebSocket terminal environment has slightly different requirements than running locally (e.g., terminal size handling, connection lifecycle)
- **What changes**: Usually minor adjustments to initialization, cleanup, or terminal configuration
- **Goal**: Give developers an accurate preview of runtime behavior, even if the hosted code differs slightly from the documented snippet
**Example structure**:
src/Hex1b.Website/Examples/ ├── CounterExample.cs # Live demo version ├── TodoExample.cs # Live demo version └── ...
The documentation code block shows the "clean" version a developer would write locally, while the `Examples/` folder contains the WebSocket-compatible version.
###### When to Create Live Demos
✅ **Good candidates for live demos**:
- Interactive widgets (buttons, text boxes, lists)
- Layout examples showing responsive behavior
- Input handling demonstrations
- Theming examples
❌ **Not suitable for live demos**:
- Examples using `Hex1bTerminal` for testing (headless/mock terminals)
- Examples that require local file system access
- Performance benchmarks or stress tests
- Examples with external dependencies
###### Registering Examples in Program.cs
**IMPORTANT**: After creating WebSocket example files, you must register them in `src/Hex1b.Website/Program.cs`. Examples will not be available in the live demos until registered.
Add a line for each example in the appropriate section:
```csharp
// Register [Widget name] widget documentation examples
builder.Services.AddSingleton<IGalleryExample, YourNewExample>();
Look for existing example registrations grouped by feature area and add your new examples in the corresponding section. If documenting a new widget, create a new comment section for clarity.
When updating documentation examples that have live demos:
Examples/ implementationExamples/ fileDon't just describe what methods exist—explain:
Example from widgets-and-nodes.md:
## Why This Matters
1. **State Preservation**: Focus doesn't jump around when the UI re-renders
2. **Performance**: Only changed parts of the tree get updated
3. **Simplicity**: You describe the UI declaratively; Hex1b figures out the transitions
Include:
Example:
| Layer | Type | Mutability | Purpose |
|-------|------|------------|---------|
| **Widget** | `record` | Immutable | Describes the desired UI |
| **Node** | `class` | Mutable | Manages state, renders to terminal |
Create a documentation graph:
Example from getting-started.md:
## Next Steps
- [Widgets & Nodes](/guide/widgets-and-nodes) - Understand the core architecture
- [Layout System](/guide/layout) - Master the constraint-based layout
- [Input Handling](/guide/input) - Learn about keyboard shortcuts and focus
- [Theming](/guide/theming) - Customize the appearance of your app
Each example should demonstrate one concept:
samples/Documentation changes should accompany code changes:
guide/widgets/*.md fileBefore finalizing documentation:
When documenting widgets in guide/widgets/, follow this consistent structure to ensure users can quickly find what they need.
📖 Canonical Example: See guide/widgets/text.md for a fully-realized example demonstrating all the patterns described below, including:
.cs snippet files with ?raw imports<CodeBlock> for full examples<StaticTerminalPreview> for visual variationsHex1bApp setupIMPORTANT: After creating a new widget documentation file, you MUST add it to the navigation sidebar in src/content/.vitepress/config.ts.
src/content/.vitepress/config.ts'Widgets' section under sidebar: { '/guide/': [...] }{ text: 'WidgetName', link: '/guide/widgets/widget-name' }text propertyFailure to update the navigation means users cannot discover the new documentation through the sidebar!
Include these when they add clear value:
Widget documentation benefits from live terminal demos using the <CodeBlock> component. However, choose the appropriate demo type based on the widget's nature.
Use Live Demo (<CodeBlock>) | Use Static Preview (<StaticTerminalPreview>) |
|---|---|
| Interactive widgets (buttons, text boxes) | Display-only widgets (text, layout) |
| User input demonstration needed | Showing specific visual states |
| Responsive behavior on resize | Code snippet with visual result |
| Focus/navigation demonstrations | Documenting multiple variations |
Live demos are best when:
Static previews are best when:
Hex1bApp setup)Every widget documentation page with interactive elements MUST include:
<CodeBlock example="..."> componentsrc/Hex1b.Website/Examples/src/Hex1b.Website/Program.csThis is NOT optional. Documentation without working interactive demos is incomplete. When writing widget docs:
.cs file in src/Hex1b.Website/Examples/Program.cs with builder.Services.AddSingleton<IGalleryExample, YourExample>()example="example-id" attribute to <CodeBlock> in the markdownVerification: After writing documentation, navigate to the page in a browser and click "Run in browser" to confirm the demo works.
src/Hex1b.Website/Examples/ for each demo<CodeBlock> component with the example attribute linking to the example Id<script setup> at the top of the markdown fileExample structure for widget docs:
<script setup>
const basicCode = \`using Hex1b;
using Hex1b.Widgets;
var app = new Hex1bApp(ctx => Task.FromResult<Hex1bWidget>(
ctx.VStack(v => [
v.WidgetMethod("example")
])
));
await app.RunAsync();\`
</script>
# WidgetName
Brief description.
## Basic Usage
<CodeBlock lang="csharp" :code="basicCode" command="dotnet run" example="widget-basic" exampleTitle="Widget - Basic Usage" />
Creating the WebSocket example file (src/Hex1b.Website/Examples/WidgetBasicExample.cs):
using Hex1b;
using Hex1b.Widgets;
using Microsoft.Extensions.Logging;
namespace Hex1b.Website.Examples;
public class WidgetBasicExample(ILogger<WidgetBasicExample> logger) : Hex1bExample
{
private readonly ILogger<WidgetBasicExample> _logger = logger;
public override string Id => "widget-basic"; // Must match example attribute in CodeBlock
public override string Title => "Widget - Basic Usage";
public override string Description => "Demonstrates basic widget usage";
public override Func<Hex1bWidget> CreateWidgetBuilder()
{
_logger.LogInformation("Creating widget basic example");
return () =>
{
var ctx = new RootContext();
return ctx.VStack(v => [
v.WidgetMethod("example")
]);
};
}
}
Always use the fluent API:
ctx.Text("Hello") or v.Text("Hello")new TextBlockWidget("Hello") - Do not show direct widget constructionBasic Usage must include Hex1bApp:
using Hex1b;
using Hex1b.Widgets;
var app = new Hex1bApp(ctx => Task.FromResult<Hex1bWidget>(
ctx.Text("Hello, World!")
));
await app.RunAsync();
Show widgets in context - Demonstrate usage within layout containers:
var app = new Hex1bApp(ctx => Task.FromResult<Hex1bWidget>(
ctx.VStack(v => [
v.Text("Title"),
v.Text("Description")
])
));
The following template shows the recommended structure. Adapt it based on the widget's complexity:
<script setup>
const basicCode = \`using Hex1b;
using Hex1b.Widgets;
var app = new Hex1bApp(ctx => Task.FromResult<Hex1bWidget>(
ctx.WidgetMethod("example")
));
await app.RunAsync();\`
</script>
# WidgetName
Brief description of what the widget does.
## Basic Usage
<CodeBlock lang="csharp" :code="basicCode" command="dotnet run" example="widget-basic" exampleTitle="Widget - Basic Usage" />
## [Feature Section(s)]
Document widget-specific features with focused examples.
Store code snippets as external `.cs` files and import them with `?raw`:
```markdown
<script setup>
import featureSnippet from './snippets/widget-feature.cs?raw'
</script>
<StaticTerminalPreview svgPath="/svg/widget-feature.svg" :code="featureSnippet" />
##### Mirror Warning for Code Samples
When a documentation code sample mirrors a WebSocket example, add a warning comment to remind maintainers to keep them in sync:
```markdown
<!--
⚠️ MIRROR WARNING: This code sample mirrors the WebSocket example in:
src/Hex1b.Website/Examples/WidgetBasicExample.cs
Keep both files in sync when making changes.
-->
Place this comment directly before the code block or <script setup> section.
For static previews with interactive cell inspection, use the Static Generator to create SVG and HTML files. These offer:
A template console application is provided at:
.github/skills/doc-writer/static-generator/
├── StaticGenerator.csproj # Project file (references Hex1b)
├── Program.cs # Template with GenerateSnapshot helper
└── README.md # Detailed usage instructions
IMPORTANT: Do NOT modify the template files directly. Always copy to a temporary location first.
Step 1: Create working directory inside workspace
mkdir -p .tmp-static-gen
cp .github/skills/doc-writer/static-generator/* .tmp-static-gen/
Step 2: Fix the project reference path
The template uses a relative path for the original location. Update the project reference in .tmp-static-gen/StaticGenerator.csproj:
# Change the ProjectReference Include path from:
# Include="../../../../src/Hex1b/Hex1b.csproj"
# To:
# Include="../src/Hex1b/Hex1b.csproj"
Step 3: Modify Program.cs in the working copy
Edit .tmp-static-gen/Program.cs and replace the contents of GenerateSnapshots() with your specific widget:
static async Task GenerateSnapshots(string outputDir)
{
// Example: Generate a text widget with wrapping
await GenerateSnapshot(outputDir, "text-wrap-demo", "Text Wrapping Demo", 50, 5,
ctx => ctx.VStack(v => [
v.Text(
"This long paragraph demonstrates how text wrapping works in Hex1b. " +
"When content exceeds the available width, it automatically breaks " +
"at word boundaries."
).Wrap()
]));
}
Step 4: Run the generator
cd .tmp-static-gen
dotnet run -- output
Step 5: Verify the output
Check that the files were created:
ls -la output/
# Should contain: text-wrap-demo.svg
Step 6: Copy to public assets
cp output/*.svg ../src/content/public/svg/
Step 7: Clean up
cd ..
rm -rf .tmp-static-gen
Step 8: Use in documentation
Create a snippets folder alongside your markdown and add the code as a .cs file:
guide/widgets/
├── your-widget.md
└── snippets/
└── text-wrap-demo.cs
Then import and use it in your markdown:
<script setup>
import wrapSnippet from './snippets/text-wrap-demo.cs?raw'
</script>
<StaticTerminalPreview svgPath="/svg/text-wrap-demo.svg" :code="wrapSnippet" />
await GenerateSnapshot(
outputDir, // Always use "output"
"filename", // Name without extension (creates .svg)
"Description", // For console output only
width, // Terminal columns
height, // Terminal rows
ctx => widget // Widget builder using RootContext
);
The <StaticTerminalPreview> component displays code with an expandable "View output" panel. Clicking the chevron icon slides open a panel showing the rendered terminal output.
This component uses Shiki for syntax highlighting with line numbers, matching the styling of the main <CodeBlock> component.
Usage (recommended - external .cs files with ?raw import):
Store code snippets as external .cs files in a snippets/ folder alongside your markdown, then import them using Vite's ?raw suffix:
guide/widgets/
├── text.md
└── snippets/
├── text-truncate.cs
├── text-wrap.cs
└── text-ellipsis.cs
<script setup>
import truncateSnippet from './snippets/text-truncate.cs?raw'
import wrapSnippet from './snippets/text-wrap.cs?raw'
import ellipsisSnippet from './snippets/text-ellipsis.cs?raw'
</script>
<StaticTerminalPreview svgPath="/svg/text-truncate.svg" :code="truncateSnippet" />
Benefits of external files:
.cs files with proper syntax highlighting in your editorProps:
| Prop | Type | Default | Description |
|---|---|---|---|
svgPath | string | required | Path to .svg file relative to public folder |
code | string | (from slot) | Code to display (use with ?raw imports) |
language | string | "csharp" | Language for syntax highlighting |
Features:
Note: The component parses cell data directly from the SVG's DOM structure (using data-x, data-y attributes and element properties). No separate HTML file is needed.
| Option | Default | Description |
|---|---|---|
ShowCellGrid | false | Show grid lines between cells |
ShowPixelGrid | false | Show pixel-level grid |
DefaultBackground | #0f0f1a | Background color |
DefaultForeground | #e0e0e0 | Text color |
FontFamily | Cascadia Code stack | Monospace font |
FontSize | 14 | Font size in pixels |
CellWidth | 9 | Cell width in pixels |
CellHeight | 18 | Cell height in pixels |
IMPORTANT: Unless your code sample specifically demonstrates cursor/mouse behavior, disable the cursor in generated SVGs. A random cursor block appearing in documentation screenshots confuses readers.
The Hex1bTerminalSnapshot.ToSvg() method includes cursor position by default. To hide the cursor, cast to IHex1bTerminalRegion:
// Cast to IHex1bTerminalRegion to use the overload without cursor
var svg = ((IHex1bTerminalRegion)snapshot).ToSvg(svgOptions);
The template's GenerateSnapshot function already uses this approach by default.
Only include the cursor when documenting cursor-related features (e.g., TextBox focus states, cursor shapes).
// Simple text
await GenerateSnapshot(outputDir, "text-simple", "Simple", 40, 3,
ctx => ctx.Text("Hello, World!"));
// Text with overflow
await GenerateSnapshot(outputDir, "text-ellipsis", "Ellipsis", 50, 3,
ctx => ctx.Text("Very long text here...").Ellipsis().FixedWidth(30));
// VStack layout
await GenerateSnapshot(outputDir, "layout-vstack", "VStack", 60, 10,
ctx => ctx.VStack(v => [
v.Text("Header"),
v.Text(""),
v.Text("Body content")
]));
// Border with content
await GenerateSnapshot(outputDir, "border-demo", "Border", 40, 6,
ctx => ctx.Border(b => [
b.Text("Content inside border")
], title: "Title"));
Write XML docs as you write the code
<summary>, <param>, <returns>, <remarks><example> for non-obvious usageCreate or update end-user guides
guide/widgets/getting-started.md if appropriateCheck if documentation needs updates
Update affected documentation
Before considering documentation complete:
<summary> tags<see> and <seealso> tagsCRITICAL: Before completing documentation work, verify that the VitePress content build succeeds. This catches issues like dead links, missing files, or syntax errors before deployment.
Run the Aspire content build pipeline:
cd /path/to/hex1b
aspire do build-content
This executes the deployment pipeline step that builds the static resources from the VitePress site. The build must succeed before documentation changes are complete.
Expected output on success:
✓ PIPELINE SUCCEEDED
If the build fails, proceed to the diagnostic step below.
If aspire do build-content fails, run the underlying npm build command directly to see the detailed error message:
cd src/content
npm install # Ensure dependencies are installed
npm run build
This is the step most likely to fail inside aspire do build-content. The npm build output provides detailed error messages explaining:
Common issues to fix:
snippets/ folders<script setup> sectionsIf you see an error like:
Found dead link /guide/widgets/nonexistent in file guide/widgets/mywidget.md
Either:
After fixing issues, re-run both verification commands to ensure the build succeeds.
❌ Don't repeat the member name in the summary
/// <summary>
/// ButtonWidget is a widget for buttons.
/// </summary>
✅ Do describe what it does
/// <summary>
/// An interactive button that responds to Enter, Space, or mouse clicks.
/// </summary>
❌ Don't use implementation details in public API docs
/// <summary>
/// Sets the internal _label field to the specified value.
/// </summary>
✅ Do describe the effect
/// <summary>
/// Sets the text displayed on the button.
/// </summary>
❌ Don't assume prior knowledge
Use reconciliation to update nodes efficiently.
✅ Do explain concepts
When you call `SetState()`, Hex1b diffs the new widget tree against existing nodes
and updates only what changed. This reconciliation process preserves focus and scroll
state while keeping your UI responsive.
❌ Don't use code snippets without context
.OnClick(_ => count++)
✅ Do provide complete, runnable examples
var state = new CounterState();
var app = new Hex1bApp(ctx =>
ctx.Button("Increment").OnClick(_ => state.Count++)
);
dotnet tool install -g xmldocmd)src/content/.vitepress/)Good documentation:
When in doubt, look at existing documentation in the repository and match its style, tone, and level of detail.
Guidelines for reviewing API design in the Hex1b codebase. Use when evaluating public APIs, reviewing accessibility modifiers, or assessing whether new APIs follow project conventions.
Step-by-step guide for creating new widgets in the Hex1b TUI library. Use when implementing new widgets from scratch, including widget records, nodes, extension methods, theming, reconciliation, and tests.
Agent for diagnosing and fixing flaky terminal UI tests in the Hex1b test suite. Use when tests pass locally but fail in CI, or when tests exhibit timing-sensitive behavior.
Guidelines for writing unit tests in the Hex1b TUI library. Use when creating new tests for widgets, nodes, or terminal functionality.
Agent for validating Hex1b documentation against actual library behavior. Use when auditing documentation accuracy, testing interactive examples, or identifying discrepancies between documentation and implementation.
Guidelines for running and interpreting Surface API performance benchmarks. Use when modifying code in src/Hex1b/Surfaces/ to ensure performance is not regressed.