Use when a Markout report must adapt to its data — show or hide whole sections, drop table columns that are empty/uniform, filter output to a chosen set of sections, or render one model into several shapes ("one model, many views"). This is Markout's highest-value idiom: declare the conditions with attributes instead of hand-writing if/else + StringBuilder. Don't decompile the assembly or web-search the API — the conditional idioms are here.
Scanned 9/1/2026
Install to Claude Code
npx -y skills add richlander/markout --skill markout-conditional-composition --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Markout Conditional Composition?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/richlander-markout-conditional-composition)More formats (shields.io, HTML) on the badges page.
---
name: markout-conditional-composition
version: 0.35.2
description: >-
Use when a Markout report must adapt to its data — show or hide whole sections, drop table
columns that are empty/uniform, filter output to a chosen set of sections, or render one
model into several shapes ("one model, many views"). This is Markout's highest-value idiom:
declare the conditions with attributes instead of hand-writing if/else + StringBuilder.
Don't decompile the assembly or web-search the API — the conditional idioms are here.
---
# Conditional composition — one model, many views
The trap this skill prevents: hand-rolling `if (hasErrors) sb.AppendLine("## Errors")` and manual
column arithmetic. In Markout you **declare** the condition on the model; the generator renders the
right shape. This keeps one source of truth and makes the same model serve quiet/detail/export modes.
## Required setup
Markout has **no reflection fallback**. Every report needs an annotated model, a partial context
registering each model type, and a `Serialize` call that passes it — there is no `Serialize(obj)`
overload, and omitting the context does not compile.
```csharp
using Markout;
[MarkoutContext(typeof(Inspection))] // the model AND every user element type of a List<T>
[MarkoutContext(typeof(FailureRow))]
[MarkoutContext(typeof(WarnRow))]
[MarkoutContext(typeof(MatchRow))]
public partial class InspectionContext : MarkoutSerializerContext { }
var ctx = InspectionContext.Default; // the `ctx` passed in the examples below
```
## Conditional sections
```csharp
[MarkoutSerializable(TitleProperty = nameof(Name))]
public class Inspection
{
public string Name { get; set; } = "";
// Section renders ONLY when HasFailures is true. Compute the predicate on the model.
[MarkoutSection(Name = "Failures", ShowWhenProperty = nameof(HasFailures))]
public List<FailureRow>? Failures { get; set; }
public bool HasFailures => Failures is { Count: > 0 };
// EmptyText shows a fallback paragraph when the list is non-null but empty; null omits the section.
[MarkoutSection(Name = "Warnings", EmptyText = "No warnings.")]
public List<WarnRow>? Warnings { get; set; }
}
```
- `ShowWhenProperty = nameof(Bool)` gates a **section** on a bool property.
- `[MarkoutShowWhen(nameof(Bool))]` gates a **scalar field** the same way.
- `[MarkoutSkipNull]` / `[MarkoutSkipDefault]` drop individual fields when null/default.
## Adaptive columns — hide what carries no information
```csharp
// Drop the "Pattern" column when it's uniform/empty across the rows (keeps tables compact).
// The attribute goes on the SECTION list and names a static bool predicate + the column to hide.
[MarkoutSection(Name = "Matches")]
[MarkoutIgnoreColumnWhen(nameof(PatternIsUniform), "Pattern")]
public List<MatchRow>? Matches { get; set; }
public static bool PatternIsUniform(List<MatchRow>? rows)
=> rows?.Select(r => r.Pattern).Distinct().Count() <= 1;
// IgnoreProperty hides named columns unconditionally.
[MarkoutSection(Name = "Debug", IgnoreProperty = "InternalId,Debug")]
public List<MatchRow>? DebugRows { get; set; }
```
`[MarkoutIgnoreColumnWhen(...)]` is the declarative replacement for "compute distinct values, then
rebuild headers and rows." Columns hidden this way are also dropped from TSV/JSONL decomposition.
## Filter to specific sections at render time
```csharp
var options = new MarkoutWriterOptions { IncludeSections = new HashSet<string> { "Failures" } };
MarkoutSerializer.Serialize(report, Console.Out, new MarkdownFormatter(), ctx, options);
```
`IncludeSections` renders only the named sections — the caller-side lever for "just show me X"
without a second model, and the declarative way to drive quiet/detail/verbosity views from one model.
## One model, many shapes: same-name section variants
When a mode needs a different projection of the same logical section (e.g. terse vs with-docs),
declare multiple properties with the **same** `Name` and gate them so exactly one renders:
```csharp
[MarkoutSection(Name = "Members", ShowWhenProperty = nameof(Terse))]
public List<MemberRow>? MembersTerse { get; set; }
[MarkoutSection(Name = "Members", ShowWhenProperty = nameof(WithDocs))]
public List<MemberDocRow>? MembersWithDocs { get; set; }
```
This "same-name polymorphic section" is how a single report serves `--docs`, quiet, and select
modes without branching in the writer. Set the gating bools when you build the model.
## Guardrails
- Prefer declaration over imperative assembly: no `if`+`AppendLine`, no manual header/row rebuilding.
- Keep predicates (`Has*`, `Terse`) as computed properties on the model, next to the data.
- For same-name section variants, use mutually exclusive `ShowWhenProperty` gates rather than
relying on empty-list omission; the gates make the selected projection explicit.
- Empty vs absent matters: `null` list omits a section; empty list + `EmptyText` shows the fallback.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!