Generation/SuiAllowedPropertyList.cs

Static helper that lists which CSS-like properties the S&box UI generator may emit and which are forbidden. It provides IsAllowed, IsForbidden and Validate methods to check property names and certain disallowed values for display and position.

Reflection
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;
	}
}