Skip to content

Commit 65e373f

Browse files
jodavisclaude
andauthored
Add AdaptiveRemote.Contracts shared library for layout DTOs (#137)
* ADR-166: Add AdaptiveRemote.Contracts shared library and solution filters - Add AdaptiveRemote.Contracts project (net10.0, no platform-specific dependencies) containing all layout definition DTOs, enums, and LayoutContractsJsonContext from the spec's Shared Contracts section: CommandType enum, ICommandProperties interface, LayoutElementDto hierarchy (compiled), RawLayoutElementDto hierarchy (raw), RawLayout/CompiledLayout/ PreviewLayout top-level records, ValidationIssue/ValidationResult records, and source-generated LayoutContractsJsonContext for consistent serialization across all consumers including Native AOT Lambda functions. - Add AdaptiveRemote.Contracts to AdaptiveRemote.sln with full build configurations. - Reference AdaptiveRemote.Contracts from AdaptiveRemote.App. - Add client.slnf and backend.slnf solution filters. - Update _doc_Projects.md to document the new shared contracts project. https://claude.ai/code/session_01T3tonn7C9F6TYbqH23KmG1 * Address PR review comments - Remove Action from CommandType enum (it's a WPF adapter, not a command type) - Change type discriminator from "type" to "\$type" on both LayoutElementDto and RawLayoutElementDto to avoid conflict with the behavioral Type property on CommandDefinitionDto and RawCommandDefinitionDto - Move RawLayout into RawLayoutElementDto.cs, CompiledLayout into LayoutElementDto.cs, PreviewLayout into its own PreviewLayout.cs; delete Layouts.cs https://claude.ai/code/session_01T3tonn7C9F6TYbqH23KmG1 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5900a56 commit 65e373f

13 files changed

Lines changed: 233 additions & 0 deletions

AdaptiveRemote.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ VisualStudioVersion = 18.0.11217.181
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdaptiveRemote.App", "src\AdaptiveRemote.App\AdaptiveRemote.App.csproj", "{6C7C380B-D7A4-412E-8487-2AFC89EA802F}"
77
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdaptiveRemote.Contracts", "src\AdaptiveRemote.Contracts\AdaptiveRemote.Contracts.csproj", "{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}"
9+
EndProject
810
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveRemote", "src\AdaptiveRemote\AdaptiveRemote.csproj", "{7BE31162-0D09-4F80-8CE5-978F7AECC1EF}"
911
EndProject
1012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdaptiveRemote.Console", "src\AdaptiveRemote.Console\AdaptiveRemote.Console.csproj", "{345B73FC-07F9-490F-B566-2677D10B1834}"
@@ -188,6 +190,18 @@ Global
188190
{54522D5A-CEB3-F5B9-2654-1005EF1C3262}.Release|x64.Build.0 = Release|Any CPU
189191
{54522D5A-CEB3-F5B9-2654-1005EF1C3262}.Release|x86.ActiveCfg = Release|Any CPU
190192
{54522D5A-CEB3-F5B9-2654-1005EF1C3262}.Release|x86.Build.0 = Release|Any CPU
193+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
194+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|Any CPU.Build.0 = Debug|Any CPU
195+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|x64.ActiveCfg = Debug|Any CPU
196+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|x64.Build.0 = Debug|Any CPU
197+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|x86.ActiveCfg = Debug|Any CPU
198+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Debug|x86.Build.0 = Debug|Any CPU
199+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|Any CPU.ActiveCfg = Release|Any CPU
200+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|Any CPU.Build.0 = Release|Any CPU
201+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|x64.ActiveCfg = Release|Any CPU
202+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|x64.Build.0 = Release|Any CPU
203+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|x86.ActiveCfg = Release|Any CPU
204+
{F81FEF3B-DB7A-4C04-9DF0-72E98382097A}.Release|x86.Build.0 = Release|Any CPU
191205
EndGlobalSection
192206
GlobalSection(SolutionProperties) = preSolution
193207
HideSolutionNode = FALSE

backend.slnf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"solution": {
3+
"path": "AdaptiveRemote.sln",
4+
"projects": [
5+
"src\\AdaptiveRemote.Contracts\\AdaptiveRemote.Contracts.csproj"
6+
]
7+
}
8+
}

client.slnf

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"solution": {
3+
"path": "AdaptiveRemote.sln",
4+
"projects": [
5+
"src\\AdaptiveRemote.Contracts\\AdaptiveRemote.Contracts.csproj",
6+
"src\\AdaptiveRemote.App\\AdaptiveRemote.App.csproj",
7+
"src\\AdaptiveRemote\\AdaptiveRemote.csproj",
8+
"src\\AdaptiveRemote.Console\\AdaptiveRemote.Console.csproj",
9+
"src\\AdaptiveRemote.Headless\\AdaptiveRemote.Headless.csproj",
10+
"test\\AdaptiveRemote.App.Tests\\AdaptiveRemote.App.Tests.csproj",
11+
"test\\AdaptiveRemote.Speech.Tests\\AdaptiveRemote.Speech.Tests.csproj",
12+
"test\\AdaptiveRemote.EndtoEndTests.TestServices\\AdaptiveRemote.EndtoEndTests.TestServices.csproj",
13+
"test\\AdaptiveRemote.EndToEndTests.Steps\\AdaptiveRemote.EndToEndTests.Steps.csproj",
14+
"test\\AdaptiveRemote.EndToEndTests.Host.Headless\\AdaptiveRemote.EndToEndTests.Host.Headless.csproj",
15+
"test\\AdaptiveRemote.EndToEndTests.Host.Wpf\\AdaptiveRemote.EndToEndTests.Host.Wpf.csproj",
16+
"test\\AdaptiveRemote.EndtoEndTests.Host.Console\\AdaptiveRemote.EndToEndTests.Host.Console.csproj"
17+
]
18+
}
19+
}

src/AdaptiveRemote.App/AdaptiveRemote.App.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
<InternalsVisibleTo Include="AdaptiveRemote.EndtoEndTests.TestServices" />
1313
</ItemGroup>
1414

15+
<ItemGroup>
16+
<ProjectReference Include="..\AdaptiveRemote.Contracts\AdaptiveRemote.Contracts.csproj" />
17+
</ItemGroup>
18+
1519
<ItemGroup>
1620
<PackageReference Include="Azure.Identity" />
1721
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" />
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<RootNamespace>AdaptiveRemote.Contracts</RootNamespace>
8+
</PropertyGroup>
9+
10+
</Project>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace AdaptiveRemote.Contracts;
2+
3+
// Identifies the runtime command type. The client uses this to instantiate the correct
4+
// App runtime type (TiVoCommand, IRCommand, LifecycleCommand).
5+
// Type-specific execution parameters are resolved by the client from its own configuration:
6+
// TiVo — CommandId = Name.ToUpperInvariant() (existing convention)
7+
// IR — payload programmed via remote, stored in ProgrammaticSettings
8+
// Others — keyed by Name
9+
public enum CommandType { Lifecycle, TiVo, IR }
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace AdaptiveRemote.Contracts;
2+
3+
// Shared behavioral interface — prevents drift between the compiled and raw command types.
4+
// Adding a new behavioral property means updating this interface first; the compiler
5+
// will flag any implementing record that doesn't follow.
6+
public interface ICommandProperties
7+
{
8+
CommandType Type { get; }
9+
string Name { get; }
10+
string Label { get; }
11+
string? Glyph { get; }
12+
string SpeakPhrase { get; }
13+
string? Reverse { get; }
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace AdaptiveRemote.Contracts;
4+
5+
// Source-generated JSON context — required for Native AOT Lambda functions;
6+
// shared by all consumers to ensure consistent serialization behaviour.
7+
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
8+
[JsonSerializable(typeof(RawLayout))]
9+
[JsonSerializable(typeof(CompiledLayout))]
10+
[JsonSerializable(typeof(PreviewLayout))]
11+
[JsonSerializable(typeof(ValidationResult))]
12+
[JsonSerializable(typeof(IReadOnlyList<RawLayout>))]
13+
[JsonSerializable(typeof(IReadOnlyList<CompiledLayout>))]
14+
public partial class LayoutContractsJsonContext : JsonSerializerContext { }
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace AdaptiveRemote.Contracts;
4+
5+
// ---------------------------------------------------------------------------
6+
// Compiled layout element DTOs
7+
// Used in CompiledLayout.Elements. Deserialized directly by the client application.
8+
// Contains only behavioral properties — grid positions and CSS overrides have been
9+
// compiled into CssDefinitions and are not needed by the client.
10+
// ---------------------------------------------------------------------------
11+
12+
// "$type" avoids conflict with the behavioral Type property on CommandDefinitionDto.
13+
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
14+
[JsonDerivedType(typeof(CommandDefinitionDto), "command")]
15+
[JsonDerivedType(typeof(LayoutGroupDefinitionDto), "group")]
16+
public abstract record LayoutElementDto(string CssId);
17+
18+
// Maps to AdaptiveRemote.App.Models.Command at layout-apply time (client epic).
19+
// Type carries the CommandType discriminator so the client knows which runtime type to instantiate.
20+
// No subtype hierarchy is used — all behavioral properties are flat; type-specific execution
21+
// parameters are resolved by the client from its own configuration (see CommandType above).
22+
public record CommandDefinitionDto(
23+
CommandType Type,
24+
string Name,
25+
string Label,
26+
string? Glyph,
27+
string SpeakPhrase,
28+
string? Reverse,
29+
string CssId
30+
) : LayoutElementDto(CssId), ICommandProperties;
31+
32+
// Maps to AdaptiveRemote.App.Models.LayoutGroup at layout-apply time (client epic).
33+
public record LayoutGroupDefinitionDto(
34+
string CssId,
35+
IReadOnlyList<LayoutElementDto> Children
36+
) : LayoutElementDto(CssId);
37+
38+
// ---------------------------------------------------------------------------
39+
// Client-consumable format produced by LayoutCompilerService.
40+
// Deserialized directly by the client application — no intermediate parsing model needed.
41+
// The client maps Elements → runtime Command objects at layout-apply time (client epic).
42+
// ---------------------------------------------------------------------------
43+
44+
public record CompiledLayout(
45+
Guid Id,
46+
Guid RawLayoutId,
47+
string UserId,
48+
bool IsActive,
49+
int Version,
50+
IReadOnlyList<LayoutElementDto> Elements,
51+
string CssDefinitions, // global CSS for the layout grid
52+
DateTimeOffset CompiledAt
53+
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace AdaptiveRemote.Contracts;
2+
3+
// Editor-consumable preview format, produced by LayoutCompilerService.
4+
public record PreviewLayout(
5+
Guid RawLayoutId,
6+
int Version,
7+
string RenderedHtml,
8+
string RenderedCss,
9+
DateTimeOffset CompiledAt,
10+
ValidationResult ValidationResult
11+
);

0 commit comments

Comments
 (0)