diff --git a/AdaptiveRemote.sln b/AdaptiveRemote.sln
index a63a24a..c2ff52d 100644
--- a/AdaptiveRemote.sln
+++ b/AdaptiveRemote.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 18.0.11217.181
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdaptiveRemote.App", "src\AdaptiveRemote.App\AdaptiveRemote.App.csproj", "{6C7C380B-D7A4-412E-8487-2AFC89EA802F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdaptiveRemote.Contracts", "src\AdaptiveRemote.Contracts\AdaptiveRemote.Contracts.csproj", "{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveRemote", "src\AdaptiveRemote\AdaptiveRemote.csproj", "{7BE31162-0D09-4F80-8CE5-978F7AECC1EF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdaptiveRemote.Console", "src\AdaptiveRemote.Console\AdaptiveRemote.Console.csproj", "{345B73FC-07F9-490F-B566-2677D10B1834}"
@@ -188,6 +190,18 @@ Global
{54522D5A-CEB3-F5B9-2654-1005EF1C3262}.Release|x64.Build.0 = Release|Any CPU
{54522D5A-CEB3-F5B9-2654-1005EF1C3262}.Release|x86.ActiveCfg = Release|Any CPU
{54522D5A-CEB3-F5B9-2654-1005EF1C3262}.Release|x86.Build.0 = Release|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|x64.Build.0 = Debug|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|x86.Build.0 = Debug|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|x64.ActiveCfg = Release|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|x64.Build.0 = Release|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|x86.ActiveCfg = Release|Any CPU
+ {F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/backend.slnf b/backend.slnf
new file mode 100644
index 0000000..bda5c53
--- /dev/null
+++ b/backend.slnf
@@ -0,0 +1,8 @@
+{
+ "solution": {
+ "path": "AdaptiveRemote.sln",
+ "projects": [
+ "src\\AdaptiveRemote.Contracts\\AdaptiveRemote.Contracts.csproj"
+ ]
+ }
+}
diff --git a/client.slnf b/client.slnf
new file mode 100644
index 0000000..1991596
--- /dev/null
+++ b/client.slnf
@@ -0,0 +1,19 @@
+{
+ "solution": {
+ "path": "AdaptiveRemote.sln",
+ "projects": [
+ "src\\AdaptiveRemote.Contracts\\AdaptiveRemote.Contracts.csproj",
+ "src\\AdaptiveRemote.App\\AdaptiveRemote.App.csproj",
+ "src\\AdaptiveRemote\\AdaptiveRemote.csproj",
+ "src\\AdaptiveRemote.Console\\AdaptiveRemote.Console.csproj",
+ "src\\AdaptiveRemote.Headless\\AdaptiveRemote.Headless.csproj",
+ "test\\AdaptiveRemote.App.Tests\\AdaptiveRemote.App.Tests.csproj",
+ "test\\AdaptiveRemote.Speech.Tests\\AdaptiveRemote.Speech.Tests.csproj",
+ "test\\AdaptiveRemote.EndtoEndTests.TestServices\\AdaptiveRemote.EndtoEndTests.TestServices.csproj",
+ "test\\AdaptiveRemote.EndToEndTests.Steps\\AdaptiveRemote.EndToEndTests.Steps.csproj",
+ "test\\AdaptiveRemote.EndToEndTests.Host.Headless\\AdaptiveRemote.EndToEndTests.Host.Headless.csproj",
+ "test\\AdaptiveRemote.EndToEndTests.Host.Wpf\\AdaptiveRemote.EndToEndTests.Host.Wpf.csproj",
+ "test\\AdaptiveRemote.EndtoEndTests.Host.Console\\AdaptiveRemote.EndToEndTests.Host.Console.csproj"
+ ]
+ }
+}
diff --git a/src/AdaptiveRemote.App/AdaptiveRemote.App.csproj b/src/AdaptiveRemote.App/AdaptiveRemote.App.csproj
index 396f43f..e104217 100644
--- a/src/AdaptiveRemote.App/AdaptiveRemote.App.csproj
+++ b/src/AdaptiveRemote.App/AdaptiveRemote.App.csproj
@@ -12,6 +12,10 @@
+
+
+
+
diff --git a/src/AdaptiveRemote.Contracts/AdaptiveRemote.Contracts.csproj b/src/AdaptiveRemote.Contracts/AdaptiveRemote.Contracts.csproj
new file mode 100644
index 0000000..d804afe
--- /dev/null
+++ b/src/AdaptiveRemote.Contracts/AdaptiveRemote.Contracts.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net10.0
+ enable
+ enable
+ AdaptiveRemote.Contracts
+
+
+
diff --git a/src/AdaptiveRemote.Contracts/CommandType.cs b/src/AdaptiveRemote.Contracts/CommandType.cs
new file mode 100644
index 0000000..5ea8a4f
--- /dev/null
+++ b/src/AdaptiveRemote.Contracts/CommandType.cs
@@ -0,0 +1,9 @@
+namespace AdaptiveRemote.Contracts;
+
+// Identifies the runtime command type. The client uses this to instantiate the correct
+// App runtime type (TiVoCommand, IRCommand, LifecycleCommand).
+// Type-specific execution parameters are resolved by the client from its own configuration:
+// TiVo — CommandId = Name.ToUpperInvariant() (existing convention)
+// IR — payload programmed via remote, stored in ProgrammaticSettings
+// Others — keyed by Name
+public enum CommandType { Lifecycle, TiVo, IR }
diff --git a/src/AdaptiveRemote.Contracts/ICommandProperties.cs b/src/AdaptiveRemote.Contracts/ICommandProperties.cs
new file mode 100644
index 0000000..51d7f27
--- /dev/null
+++ b/src/AdaptiveRemote.Contracts/ICommandProperties.cs
@@ -0,0 +1,14 @@
+namespace AdaptiveRemote.Contracts;
+
+// Shared behavioral interface — prevents drift between the compiled and raw command types.
+// Adding a new behavioral property means updating this interface first; the compiler
+// will flag any implementing record that doesn't follow.
+public interface ICommandProperties
+{
+ CommandType Type { get; }
+ string Name { get; }
+ string Label { get; }
+ string? Glyph { get; }
+ string SpeakPhrase { get; }
+ string? Reverse { get; }
+}
diff --git a/src/AdaptiveRemote.Contracts/LayoutContractsJsonContext.cs b/src/AdaptiveRemote.Contracts/LayoutContractsJsonContext.cs
new file mode 100644
index 0000000..1368475
--- /dev/null
+++ b/src/AdaptiveRemote.Contracts/LayoutContractsJsonContext.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace AdaptiveRemote.Contracts;
+
+// Source-generated JSON context — required for Native AOT Lambda functions;
+// shared by all consumers to ensure consistent serialization behaviour.
+[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
+[JsonSerializable(typeof(RawLayout))]
+[JsonSerializable(typeof(CompiledLayout))]
+[JsonSerializable(typeof(PreviewLayout))]
+[JsonSerializable(typeof(ValidationResult))]
+[JsonSerializable(typeof(IReadOnlyList))]
+[JsonSerializable(typeof(IReadOnlyList))]
+public partial class LayoutContractsJsonContext : JsonSerializerContext { }
diff --git a/src/AdaptiveRemote.Contracts/LayoutElementDto.cs b/src/AdaptiveRemote.Contracts/LayoutElementDto.cs
new file mode 100644
index 0000000..92cf7d5
--- /dev/null
+++ b/src/AdaptiveRemote.Contracts/LayoutElementDto.cs
@@ -0,0 +1,53 @@
+using System.Text.Json.Serialization;
+
+namespace AdaptiveRemote.Contracts;
+
+// ---------------------------------------------------------------------------
+// Compiled layout element DTOs
+// Used in CompiledLayout.Elements. Deserialized directly by the client application.
+// Contains only behavioral properties — grid positions and CSS overrides have been
+// compiled into CssDefinitions and are not needed by the client.
+// ---------------------------------------------------------------------------
+
+// "$type" avoids conflict with the behavioral Type property on CommandDefinitionDto.
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
+[JsonDerivedType(typeof(CommandDefinitionDto), "command")]
+[JsonDerivedType(typeof(LayoutGroupDefinitionDto), "group")]
+public abstract record LayoutElementDto(string CssId);
+
+// Maps to AdaptiveRemote.App.Models.Command at layout-apply time (client epic).
+// Type carries the CommandType discriminator so the client knows which runtime type to instantiate.
+// No subtype hierarchy is used — all behavioral properties are flat; type-specific execution
+// parameters are resolved by the client from its own configuration (see CommandType above).
+public record CommandDefinitionDto(
+ CommandType Type,
+ string Name,
+ string Label,
+ string? Glyph,
+ string SpeakPhrase,
+ string? Reverse,
+ string CssId
+) : LayoutElementDto(CssId), ICommandProperties;
+
+// Maps to AdaptiveRemote.App.Models.LayoutGroup at layout-apply time (client epic).
+public record LayoutGroupDefinitionDto(
+ string CssId,
+ IReadOnlyList Children
+) : LayoutElementDto(CssId);
+
+// ---------------------------------------------------------------------------
+// Client-consumable format produced by LayoutCompilerService.
+// Deserialized directly by the client application — no intermediate parsing model needed.
+// The client maps Elements → runtime Command objects at layout-apply time (client epic).
+// ---------------------------------------------------------------------------
+
+public record CompiledLayout(
+ Guid Id,
+ Guid RawLayoutId,
+ string UserId,
+ bool IsActive,
+ int Version,
+ IReadOnlyList Elements,
+ string CssDefinitions, // global CSS for the layout grid
+ DateTimeOffset CompiledAt
+);
diff --git a/src/AdaptiveRemote.Contracts/PreviewLayout.cs b/src/AdaptiveRemote.Contracts/PreviewLayout.cs
new file mode 100644
index 0000000..2e968d2
--- /dev/null
+++ b/src/AdaptiveRemote.Contracts/PreviewLayout.cs
@@ -0,0 +1,11 @@
+namespace AdaptiveRemote.Contracts;
+
+// Editor-consumable preview format, produced by LayoutCompilerService.
+public record PreviewLayout(
+ Guid RawLayoutId,
+ int Version,
+ string RenderedHtml,
+ string RenderedCss,
+ DateTimeOffset CompiledAt,
+ ValidationResult ValidationResult
+);
diff --git a/src/AdaptiveRemote.Contracts/RawLayoutElementDto.cs b/src/AdaptiveRemote.Contracts/RawLayoutElementDto.cs
new file mode 100644
index 0000000..3bf30c8
--- /dev/null
+++ b/src/AdaptiveRemote.Contracts/RawLayoutElementDto.cs
@@ -0,0 +1,64 @@
+using System.Text.Json.Serialization;
+
+namespace AdaptiveRemote.Contracts;
+
+// ---------------------------------------------------------------------------
+// Raw layout element DTOs
+// Shared between the editor application (serialization) and LayoutCompilerService
+// (deserialization). Extends behavioral properties with authoring properties that
+// the compiler resolves into CssDefinitions and strips from the compiled output.
+// ---------------------------------------------------------------------------
+
+// "$type" avoids conflict with the behavioral Type property on RawCommandDefinitionDto.
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
+[JsonDerivedType(typeof(RawCommandDefinitionDto), "command")]
+[JsonDerivedType(typeof(RawLayoutGroupDefinitionDto), "group")]
+public abstract record RawLayoutElementDto(
+ string CssId,
+ int GridRow,
+ int GridColumn,
+ int GridRowSpan = 1,
+ int GridColumnSpan = 1,
+ string? AdditionalCss = null // per-element CSS overrides (e.g. red background for Power)
+);
+
+public record RawCommandDefinitionDto(
+ CommandType Type,
+ string Name,
+ string Label,
+ string? Glyph,
+ string SpeakPhrase,
+ string? Reverse,
+ string CssId,
+ int GridRow,
+ int GridColumn,
+ int GridRowSpan = 1,
+ int GridColumnSpan = 1,
+ string? AdditionalCss = null
+) : RawLayoutElementDto(CssId, GridRow, GridColumn, GridRowSpan, GridColumnSpan, AdditionalCss),
+ ICommandProperties;
+
+public record RawLayoutGroupDefinitionDto(
+ string CssId,
+ IReadOnlyList Children,
+ int GridRow,
+ int GridColumn,
+ int GridRowSpan = 1,
+ int GridColumnSpan = 1,
+ string? AdditionalCss = null
+) : RawLayoutElementDto(CssId, GridRow, GridColumn, GridRowSpan, GridColumnSpan, AdditionalCss);
+
+// ---------------------------------------------------------------------------
+// Administrator-editable source format. Elements are typed; no opaque JSON string.
+// ---------------------------------------------------------------------------
+
+public record RawLayout(
+ Guid Id,
+ string UserId,
+ string Name,
+ IReadOnlyList Elements,
+ int Version,
+ DateTimeOffset CreatedAt,
+ DateTimeOffset UpdatedAt,
+ ValidationResult? ValidationResult // written by LayoutProcessingService via IRawLayoutStatusWriter
+);
diff --git a/src/AdaptiveRemote.Contracts/ValidationResult.cs b/src/AdaptiveRemote.Contracts/ValidationResult.cs
new file mode 100644
index 0000000..81c3e7b
--- /dev/null
+++ b/src/AdaptiveRemote.Contracts/ValidationResult.cs
@@ -0,0 +1,5 @@
+namespace AdaptiveRemote.Contracts;
+
+public record ValidationIssue(string Code, string Message, string? Path);
+
+public record ValidationResult(bool IsValid, IReadOnlyList Issues);
diff --git a/src/_doc_Projects.md b/src/_doc_Projects.md
index b786872..e81f19d 100644
--- a/src/_doc_Projects.md
+++ b/src/_doc_Projects.md
@@ -33,6 +33,14 @@ This document describes the high-level organization of the AdaptiveRemote reposi
- Minimal code to launch the WPF app with console logging.
- No business logic or features.
+### AdaptiveRemote.Contracts
+- **Purpose:** Shared class library containing layout definition DTOs, enums, interfaces, and the source-generated `LayoutContractsJsonContext` used by both the client application and backend services.
+- **Guidance:** _No platform-specific dependencies._ Targets `net10.0` only. Contains pure data types (records, enums, interfaces) with no behavior.
+- **Boundaries:**
+ - No WPF, Windows APIs, or Blazor dependencies.
+ - No MVVM or runtime behavior — DTOs only.
+ - Included in both `client.slnf` and `backend.slnf`.
+
## Test Projects
### AdaptiveRemote.App.Tests