starterkit-converter-authoring
Skill65 starsChanged 19 days agoMITView on GitHub
How to write a new `IConverter` for Aspid.MVVM StarterKit correctly the first time — choosing a base (`NumberConverter`, `StringToNumberConverter`, `DictionaryLookupConverter`), the numeric and Vector families, `ConvertBack` and the "To" naming rule, `ConverterFallback<T>` and when not to use it, nested `[SerializeReference]` slots, `[UsedInModes]`, `[TypeSelectorDisplay]`, logging through `ConverterLogger`, contract tests. Use when creating, reviewing or reworking any converter in `StarterKit/Runtime/Converters`, when the user asks to "add a converter", "make it like the other converters", or shows a converter file.
In this repository9 other files it gives its agents.
- AGENTS.mdAGENTS.md
- asp-branchSkill
- asp-commitSkill
- aspid-code-styleSkill
- asp-mvvm-releaseSkill
- asp-prSkill
- docs-siteSkill
- starterkit-binder-authoringSkill
- starterkit-layoutSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 0 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 73
If the implementation already accepts null, declare the interface as IConverter<T?, …>.
- Very specific: line 78
Add ConvertBack where the reverse is unambiguous.
- Very specific: line 88
When [Min]/[Range] on a field rules the invalid value out of the Inspector, the runtime check for the same condition is removed and the …
- Very specific: line 95
Do not apply to a field that is the converter's essence (the format in NumberFormatConverter).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill 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
StarterKit converter
General style: skill aspid-code-style; placement: starterkit-layout; docs: asp-xmldoc. This skill covers only what is specific to converters.
Skeleton
#nullable enable
using System;
using UnityEngine;
using Aspid.FastTools.Types;
// ReSharper disable once CheckNamespace
namespace Aspid.MVVM.StarterKit
{
/// <summary>Clamps a number to a range.</summary>
[Serializable]
[TypeSelectorDisplay(
Group = "Aspid/Numbers",
Name = "Clamp",
Tooltip = "Clamps a number to a range")]
public class ClampNumberConverter : TwoWayNumberConverter
{
[Tooltip("Lower bound.")]
[SerializeField] private double _min;
[Tooltip("Upper bound.")]
[SerializeField] private double _max = 1d;
/// <remarks>Default: 0..1.</remarks>
public ClampNumberConverter() { }
/// <param name="min">Lower bound.</param>
/// <param name="max">Upper bound.</param>
/// <exception cref="ArgumentOutOfRangeException">Max is below min.</exception>
public ClampNumberConverter(double min, double max)
{
if (max < min) throw new ArgumentOutOfRangeException(nameof(max));
_min = min;
_max = max;
}
protected override double Apply(double value) =>
Math.Clamp(value, _min, _max);
}
}- The class is
[Serializable]and notsealedwhen generic. The display name matches the class name;Groupfollows the family. [SerializeReference]fields typedIConverter<,>,ICollectionFilter<>,ICollectionOrder<>,ICanExecuteHandler,IViewFactory<>,PluralRuleare not marked[TypeSelector]:StarterKitTypePickerDrawersupplies the picker by field type.[TypeSelector(...)]with arguments remains only where the candidate set must be narrowed.
Which base to use
| Situation | Base |
| Number → number, one formula, the 16 numeric interfaces | NumberConverter / TwoWayNumberConverter (Converters/General/Numbers); override only Apply / Undo, plus ApplyToInteger when the int/long results need another formula (RoundNumberConverter ignores its decimal places there) |
| String → number | StringToNumberConverter<T> (Strings/ToNumber): TryParse/Clamp/ConvertBack/Expected; decimal stays separate (string-backed fields) |
| Key → value by table | subclass DictionaryLookupConverter (see EnumToValueConverter), never a copy of the logic |
Formatting via ToString(format, culture) | Helpers/Globalization/FormatExtensions.FormatOrGeneral<T> instead of a private try/catch |
Unix time, "now" by DateTimeKind | Helpers/Time/UnixTime, Helpers/Time/CurrentTime |
| Range of a narrow type | Helpers/Numeric/NumericSaturation: saturate, never drop |
Which types to accept
- A converter that takes a number implements the whole family
int,long,float,doublewhen integer input is meaningful (Lerp,Remap,Round, formatters). Normalized0..1inputs (Easing,SmoothStep,InverseLerp) and wrappers over Unity float APIs stayfloatplusdouble → double; whenMathf/AnimationCurvesits underneath, double goes through the float path and one<remarks>phrase says so. - One vector operation lives in one class implementing
IConverter<Vector2, Vector2>,<Vector3, Vector3>,<Vector4, Vector4>, named without a digit (VectorNormalizeConverter). Exception: theVector2CombineConverter/Vector3CombineConverterbases. - If the implementation already accepts
null, declare the interface asIConverter<T?, …>; return-type nullability matches the interface. - Public
Convert(...)overloads plus explicit implementations: the explicit one delegates to the public one on the line after=>.
ConvertBack and the name
- Add
ConvertBackwhere the reverse is unambiguous; do not force it when it is lossy or already covered by a paired converter. - Drop "To" from the name only when a reverse interface exists:
IConverter<B, A>/ITwoWayConverter<B, A>beside the forward one, orITwoWayConverter<A, A>(DegreesRadiansConverter,ColorColor32Converter). A singleITwoWayConverter<A, B>keeps "To" (BoolToValueConverter,StringToIntConverter).ConverterAssetwrappers and caster binders are outside the rule. - A field used only in
ConvertBack: name_convertBackFallback+[UsedInModes(BindMode.TwoWay, BindMode.OneWayToSource)]. A parser field used only inConvert:[UsedInModes(BindMode.OneWay, BindMode.TwoWay, BindMode.OneTime)].
Errors and fallback
- The constructor throws on invalid arguments. Misconfigured serialized fields are detected in
Convertand logged on every call throughConverterLogger; no "already logged" flags. ConverterFallback<T>is for runtime failures that depend on the value (unparseable string, division by zero, a root with no real result), or when runtime and configuration failures mix in one converter. Pure configuration errors (an empty required reference) get no fallback field:LogErrorplus a sensible return.- The constructor takes the fallback as one optional
ConverterFallback<T>? fallback = null, assigned as_fallback = fallback ?? _fallback;. The internal representation (ticks, a decimal string) never leaks into the signature. UseFallback/Failcalls use named arguments, one per line; the problem text comes fromvalue.Expected("…").- When
[Min]/[Range]on a field rules the invalid value out of the Inspector, the runtime check for the same condition is removed and the constructor throwsArgumentOutOfRangeException. Attribute, exception and Tooltip agree.
Nested slots
- A parameter that duplicates another converter (a format string, a comparison value) becomes
[SerializeReference] private IConverter<…>? _slotwith a default implementation in the initializer (= new NumberFormatConverter("0.##")). - Empty slot: either documented pass-through (optional) or an error on every call plus
ArgumentNullExceptionin the constructor (required). - A convenience constructor with the plain parameter may stay, delegating into the slot.
- Do not apply to a field that is the converter's essence (the format in
NumberFormatConverter).
Serialized fields
[Tooltip]on every one, one short phrase. A behavioral caveat is repeated in the constructor<param>.[Min],[Range]and similar wherever the field can become invalid: indices and counts[Min(0)], fractions[Range(0f, 1f)].- No
= default!, no[FormerlySerializedAs].
Tests
- The catalogue is covered by contract tests in
Tests/EditMode/Converters/Contracts/*(TypeSelectorDisplay, field coverage, picker, frozen names inConverterRenameCompatibilityTests). Update the frozen list after a rename; make sure the contracts pass after an addition. - Assertions and comments in English.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
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.

