Editor/Serialization/IR/IRNodeEnvelope.cs
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Grains.RazorDesigner.Document;
namespace Grains.RazorDesigner.Serialization.IR;
public sealed record IRNodeEnvelope
{
// Shared empty collections — avoids allocations for the common case.
private static readonly IReadOnlyList<Grains.RazorDesigner.Wiring.Binding> _emptyBindings = System.Array.Empty<Grains.RazorDesigner.Wiring.Binding>();
private static readonly IReadOnlyDictionary<string, object> _emptyMetadata = new Dictionary<string, object>();
private static readonly IReadOnlyList<IRNodeEnvelope> _emptyChildren = System.Array.Empty<IRNodeEnvelope>();
private static readonly IReadOnlyDictionary<string, IRNodeEnvelope> _emptySlots = new Dictionary<string, IRNodeEnvelope>();
// Document identity GUID — preserved verbatim across save/load. Never reminted on read.
public Guid Id { get; init; }
// Control type as a PascalCase string (JsonStringEnumConverter handles this).
public ControlType Kind { get; init; }
// CSS class name — user-editable, used as the SCSS selector.
public string ClassName { get; init; }
// All layout/visual appearance fields. Serialises the M4 Appearance struct directly.
public Appearance Appearance { get; init; } = Appearance.Default;
// Per-kind payload with "$type" discriminator (from [JsonPolymorphic] on abstract Payload).
public Payload Payload { get; init; }
public IReadOnlyDictionary<string, IRNodeEnvelope> Slots { get; init; } = _emptySlots;
// Non-slot children in document order.
public IReadOnlyList<IRNodeEnvelope> Children { get; init; } = _emptyChildren;
[JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
public IReadOnlyList<IRStateEnvelope> States { get; init; }
[JsonIgnore( Condition = JsonIgnoreCondition.Never )]
public IReadOnlyList<Grains.RazorDesigner.Wiring.Binding> Bindings { get; init; } = _emptyBindings;
// Reserved metadata dict — always {} in v1.
[JsonIgnore( Condition = JsonIgnoreCondition.Never )]
public IReadOnlyDictionary<string, object> Metadata { get; init; } = _emptyMetadata;
// Forward-compat: unknown per-node keys from future-version files ride here.
[JsonExtensionData]
public IDictionary<string, JsonElement> Extra { get; init; }
}
public sealed record IRStateEnvelope
{
public Grains.RazorDesigner.Document.PseudoKind State { get; init; }
[JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingDefault )]
public Grains.RazorDesigner.Document.NthChildMode NthChildMode { get; init; } = Grains.RazorDesigner.Document.NthChildMode.Literal;
[JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingDefault )]
public int NthChildArg { get; init; } = 1;
public Grains.RazorDesigner.Document.Appearance Delta { get; init; } = Grains.RazorDesigner.Document.Appearance.Default;
}