diff --git a/src/AcDream.App/UI/UiPanel.cs b/src/AcDream.App/UI/UiPanel.cs
index 5b5e81df..926eb1e8 100644
--- a/src/AcDream.App/UI/UiPanel.cs
+++ b/src/AcDream.App/UI/UiPanel.cs
@@ -117,9 +117,31 @@ public class UiLabel : UiElement
public UiLabel() { ClickThrough = true; }
+ ///
+ /// CT7 gate regression (2026-08-25, MossTank plugin text vanished): a
+ /// markup <label> authors position only, so this element's
+ /// box is degenerate (0x0) — under CT-GF1's completed
+ /// UIRegion::DrawHere @0x0069FA30 port the self-clip cropped the
+ /// glyphs to nothing. A label must not self-clip: it is
+ /// pure text whose real clip is
+ /// its ancestors (the plugin panel/window, which ARE properly sized) —
+ /// the same effective containment retail gives a text region whose box
+ /// hugs its glyphs. below also keeps the box
+ /// truthful by measuring the current text, so layout/hit consumers see
+ /// real extents (converges the frame after a bound text change).
+ ///
+ protected override bool ClipsChildren => false;
+
protected override void OnDraw(UiRenderContext ctx)
{
string text = TextSource?.Invoke() ?? Text;
+ float w = DatFont is { } df
+ ? df.MeasureWidth(text)
+ : (ctx.DefaultFont?.MeasureWidth(text) ?? text.Length * 7f);
+ float h = DatFont?.LineHeight
+ ?? ctx.DefaultFont?.LineHeight ?? 14f;
+ if (w != Width) Width = w;
+ if (h != Height) Height = h;
if (DatFont is { } dat)
ctx.DrawStringDat(dat, text, 0, 0, TextColor, Outline);
else
diff --git a/tests/AcDream.App.Tests/UI/UiAncestorClipTests.cs b/tests/AcDream.App.Tests/UI/UiAncestorClipTests.cs
index 22f976f8..d0ba8eff 100644
--- a/tests/AcDream.App.Tests/UI/UiAncestorClipTests.cs
+++ b/tests/AcDream.App.Tests/UI/UiAncestorClipTests.cs
@@ -376,4 +376,50 @@ public sealed class UiAncestorClipTests
root.DrawOverlays(ctx);
Assert.NotEmpty(renderer.DebugSpriteSegmentVerts);
}
+
+ ///
+ /// CT7 gate regression (2026-08-25): the MossTank plugin's markup
+ /// labels lost ALL text under the completed self-clip — a markup
+ /// <label> authors position only, so UiLabel's box was 0x0 and the
+ /// element's own clip cropped every glyph. UiLabel now opts out of the
+ /// self-clip (its ancestors clip — the retail containment for a text
+ /// region whose box hugs its glyphs) and keeps a truthful measured box.
+ /// Button captions never regressed because markup buttons author w/h.
+ ///
+ [Fact]
+ public void UnsizedMarkupLabel_InsideASizedPanel_DoesNotSelfClipItsSubtree()
+ {
+ var (device, renderer, ctx) = MakeContext(800f, 600f);
+ var panel = new UiPanel
+ {
+ Left = 50f, Top = 50f, Width = 200f, Height = 100f,
+ BackgroundColor = default, BorderColor = default,
+ };
+ // Exactly what MarkupDocument builds for