Collapse runtime menu tab surface state

This commit is contained in:
Meta Vens 2026-06-29 08:10:36 +01:00
commit 6ca7fbc64b
2 changed files with 36 additions and 89 deletions

View file

@ -4867,7 +4867,6 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root)
"AetheriaRuntimeMenuTabsSurfaceBuilder.Compose(",
"AetheriaRuntimeMenuTabsSurfaceBuilder.Project(",
"new AetheriaRuntimeMenuTabProjectionOption(",
"new AetheriaRuntimeMenuTabSurfaceEntry(",
"private static string TabKey("
};
@ -4891,32 +4890,36 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root)
"MenuPanel tab visibility must read managed typed current-docking state instead of direct docking caches or ActionGameManager observed adapters.");
}
var requiredProjectionBuilderSymbols = new[]
var requiredDocumentBuilderSymbols = new[]
{
"public sealed class AetheriaRuntimeMenuTabModelOption",
"public static string NormalizeTabKey(string tabKey)",
"public static AetheriaRuntimeSurfaceDocument Build(",
"private static AetheriaRuntimeMenuTabsSurfaceState ComposeState(",
"public int Order { get; }",
".OrderBy(tab => tab.Order)",
"new AetheriaRuntimeMenuTabSurfaceEntry(",
"var normalizedCurrent = NormalizeTabKey(currentTabKey)",
"var tabs = (visibleTabs ?? Array.Empty<AetheriaRuntimeMenuTabModelOption>())",
"var selected = string.Equals(key, normalizedCurrent, StringComparison.Ordinal)",
"string.Equals(key, normalizedCurrent, StringComparison.Ordinal)"
};
var missingProjectionBuilderSymbols = requiredProjectionBuilderSymbols
var missingDocumentBuilderSymbols = requiredDocumentBuilderSymbols
.Where(symbol => !menuTabsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (missingProjectionBuilderSymbols.Length > 0)
if (missingDocumentBuilderSymbols.Length > 0)
{
throw new InvalidOperationException(
"Shared runtime menu tab surface builder no longer owns tab projection semantics: " +
string.Join(", ", missingProjectionBuilderSymbols));
"Shared runtime menu tab surface builder no longer owns tab document composition semantics: " +
string.Join(", ", missingDocumentBuilderSymbols));
}
if (menuTabsSurfaceBuilder.Contains("public static AetheriaRuntimeMenuTabsSurfaceState Compose(", StringComparison.Ordinal) ||
menuTabsSurfaceBuilder.Contains("public static AetheriaRuntimeMenuTabsSurfaceState Project(", StringComparison.Ordinal))
menuTabsSurfaceBuilder.Contains("public static AetheriaRuntimeMenuTabsSurfaceState Project(", StringComparison.Ordinal) ||
menuTabsSurfaceBuilder.Contains("AetheriaRuntimeMenuTabsSurfaceState", StringComparison.Ordinal) ||
menuTabsSurfaceBuilder.Contains("AetheriaRuntimeMenuTabSurfaceEntry", StringComparison.Ordinal) ||
menuTabsSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Runtime menu tab composition must stay behind Build(...); do not re-expose Compose/Project as the public tab surface path.");
"Runtime menu tab composition must emit the typed surface document directly; do not rebuild a shadow SurfaceState/Entry projection layer.");
}
var requiredDockedStoryObserverSymbols = new[]
@ -4996,8 +4999,7 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root)
"public static class AetheriaRuntimeMenuTabsSurfaceCommands",
"public static bool TryRead(",
"public static AetheriaRuntimeSurfaceDocument Build(",
"AetheriaRuntimeMenuTabsSurfaceState",
"AetheriaRuntimeMenuTabSurfaceEntry"
"var tabs = (visibleTabs ?? Array.Empty<AetheriaRuntimeMenuTabModelOption>())"
};
var missingBuilderSymbols = requiredBuilderSymbols
.Where(symbol => !menuTabsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))

View file

@ -5,37 +5,6 @@ using GameCult.Eve.Surface;
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeMenuTabSurfaceEntry
{
public AetheriaRuntimeMenuTabSurfaceEntry(string key, string label, bool selected)
{
Key = key ?? "";
Label = label ?? "";
Selected = selected;
}
public string Key { get; }
public string Label { get; }
public bool Selected { get; }
}
public sealed class AetheriaRuntimeMenuTabsSurfaceState
{
public AetheriaRuntimeMenuTabsSurfaceState(
string currentTabKey,
IReadOnlyList<AetheriaRuntimeMenuTabSurfaceEntry> visibleTabs,
string updatedAtUtc)
{
CurrentTabKey = currentTabKey ?? "";
VisibleTabs = visibleTabs ?? Array.Empty<AetheriaRuntimeMenuTabSurfaceEntry>();
UpdatedAtUtc = updatedAtUtc ?? "";
}
public string CurrentTabKey { get; }
public IReadOnlyList<AetheriaRuntimeMenuTabSurfaceEntry> VisibleTabs { get; }
public string UpdatedAtUtc { get; }
}
public sealed class AetheriaRuntimeMenuTabModelOption
{
public AetheriaRuntimeMenuTabModelOption(string key, string label, int order)
@ -71,55 +40,36 @@ namespace GameCult.Aetheria.State.Verse
IEnumerable<AetheriaRuntimeMenuTabModelOption> visibleTabs,
string updatedAtUtc,
long version = 1)
{
return Build(
ComposeState(currentTabKey, visibleTabs, updatedAtUtc),
version);
}
private static AetheriaRuntimeMenuTabsSurfaceState ComposeState(
string currentTabKey,
IEnumerable<AetheriaRuntimeMenuTabModelOption> visibleTabs,
string updatedAtUtc)
{
var normalizedCurrent = NormalizeTabKey(currentTabKey);
return new AetheriaRuntimeMenuTabsSurfaceState(
normalizedCurrent,
(visibleTabs ?? Array.Empty<AetheriaRuntimeMenuTabModelOption>())
.Where(tab => tab != null)
.OrderBy(tab => tab.Order)
.ThenBy(tab => NormalizeTabKey(tab.Key), StringComparer.Ordinal)
.Select(tab =>
var tabs = (visibleTabs ?? Array.Empty<AetheriaRuntimeMenuTabModelOption>())
.Where(tab => tab != null)
.OrderBy(tab => tab.Order)
.ThenBy(tab => NormalizeTabKey(tab.Key), StringComparer.Ordinal)
.Select(tab =>
{
var key = NormalizeTabKey(tab.Key);
var label = string.IsNullOrWhiteSpace(tab.Label) ? key : tab.Label;
var selected = string.Equals(key, normalizedCurrent, StringComparison.Ordinal);
return new
{
var key = NormalizeTabKey(tab.Key);
return new AetheriaRuntimeMenuTabSurfaceEntry(
key,
string.IsNullOrWhiteSpace(tab.Label) ? key : tab.Label,
string.Equals(key, normalizedCurrent, StringComparison.Ordinal));
})
.ToArray(),
updatedAtUtc);
}
Key = key,
Label = label,
Selected = selected
};
})
.ToArray();
public static AetheriaRuntimeSurfaceDocument Build(
AetheriaRuntimeMenuTabsSurfaceState state,
long version = 1)
{
state ??= new AetheriaRuntimeMenuTabsSurfaceState(
"",
Array.Empty<AetheriaRuntimeMenuTabSurfaceEntry>(),
"");
var commands = state.VisibleTabs
var commands = tabs
.Select(tab => new AetheriaRuntimeSurfaceCommandTemplate(
CommandFor(tab.Key),
LabelFor(tab),
tab.Label,
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport))
.ToArray();
var buttons = state.VisibleTabs
var buttons = tabs
.Select(tab => Button(
$"{SurfaceId}.{SafeId(tab.Key)}",
tab.Selected ? $"{LabelFor(tab)} *" : LabelFor(tab),
tab.Selected ? $"{tab.Label} *" : tab.Label,
CommandFor(tab.Key)))
.ToArray();
@ -128,7 +78,7 @@ namespace GameCult.Aetheria.State.Verse
providerKind: "runtime.menu",
title: "Runtime Menu Tabs",
version: version,
updatedAtUtc: state.UpdatedAtUtc,
updatedAtUtc: updatedAtUtc ?? "",
surface: new AetheriaRuntimeSurfaceTree(
SurfaceId,
Node(
@ -137,17 +87,12 @@ namespace GameCult.Aetheria.State.Verse
Array.Empty<(string Key, string Value)>(),
Text(
$"{SurfaceId}.current",
$"Current: {state.CurrentTabKey}"),
$"Current: {normalizedCurrent}"),
ButtonRow($"{SurfaceId}.tabs", buttons)),
Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
commands: commands);
}
private static string LabelFor(AetheriaRuntimeMenuTabSurfaceEntry tab)
{
return string.IsNullOrWhiteSpace(tab.Label) ? tab.Key : tab.Label;
}
private static string SafeId(string value)
{
if (string.IsNullOrWhiteSpace(value))