From cbbe1da82530937b09d7e62b49b00947f8b78bd4 Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Tue, 6 Oct 2026 04:31:48 +0300 Subject: [PATCH] =?UTF-8?q?practice:=20the=20metadata=20box=20rides=20a=20?= =?UTF-8?q?body=20portal=20=E2=80=94=20never=20clipped=20again=20(round=20?= =?UTF-8?q?17=20fix)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Diagnosed: the transcript's MessageContent boxes carry overflow-hidden (ai-elements/message.tsx) — the absolutely-positioned box rendered fine over its own message block's content (the closing-words text under the conclude card) and clipped the moment it crossed that block's bottom edge onto the next message. Exactly the reported cut-off. Fix: the box portals to document.body in fixed position (the dialogs' law) — placed from the trigger's viewport rect at open time, below the trigger, left-clamped to the viewport; z-50. Any-scroll-close means there is nothing to track. --- components/practice-cards.tsx | 47 +++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/components/practice-cards.tsx b/components/practice-cards.tsx index 45f6381..cff604c 100644 --- a/components/practice-cards.tsx +++ b/components/practice-cards.tsx @@ -21,6 +21,7 @@ import type { ArtifactPartProps } from "@/src/core"; */ import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; import { ThinkingBlock } from "@/components/thinking-block"; import { usePopoverDismiss } from "@/components/popover-dismiss"; @@ -177,7 +178,12 @@ function MetadataPopover({ problemInput: { mode?: unknown; target?: unknown }; }) { const [open, setOpen] = useState(false); + const triggerRef = useRef(null); const rootRef = useRef(null); + // The portal's anchor (owner fix, 2026-10-06): the box is placed from + // the trigger's viewport rect AT OPEN TIME — any scroll closes the + // box, so there is nothing to track. + const [anchor, setAnchor] = useState<{ top: number; left: number } | null>(null); const [names, setNames] = useState | null>(null); const targets = Array.isArray(problemInput.target) ? problemInput.target.filter((t): t is string => typeof t === "string") @@ -216,19 +222,39 @@ function MetadataPopover({ - {open && ( -
+ {open && + anchor && + createPortal( +
Mode
{mode || "—"}
@@ -256,8 +282,9 @@ function MetadataPopover({
Retest
{retest === undefined ? "—" : retest ? "yes" : "no"}
-
- )} +
, + document.body, + )} ); }