| name | api-reviewer |
| description | 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. |
API Reviewer Skill
This skill captures API design preferences for the Hex1b codebase. Use it when reviewing public APIs, evaluating whether code should be public or internal, or assessing API design decisions made by AI agents.
Codebase Architecture
Hex1b is structured into distinct layers, each with different API design considerations:
Layer 1: Hex1bTerminal (Core)
The foundational layer maintaining an in-memory representation of terminal state.
| Component | Purpose | Accessibility |
|---|
Hex1bTerminal | Core terminal state management | Public |
Hex1bTerminalBuilder | Fluent terminal construction | Public |
| Presentation adapters | Output to real terminals, tests, web | Public interfaces |
| Workload adapters | Input/output abstraction | Public interfaces |
Surface, SurfaceCell | Low-level cell storage | Public (low-level API) |
Design note: Hex1bTerminal emerged from the need to properly test TUI components. The adapter pattern enables use in real terminals, unit tests, or projecting state across networks.
Layer 2: Hex1bApp/TUI
The widget/node tree layer, inspired by React/Ink but not beholden to it.
| Component | Purpose | Accessibility |
|---|
Hex1bApp | Root of TUI applications | Public |
*Widget records | Declarative UI configuration | Public |
*Node classes | DOM-like render elements | Generally public (for extensibility) |
| Reconciliation logic | Widget→Node synchronization | Internal |
| Layout algorithms | Measure/arrange implementation | Internal |
Layer 3: Automation
Testing and automation infrastructure.
| Component | Purpose | Accessibility |
|---|
Hex1bTerminalInputSequenceBuilder | Fluent input automation | Public |
CellPatternSearcher | Visual assertion matching | Public |
Hex1bTerminalSnapshot | Captured terminal state | Public |
Design note: Automation APIs are explicitly for external consumers, not just internal testing.
Public vs Internal Decision Framework
When reviewing accessibility modifiers, apply this framework:
Make it PUBLIC when:
- External consumers need it - Adapters, extension points, widget customization
- It's part of the conceptual API - Users think in terms of this abstraction
- It enables extensibility - Custom widgets, themes, adapters
- It's stable - You're confident the design won't need breaking changes
Make it INTERNAL when:
- It's implementation detail - Only Hex1b itself (or agents working on it) would invoke it
- The abstraction isn't proven - Uncertain if this is the right level of abstraction
- It could change - Design may evolve with more usage
- It exposes internals - Would leak implementation details to consumers
Make it PRIVATE when:
- Single class usage - Only used within one type
- Helper methods - Implementation helpers with no external meaning
Review Questions
When reviewing agent-generated code, ask:
API Design Patterns
Builders: Classic With...() Pattern
For Hex1bTerminalBuilder and similar construction scenarios:
var terminal = Hex1bTerminal.CreateBuilder()
.WithWorkload(workload)
.WithPresentation(presentation)
.WithDimensions(80, 24)
.WithHeadless()
.Build();
Widgets: SwiftUI-Inspired Minimal Constructors
For widget APIs, use minimal constructors with essential arguments only, then fluent extension methods:
ctx.Table(columns, rows)
.SelectedRow(selectedIndex)
.OnSelectionChanged(HandleSelection)
ctx.Progress(value, max).Fill()
new TableWidget(columns, rows, selectedIndex, onSelectionChanged, sortColumn, sortDirection, ...)
Guideline: Only include the most essential arguments in the constructor. Make usage "bleeding obvious." Use extension methods to splice in extra behavior.
Stateful Widgets: Lift-State-Up via IStatefulWidget<TSelf, TState>
When a widget owns non-trivial mutable user-facing state (a textbox's buffer + cursor, an editor's document, a navigator's selection, a checkbox's checked value), expose that state to callers so composites can drive the widget from outside.
The framework-wide contract is IStatefulWidget<TSelf, TState>:
public sealed record TextBoxWidget(...)
: Hex1bWidget, IStatefulWidget<TextBoxWidget, TextBoxState>
{
internal TextBoxState? InjectedState { get; init; }
public TextBoxWidget State(TextBoxState state) => this with { InjectedState = state };
}
Callers then own the state via ctx.UseState(...) and bind it:
var state = ctx.UseState(() => new TextBoxState());
return ctx.TextBox().State(state);
When to apply:
- ✅ Mutable state that a parent might read or write (buffer text, selection, history).
- ✅ Multi-instance widgets that need independent state (two textboxes, two editors) — wrap in a single state class with explicit fields, no keyed
UseState needed.
- ❌ Pure visual/transient state (focus, hover, animation phase) — keep that internal to the node.
Rules:
- The state type must be a class (
where TState : class) so external mutations are observed.
- Reference equality matters —
.State(s) must route the same instance into the node every reconcile.
- If a widget supports both a ctor-arg "initial value" and
.State(...), conflicting use must throw InvalidOperationException on reconcile (no precedence rules to memorise).
- Method name is
State(...) — the analyzer (HEX1B0001) forbids With* on widget extension methods.
See TextBoxWidget/TextBoxState, EditorWidget/EditorState, NavigatorWidget/NavigatorState, CheckboxWidget/CheckboxState for the canonical patterns.
Options Types for Complex Configuration
When you start creating many overloads, use an options type:
public class Hex1bAppOptions
{
public Hex1bTheme? Theme { get; init; }
public IHex1bTerminalWorkloadAdapter? WorkloadAdapter { get; init; }
}
public Hex1bApp(Func<...> builder) { }
public Hex1bApp(Func<...> builder, Hex1bTheme theme) { }
public Hex1bApp(Func<...> builder, Hex1bTheme theme, IWorkloadAdapter adapter) { }
But: Don't have options classes everywhere. Be conservative with what you require.
Async Patterns
Prefer Task, Use ValueTask for Performance
public Task<Hex1bWidget> BuildAsync(WidgetContext ctx);
public ValueTask HandleInputAsync(Hex1bKeyEvent key);
Sync + Async Overloads for Callbacks
Provide both sync and async overloads for event handlers. Implement everything assuming async is possible, then wrap sync handlers:
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 };
Note: Samples often favor sync versions for simplicity.
Naming and Nullability
Types Over Magic Strings
public void SetColor(Hex1bColor color);
public void SetColor(string colorName);
Avoid Forcing Null Arguments
public void Configure(string? requiredArg, string? optionalArg);
Configure("value", null);
public void Configure(string requiredArg);
public void Configure(string requiredArg, string optionalArg);
Avoid Primitive Obsession
Be wary of using too many primitive types, as it makes creating overloads harder in the future:
public void SetPosition(int x, int y);
public void SetPosition(Point position);
Extension Methods
Use extension methods when:
- They don't need internal access - Can work with public API surface
- They represent cross-cutting concerns - e.g.,
.Fill(), .FixedWidth()
- They add optional behavior - Not core to the type's identity
Use instance methods when:
- They need internal state - Avoiding would leak implementation details
- They're core to the type - Essential behavior, not optional configuration
Namespace Design
Discoverability First
Minimize the namespaces developers need to know:
using Hex1b;
using Hex1b.Theming;
using Hex1b.Input;
Guideline: Ideally, most developers just need using Hex1b; and discover other types through methods on types in that namespace.
Documentation Standards
📘 See the doc-writer skill for comprehensive documentation guidelines including XML API docs and end-user guides.
XML Documentation Requirements
All public APIs must have XML documentation:
public sealed record ButtonWidget(string Label) : Hex1bWidget
Documentation Guidelines
| Element | Guideline |
|---|
<summary> | Concise and accurate - what it does, not how |
<remarks> | Detailed, useful from end-user perspective, no irrelevant internals |
<param> | Required for all parameters |
<returns> | Required for non-void methods |
<example> | Complete, runnable mini-apps when possible |
<code> | Cut-and-paste ready, not just method invocation |
Anti-pattern: Examples that just show invoking the method without context. Always provide complete, runnable examples.
Error Handling
Exceptions for Irrecoverable Errors
Throw exceptions when something is truly irrecoverable:
throw new ArgumentNullException(nameof(widget));
throw new InvalidOperationException("Cannot render before Measure()");
throw new Hex1bRenderException(widget, node, phase, "Render failed", innerException);
Guideline: Think about what information developers need to debug. Attach widget/node/phase information when it helps.
Note: The RescueWidget exists to handle and display errors gracefully in the UI.
Code Organization
One Type Per File
Each type should be in its own file. This makes it easier to:
- Review changes
- Track modifications
- Support concurrent agent work
Avoid Statics
private static readonly Dictionary<string, Widget> _cache = new();
private readonly Dictionary<string, Widget> _cache = new();
Rationale: AI agents tend to use statics, which introduce subtle testability bugs.
Constants Are Fine
public const int DefaultWidth = 80;
public const int DefaultHeight = 24;
Review Checklist
When reviewing APIs, check:
Accessibility
Design
Patterns
Naming & Type Shape (enforced by analyzer)
These rules are enforced at build time by the Hex1b.Analyzers Roslyn analyzer wired into every project via the root Directory.Build.props. See AGENTS.md § "Code Analysis Rules" for the full table. If a flagged case is intentional and cannot follow the rule, suppress with #pragma warning disable HEX1B000x rather than disabling the analyzer wholesale.
Documentation
Organization
Anti-Patterns to Flag
Overly Public Agent-Generated Code
AI agents often make things public by default. Flag for review:
public void ReconcileChildNodes(List<Hex1bNode> children) { }
public int CalculateInternalLayoutOffset() { }
Too Many Overloads
When you see many overloads, suggest an options type:
public Table(columns);
public Table(columns, selectedRow);
public Table(columns, selectedRow, sortColumn);
public Table(columns, selectedRow, sortColumn, sortDirection);
Magic Strings
Flag string parameters that should be types:
public void SetAlignment(string alignment);
public void SetAlignment(Alignment alignment);
Incomplete Documentation
Flag public APIs without proper documentation:
public Hex1bWidget CreateWidget(WidgetContext ctx);