agentleFS
mitchdennymitchdenny/hex1b/AGENTS.md

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
  1. Distinctive:

    Avoid having two builder callbacks on one method.

    line 106
  2. Very specific:

    When widgets define input bindings in ConfigureDefaultBindings, use Triggers(ActionId, handler, desc) instead of Action(handler, desc) to …

    line 143
  3. Very specific:

    Create XxxWidget record in src/Hex1b/Widgets/

    line 167
  4. Very specific:

    Create XxxNode class in src/Hex1b/Nodes/

    line 168
  5. Very specific:

    Add extension methods in src/Hex1b/XxxExtensions.cs

    line 169
  6. Very specific:

    Add theme elements in src/Hex1b/Theming/XxxTheme.cs

    line 170
  7. Very specific:

    Write tests in tests/Hex1b.Tests/XxxNodeTests.cs

    line 171
  8. Very specific:

    Follow the MethodName_Scenario_ExpectedBehavior naming pattern:

    line 177
  9. Very specific:

    Use Aspire MCP tools (list_resources, list_structured_logs, etc.) for debugging

    line 227
  10. Distinctive:

    Avoid persistent containers early in development

    line 228
  11. Distinctive:

    No mouse support currently (keyboard-only navigation)

    line 233
  12. Distinctive:

    Create node directly, set properties, verify behavior

    line 281
  13. Very specific:

    Use Hex1bKeyEvent to simulate input

    line 282
  14. Very specific:

    Check measured size after Measure()

    line 283
  15. Very specific:

    Use Hex1bApp with Hex1bAppWorkloadAdapter

    line 286
  16. Distinctive:

    Test full widget → node → render cycle

    line 287
  17. Distinctive:

    When asking questions about this codebase:

    line 292

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

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.

SkillWhen to Use
widget-creatorCreating new widgets (widget records, nodes, theming, tests)
writing-unit-testsWriting unit tests for widgets, nodes, or terminal functionality
test-fixerDiagnosing flaky tests, especially timing-related failures in CI
api-reviewerReviewing API design, accessibility modifiers, and naming conventions
doc-writerWriting XML API documentation or end-user guides
doc-testerValidating documentation accuracy against library behavior
surface-benchmarkerRunning performance benchmarks after modifying src/Hex1b/Surfaces/
aspireWorking 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):

ConceptLocationMutabilityPurpose
Widgetsrc/Hex1b/Widgets/ImmutableDescribes what to render
Nodesrc/Hex1b/Nodes/MutableManages 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 bounds
  • Size - Measured dimensions
  • Rect - Position and size for arrangement
  • SizeHint - 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:

IDRuleNotes
HEX1B0001Widget extension/instance methods must not start with WithWith* is reserved for Hex1bTerminalBuilder. Widgets use On* for handlers and bare verb-noun names (Title("..."), MaxFloating(3)) for configuration.
HEX1B0002Types deriving from Hex1bWidget must end with Widget
HEX1B0003Types deriving from Hex1bNode must end with Node
HEX1B0004Types deriving from Hex1bWidget must be declared as record
HEX1B0005Types deriving from Hex1bNode must be declared as class
HEX1B0006Receiver of widget extension on WidgetContext<T> must be named contextFlutter-style — keeps the receiver name predictable across every context.Border(...)-style call.
HEX1B0007Receiver of widget instance extension must be named widgetApplies when the receiver is Hex1bWidget-derived (or a generic constrained to it). WidgetContext<T> receivers are governed by HEX1B0006.
HEX1B0008A 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.
HEX1B0009A widget extension/instance method should declare at most one widget-builder callback parameterMulti-builder shapes (e.g. HSplitter(left, right)) require explicit [SuppressMessage("Hex1b.ApiDesign", "HEX1B0009")]. Same On* exclusion as HEX1B0008.
HEX1B0010Widgets must not override both Build and ReconcileAsyncChoose the compositional or primitive path.
HEX1B0011One distinct top-level type per fileEnabled 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 for Hex1bTerminalBuilder. Use On* 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 … or where 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-creator skill for comprehensive step-by-step guidance including templates, theming, and test patterns.

Quick checklist:

  1. Create XxxWidget record in src/Hex1b/Widgets/
  2. Create XxxNode class in src/Hex1b/Nodes/
  3. Add extension methods in src/Hex1b/XxxExtensions.cs
  4. Add theme elements in src/Hex1b/Theming/XxxTheme.cs
  5. Write tests in tests/Hex1b.Tests/XxxNodeTests.cs

Test Conventions

📘 Use the test-fixer skill 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 build

Running Tests

dotnet test

Running a Sample

dotnet run --project samples/Cancellation

🚀 .NET Aspire

📘 Use the aspire skill 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 packages

Key Points

  • Changes to apphost.cs require 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 → focused Node
  • InputBindings are checked before standard input handling
  • Return InputResult.Handled from HandleInput if 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

FilePurpose
src/Hex1b/Hex1bApp.csMain entry point, reconciliation, render loop
src/Hex1b/Hex1bRenderContext.csTerminal rendering abstraction
src/Hex1b/IHex1bTerminal.csTerminal interface (mockable for tests)
src/Hex1b/Nodes/Hex1bNode.csBase class for all nodes
src/Hex1b/Widgets/Hex1bWidget.csBase class for all widgets
src/Hex1b/Layout/Constraints.csLayout constraint system

🧪 Testing

Running Tests

dotnet test

Unit Testing Nodes

  • Create node directly, set properties, verify behavior
  • Use Hex1bKeyEvent to simulate input
  • Check measured size after Measure()

Integration Testing

  • Use Hex1bApp with Hex1bAppWorkloadAdapter
  • Test full widget → node → render cycle
  • See tests/Hex1b.Tests/ for examples

💬 Asking for Help

When asking questions about this codebase:

  1. Reference specific file paths
  2. Mention whether you're working with widgets or nodes
  3. Include relevant test context if debugging
  4. Note any Aspire-specific requirements

This file is intended for AI coding agents. Humans should refer to CONTRIBUTING.md for contribution guidelines.

Download now (ZIP)

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.