Generation/SuiAllowedPropertyList.cs
using System.Collections.Generic;
namespace SboxUiDesigner.Generation;
/// <summary>
/// Whitelist of CSS properties the generator is allowed to emit. Anything
/// outside this list is a hard error — the SCSS emitter REFUSES to produce
/// it. This is the engine-correctness boundary documented in PRD doc 08
/// and `references/ui-anti-patterns.md` (Confusion 2).
///
/// The list mirrors what the s&box runtime UI Yoga + style implementation
/// actually parses. Web CSS properties not in this list (display: grid,
/// position: fixed, float, etc.) are silently ignored at runtime AND
/// produce silent design bugs — so we forbid them at generate time.
/// </summary>
public static class SuiAllowedPropertyList
{
private static readonly HashSet<string> _allowed = new( System.StringComparer.OrdinalIgnoreCase )
{
// Display & layout
"display",
"flex-direction", "flex-wrap", "flex-grow", "flex-shrink", "flex-basis", "flex",
"gap", "row-gap", "column-gap",
"justify-content", "align-items", "align-self",
"position",
"left", "top", "right", "bottom",
"width", "height", "min-width", "min-height", "max-width", "max-height",
"margin", "margin-left", "margin-top", "margin-right", "margin-bottom",
"padding", "padding-left", "padding-top", "padding-right", "padding-bottom",
"overflow", "z-index",
// Visual
"background-color",
"background-image", "background-size", "background-position", "background-repeat",
"background-image-tint",
"border", "border-color", "border-width", "border-radius",
"box-shadow", "text-shadow",
"opacity",
// Text
"color",
"font-family", "font-size", "font-weight", "font-style",
"text-align", "text-transform", "text-decoration",
"letter-spacing", "line-height", "white-space",
"text-overflow",
// Motion
"transform",
"transition", "transition-duration", "transition-property", "transition-timing-function", "transition-delay",
"animation", "animation-name", "animation-duration", "animation-iteration-count", "animation-timing-function",
// s&box-specific
"pointer-events",
"sound-in", "sound-out",
};
/// <summary>Hard-blocked properties that web tutorials suggest but the engine doesn't support.</summary>
private static readonly HashSet<string> _forbidden = new( System.StringComparer.OrdinalIgnoreCase )
{
"grid-template-columns", "grid-template-rows", "grid-template-areas", "grid-area",
"grid-row", "grid-column", "grid-gap", "grid-row-gap", "grid-column-gap",
"float", "clear",
};
/// <summary>Hard-blocked VALUE strings — properties that exist but with disallowed values.</summary>
private static readonly HashSet<string> _forbiddenDisplayValues = new( System.StringComparer.OrdinalIgnoreCase )
{
"grid", "block", "inline", "inline-block", "inline-flex", "table", "table-cell", "contents",
};
private static readonly HashSet<string> _forbiddenPositionValues = new( System.StringComparer.OrdinalIgnoreCase )
{
"fixed", "sticky",
};
public static bool IsAllowed( string property ) => _allowed.Contains( property );
public static bool IsForbidden( string property ) => _forbidden.Contains( property );
/// <summary>
/// Validate a property/value pair. Returns null if accepted, an error
/// string if rejected (will block the generation with a useful message).
/// </summary>
public static string Validate( string property, string value )
{
if ( string.IsNullOrEmpty( property ) ) return "empty property name";
if ( _forbidden.Contains( property ) )
return $"'{property}' is on the forbidden list (engine doesn't support it)";
if ( !_allowed.Contains( property ) )
return $"'{property}' is not on the allowed-property list";
if ( property.Equals( "display", System.StringComparison.OrdinalIgnoreCase ) )
{
if ( !string.IsNullOrEmpty( value ) && _forbiddenDisplayValues.Contains( value.Trim() ) )
return $"display: {value} is not supported (only flex, none)";
}
else if ( property.Equals( "position", System.StringComparison.OrdinalIgnoreCase ) )
{
if ( !string.IsNullOrEmpty( value ) && _forbiddenPositionValues.Contains( value.Trim() ) )
return $"position: {value} is not supported (only static, relative, absolute)";
}
return null;
}
}