hex1b
AGENTS.md181 starsChanged 7 months agoMITView on GitHub
This document provides context and conventions for AI coding agents (GitHub Copilot, Claude, Cursor, etc.) working with the Hex1b codebase. This repository includes specialized skills in .github/skills/ that provide detailed guidance for specific tasks. Invoke these skills when working on related tasks - they contain step-by-step procedures, templates, and best practices. Skills are invoked automatically by AI agents based on the task context. They contain comprehensive procedures that complement the high-level guidance in this file. Hex1b is a .NET library…
We split this AGENTS.md into the rules it gives an agent
17 rules. About 0 are common, 6 are distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 106
Avoid having two builder callbacks on one method.
- Very specific: line 143
When widgets define input bindings in ConfigureDefaultBindings, use Triggers(ActionId, handler, desc) instead of Action(handler, desc) to …
- Very specific: line 167
Create XxxWidget record in src/Hex1b/Widgets/
- Very specific: line 168
Create XxxNode class in src/Hex1b/Nodes/
- Very specific: line 169
Add extension methods in src/Hex1b/XxxExtensions.cs
- Very specific: line 170
Add theme elements in src/Hex1b/Theming/XxxTheme.cs
- Very specific: line 171
Write tests in tests/Hex1b.Tests/XxxNodeTests.cs
- Very specific: line 177
Follow the MethodName_Scenario_ExpectedBehavior naming pattern:
- Very specific: line 227
Use Aspire MCP tools (list_resources, list_structured_logs, etc.) for debugging
- Distinctive: line 228
Avoid persistent containers early in development
- Distinctive: line 233
No mouse support currently (keyboard-only navigation)
- Distinctive: line 281
Create node directly, set properties, verify behavior
- Very specific: line 282
Use Hex1bKeyEvent to simulate input
- Very specific: line 283
Check measured size after Measure()
- Very specific: line 286
Use Hex1bApp with Hex1bAppWorkloadAdapter
- Distinctive: line 287
Test full widget → node → render cycle
- Distinctive: line 292
When asking questions about this codebase:
≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AI Coding Agent Guidelines for Hex1b
This document provides context and conventions for AI coding agents (GitHub Copilot, Claude, Cursor, etc.) working with the Hex1b codebase.
🛠️ Available Skills
This repository includes specialized skills in .github/skills/ that provide detailed guidance for specific tasks. Invoke these skills when working on related tasks - they contain step-by-step procedures, templates, and best practices.
| Skill | When to Use |
| widget-creator | Creating new widgets (widget records, nodes, theming, tests) |
| writing-unit-tests | Writing unit tests for widgets, nodes, or terminal functionality |
| test-fixer | Diagnosing flaky tests, especially timing-related failures in CI |
| api-reviewer | Reviewing API design, accessibility modifiers, and naming conventions |
| doc-writer | Writing XML API documentation or end-user guides |
| doc-tester | Validating documentation accuracy against library behavior |
| surface-benchmarker | Running performance benchmarks after modifying src/Hex1b/Surfaces/ |
| aspire | Working with .NET Aspire (running samples, debugging, MCP tools) |
Skills are invoked automatically by AI agents based on the task context. They contain comprehensive procedures that complement the high-level guidance in this file.
📋 Project Overview
Hex1b is a .NET library for building terminal user interfaces (TUI) with a React-inspired declarative API. The library ships to NuGet as Hex1b.
Key Technologies
- .NET 10.0 (preview) - Target framework
- C# 12+ - Modern C# features enabled
- MSTest (MSTest.Sdk 4.x with Microsoft.Testing.Platform) - Testing framework
- .NET Aspire - Used for running sample applications
Repository Layout
src/Hex1b/ → Main library (THE shipped package)
tests/Hex1b.Tests/ → Unit tests
samples/ → Example applications
apphost.cs → Aspire app host for samples🏗️ Architecture Concepts
Widget/Node Pattern
Hex1b separates configuration (widgets) from rendering (nodes):
| Concept | Location | Mutability | Purpose |
| Widget | src/Hex1b/Widgets/ | Immutable | Describes what to render |
| Node | src/Hex1b/Nodes/ | Mutable | Manages state, handles input, renders |
Important: Widgets are record types; Nodes are class types with mutable properties.
Reconciliation
The Hex1bApp.Reconcile() method diffs widgets against nodes:
- Same widget type → Update existing node's properties
- Different widget type → Create new node
- This preserves state (focus, cursor position) across re-renders
Layout System
Located in src/Hex1b/Layout/:
Constraints- Min/max width/height boundsSize- Measured dimensionsRect- Position and size for arrangementSizeHint- Fill, Content, or Fixed sizing
Render Loop (in Hex1bApp.cs)
Build widgets → Reconcile → Measure → Arrange → Render → Wait for input → Repeat📝 Code Conventions
Code Analysis Rules
This repo ships a Roslyn analyzer (src/Hex1b.Analyzers/) that runs against every project in the solution (lib, samples, tests, benchmarks) but is not packaged into the public Hex1b NuGet package. It enforces:
| ID | Rule | Notes |
| HEX1B0001 | Widget extension/instance methods must not start with With | With* is reserved for Hex1bTerminalBuilder. Widgets use On* for handlers and bare verb-noun names (Title("..."), MaxFloating(3)) for configuration. |
| HEX1B0002 | Types deriving from Hex1bWidget must end with Widget | |
| HEX1B0003 | Types deriving from Hex1bNode must end with Node | |
| HEX1B0004 | Types deriving from Hex1bWidget must be declared as record | |
| HEX1B0005 | Types deriving from Hex1bNode must be declared as class | |
| HEX1B0006 | Receiver of widget extension on WidgetContext<T> must be named context | Flutter-style — keeps the receiver name predictable across every context.Border(...)-style call. |
| HEX1B0007 | Receiver of widget instance extension must be named widget | Applies when the receiver is Hex1bWidget-derived (or a generic constrained to it). WidgetContext<T> receivers are governed by HEX1B0006. |
| HEX1B0008 | A single widget-builder callback parameter must be named builder | "Widget-builder callback" = Func<…, Hex1bWidget> (or array / IEnumerable<> / derived widget). On[A-Z]… event-handler methods are excluded. |
| HEX1B0009 | A widget extension/instance method should declare at most one widget-builder callback parameter | Multi-builder shapes (e.g. HSplitter(left, right)) require explicit [SuppressMessage("Hex1b.ApiDesign", "HEX1B0009")]. Same On* exclusion as HEX1B0008. |
| HEX1B0010 | Widgets must not override both Build and ReconcileAsync | Choose the compositional or primitive path. |
| HEX1B0011 | One distinct top-level type per file | Enabled as an error only under src/Hex1b/. Includes classes, records, structs, interfaces, enums, and delegates. Nested types and generated files are exempt; partial declarations of the same type are allowed. |
The API-design rules default to Warning severity and the lib builds under TreatWarningsAsErrors=true. HEX1B0011 is disabled by default and enabled via src/Hex1b/.editorconfig, so tests, samples, and other projects are unaffected. None of these analyzers are shipped to package consumers. Suppressions exist only where they encode a deliberate API decision (HEX1B0009 on Splitter's two-pane shape).
Tests for the analyzers live in tests/Hex1b.Analyzers.Tests/. New rules should follow the existing pattern: implement in src/Hex1b.Analyzers/, declare the ID in Hex1bDiagnosticIds.cs and AnalyzerReleases.Unshipped.md, and add positive + negative tests.
Naming
- Widgets:
*Widget(e.g.,TextBlockWidget,ButtonWidget) — enforced by HEX1B0002/HEX1B0004. - Nodes:
*Node(e.g.,TextBlockNode,ButtonNode) — enforced by HEX1B0003/HEX1B0005. - State objects:
*State(e.g.,TextBoxState) - for widgets requiring user-owned mutable state - Widget configuration methods: never
With*— that prefix is reserved forHex1bTerminalBuilder. UseOn*for event handlers and verb-noun for properties (e.g.,Title,MaxFloating,Disabled). Enforced by HEX1B0001. - Widget extension method parameters (Flutter-style):
WidgetContext<T>receiver →context(HEX1B0006)- Widget instance receiver (
this XxxWidget …orwhere TWidget : Hex1bWidget) →widget(HEX1B0007) - A single widget-builder callback (
Func<…, Hex1bWidget>etc.) →builder(HEX1B0008) - Avoid having two builder callbacks on one method; if genuinely required, suppress HEX1B0009 with a justification (HEX1B0009)
Widget Definition Pattern
// Widgets are records with fluent methods for event handlers
public record ButtonWidget(string Label) : Hex1bWidget
{
internal Func<ButtonClickedEventArgs, Task>? ClickHandler { get; init; }
public ButtonWidget OnClick(Action<ButtonClickedEventArgs> handler)
=> this with { ClickHandler = args => { handler(args); return Task.CompletedTask; } };
public ButtonWidget OnClick(Func<ButtonClickedEventArgs, Task> handler)
=> this with { ClickHandler = handler };
}Node Definition Pattern
public class ButtonNode : Hex1bNode
{
// Properties reconciled from widget
public string Label { get; set; } = "";
public Func<InputBindingActionContext, Task>? ClickAction { get; set; }
// Focus state (mutable, preserved across reconciliation)
public bool IsFocused { get; set; }
// Required overrides
public override void Measure(Constraints constraints) { /* ... */ }
public override void Arrange(Rect rect) { /* ... */ }
public override void Render(Hex1bRenderContext context) { /* ... */ }
}Keybinding Rebinding Pattern
When widgets define input bindings in ConfigureDefaultBindings, use Triggers(ActionId, handler, desc) instead of Action(handler, desc) to make bindings rebindable:
// In Widget record:
public static readonly ActionId MyAction = new($"{nameof(MyWidget)}.{nameof(MyAction)}");
// In Node's ConfigureDefaultBindings:
bindings.Key(Hex1bKey.Enter).Triggers(MyWidget.MyAction, handler, "Description");Users can then remap/alias/disable actions via InputBindings:
widget.InputBindings(b =>
{
b.Remove(MyWidget.MyAction); // remove default key
b.Key(Hex1bKey.X).Triggers(MyWidget.MyAction); // rebind to X
});Adding New Widgets
📘 Use the
widget-creatorskill for comprehensive step-by-step guidance including templates, theming, and test patterns.
Quick checklist:
- Create
XxxWidgetrecord insrc/Hex1b/Widgets/ - Create
XxxNodeclass insrc/Hex1b/Nodes/ - Add extension methods in
src/Hex1b/XxxExtensions.cs - Add theme elements in
src/Hex1b/Theming/XxxTheme.cs - Write tests in
tests/Hex1b.Tests/XxxNodeTests.cs
Test Conventions
📘 Use the
test-fixerskill when tests pass locally but fail in CI, or exhibit timing-sensitive behavior.
Follow the MethodName_Scenario_ExpectedBehavior naming pattern:
[TestMethod]
public void Measure_WithConstraints_ReturnsExpectedSize()
{
// Arrange, Act, Assert
}Test projects use MSTest.Sdk with OutputType=Exe and Microsoft.Testing.Platform (MTP). Use [TestMethod] for facts, [TestMethod] [DataRow(...)] for theories, [TestInitialize]/[TestCleanup] for setup/teardown, and [DoNotParallelize] on a class to opt out of parallel execution. For collection-equality, single-element, and type-cast assertions, prefer the TestSeq helpers in tests/Shared/TestSeq.cs (auto-imported via Hex1b.Testing):
TestSeq.AreEqual(expected, actual); // deep collection equality
var item = TestSeq.Single(items); // assert single + return element
var node = TestSeq.IsType<MyNode>(x); // assert type + return cast value🔧 Common Tasks
Building
dotnet buildRunning Tests
dotnet testRunning a Sample
dotnet run --project samples/Cancellation🚀 .NET Aspire
📘 Use the
aspireskill for detailed Aspire workflows, MCP tools, debugging, and integration guidance.
Aspire orchestrates sample applications. Resources are defined in apphost.cs.
Quick Commands
aspire run # Run the app host
aspire run --detach # Run in background (for agent environments)
aspire stop # Stop running instances
aspire update # Update Aspire packagesKey Points
- Changes to
apphost.csrequire restart - Use Aspire MCP tools (
list_resources,list_structured_logs, etc.) for debugging - Avoid persistent containers early in development
⚠️ Important Constraints
Terminal Limitations
- No mouse support currently (keyboard-only navigation)
- Relies on ANSI escape sequences
- Alternate screen buffer used (
\x1b[?1049h/\x1b[?1049l)
Focus System
- Focusable nodes:
TextBoxNode,ButtonNode,ListNode - Focus navigation: Tab (forward), Shift+Tab (backward), Escape (up to parent)
- Parent containers manage focus among children
Input Handling
- Input flows from
IHex1bTerminal→Hex1bApp→InputRouter→ focusedNode - InputBindings are checked before standard input handling
- Return
InputResult.HandledfromHandleInputif the input was consumed
🎯 When Making Changes
DO:
- ✅ Run tests after changes:
dotnet test - ✅ Follow existing patterns for new widgets/nodes
- ✅ Keep widgets immutable (use
record) - ✅ Preserve node state during reconciliation
- ✅ Use nullable reference types properly
DON'T:
- ❌ Add mutable state to widgets
- ❌ Forget to add reconciliation for new widget types
- ❌ Skip writing tests for new functionality
- ❌ Break the render loop sequence
📚 Key Files to Understand
| File | Purpose |
src/Hex1b/Hex1bApp.cs | Main entry point, reconciliation, render loop |
src/Hex1b/Hex1bRenderContext.cs | Terminal rendering abstraction |
src/Hex1b/IHex1bTerminal.cs | Terminal interface (mockable for tests) |
src/Hex1b/Nodes/Hex1bNode.cs | Base class for all nodes |
src/Hex1b/Widgets/Hex1bWidget.cs | Base class for all widgets |
src/Hex1b/Layout/Constraints.cs | Layout constraint system |
🧪 Testing
Running Tests
dotnet testUnit Testing Nodes
- Create node directly, set properties, verify behavior
- Use
Hex1bKeyEventto simulate input - Check measured size after
Measure()
Integration Testing
- Use
Hex1bAppwithHex1bAppWorkloadAdapter - Test full widget → node → render cycle
- See
tests/Hex1b.Tests/for examples
💬 Asking for Help
When asking questions about this codebase:
- Reference specific file paths
- Mention whether you're working with widgets or nodes
- Include relevant test context if debugging
- Note any Aspire-specific requirements
This file is intended for AI coding agents. Humans should refer to CONTRIBUTING.md for contribution guidelines.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository11 other files it gives its agents.
- aspireSkill
- playwright-cliSkill
- api-reviewerSkill
- aspireSkill
- doc-testerSkill
- doc-writerSkill
- playwright-cliSkill
- surface-benchmarkerSkill
- test-fixerSkill
- widget-creatorSkill
- writing-unit-testsSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

