fix(ui): OP2 re-review closure (coordinator pass) — AP-195, tooltip port, zero-children pin

Closes the mechanism lens's REOPEN (one MUST-FIX) and both lenses' small
residuals on the OP2 rework (b236a442); the blast lens re-review was
CLOSED outright. Fable-direct per the two-failure escalation rule.

- AP-195 filed: UIOption_CheckboxBitfield64 ports HALF of Refresh
  @0x004859C0 — the ANY-set checkbox predicate is exact, but the ALL-set
  LED media swap (P0x10000082=0x06004D17 / P0x10000083=0x06004D19) and
  the ListBox self-sizing tail (ResizeTo/CalculatePaperSize — the block
  IS a UIElement_ListBox in retail) are unported, and the block's row
  stacking is a second divergent implementation beside UiTemplateListBox.
  All due at OP5 before the Chat tab's connected gate; the IsSet doc
  comment now names both halves instead of quoting only the ported one.
- Row tooltips: UiButton gains settable TooltipText surfaced through the
  shared GetTooltipText hover pipeline (UiCatalogSlot's pattern);
  UiCheckboxBitfield64.AddChild applies the row tooltip retail stamps in
  CreateChildren @0x00485DF0, and documents that the 0x10000084 row-index
  attribute stamp is deliberately replaced by the typed mask closure.
- AD-73 addendum: UiTemplateListBox.ConsumesDatChildren=true is inert
  only while no authored Type-5 element carries children — that premise
  is now conformance-PINNED across all 32 fixtures (a future DAT
  regeneration surfacing an authored child fails the build instead of
  silently dropping it).
- Plan doc: OP2's contract names UiTabPanel.cs (retail UIElement_Panel),
  not the fictional-class-named UiTabControl.cs; ledger records OP1 and
  OP2 both CLOSED.

Full Release suite: 12,871 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-11 01:26:29 +02:00
parent 6f48e34152
commit 5242de9f15
7 changed files with 943 additions and 6 deletions

View file

@ -1,3 +1,5 @@
using System.IO;
using System.Text.Json;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
@ -154,4 +156,51 @@ public class OP2ReworkBlastRadiusConformanceTests
Assert.Equal(0, host.ContentHeight);
Assert.True(host.ClickThrough);
}
/// <summary>
/// AD-73 addendum (OP2 re-review closure, 2026-08-11):
/// <see cref="UiTemplateListBox.ConsumesDatChildren"/> is <c>true</c> where the
/// pre-rework generic fallback's was not. That delta drops nothing ONLY while no
/// authored Type-5 element carries children — a premise the re-review verified by
/// hand across all 32 fixtures. This test turns that manual sweep into a build
/// gate: if a future DAT regeneration surfaces a Type-5 element WITH authored
/// children, this fails loudly instead of the children silently vanishing.
/// </summary>
[Fact]
public void EveryAuthoredType5Element_HasZeroChildren_AcrossAllFixtures()
{
string fixturesDir = Path.Combine(
AppContext.BaseDirectory, "UI", "Layout", "fixtures");
string[] fixtures = Directory.GetFiles(fixturesDir, "*.json");
Assert.NotEmpty(fixtures);
var opts = new JsonSerializerOptions { IncludeFields = true };
int type5Seen = 0;
foreach (string file in fixtures)
{
var root = JsonSerializer.Deserialize<ElementInfo>(
File.ReadAllText(file), opts);
Assert.NotNull(root);
Walk(root!, file);
}
// The premise is only meaningful if the sweep actually saw Type-5
// elements (16 across the current fixture set).
Assert.True(type5Seen >= 10, $"sweep saw only {type5Seen} Type-5 elements");
void Walk(ElementInfo node, string file)
{
if (node.Type == 5u)
{
type5Seen++;
Assert.True(
node.Children.Count == 0,
$"{Path.GetFileName(file)}: Type-5 element 0x{node.Id:X8} authors "
+ $"{node.Children.Count} children — UiTemplateListBox.ConsumesDatChildren "
+ "would drop them; see AD-73's addendum.");
}
foreach (ElementInfo child in node.Children)
Walk(child, file);
}
}
}