The remediation text is the payload - the enable-API console link was clipped by the one-line truncate. The scenario now asserts the text wraps un-clipped and the link sits inside the visible box.
Unified · Split
| @@ -304,7 +304,8 @@ scenario( · 303 unmodified lines | ||
| 304 | 304 | await step( |
| 305 | 305 | "The row carries Google's own instruction, console link included", |
| 306 | 306 | async () => { |
| 307 | − await connections.getByText(/has not been used in project/).waitFor(); | |
| 307 | + const detail = connections.getByText(/has not been used in project/); | |
| 308 | + await detail.waitFor(); | |
| 308 | 309 | const consoleLink = connections.getByRole("link", { |
| 309 | 310 | name: /console\.developers\.google\.com/, |
| 310 | 311 | }); |
| @@ -313,6 +314,22 @@ scenario( · 2 unmodified lines | ||
| 313 | 314 | await consoleLink.getAttribute("href"), |
| 314 | 315 | "the enable-API console link is clickable", |
| 315 | 316 | ).toContain("console.developers.google.com"); |
| 317 | + // Visible in FULL, not just present in the DOM: a one-line | |
| 318 | + // truncate keeps the link in the tree while clipping it from | |
| 319 | + // view, which is exactly the regression this guards against. | |
| 320 | + // A clipped element overflows horizontally; a wrapped one | |
| 321 | + // grows taller instead. | |
| 322 | + const clipped = await detail.evaluate( | |
| 323 | + (el) => | |
| 324 | + el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1, | |
| 325 | + ); | |
| 326 | + expect(clipped, "the remediation text wraps instead of clipping").toBe(false); | |
| 327 | + const linkVisible = await consoleLink.evaluate((el) => { | |
| 328 | + const rect = el.getBoundingClientRect(); | |
| 329 | + const detailRect = el.closest("p")?.getBoundingClientRect(); | |
| 330 | + return detailRect ? rect.right <= detailRect.right + 1 : false; | |
| 331 | + }); | |
| 332 | + expect(linkVisible, "the console link sits inside the visible detail box").toBe(true); | |
| 316 | 333 | }, |
| 317 | 334 | ); |
| 318 | 335 | |
| @@ -192,9 +192,13 @@ function AccountRow(props: { · 191 unmodified lines | ||
| 192 | 192 | </CardStackEntryDescription> |
| 193 | 193 | ) : null} |
| 194 | 194 | {misconfigured && probe?.detail ? ( |
| 195 | − <CardStackEntryDescription className="mt-1 text-xs text-muted-foreground"> | |
| 195 | + // Not CardStackEntryDescription: that truncates to one line, and this | |
| 196 | + // text IS the remediation (the enable-API console link must stay | |
| 197 | + // visible in full). Wrap instead; break anywhere so the long URL | |
| 198 | + // cannot overflow the row. | |
| 199 | + <p className="mt-1 whitespace-normal text-xs text-muted-foreground [overflow-wrap:anywhere]"> | |
| 196 | 200 | <DetailWithLinks text={probe.detail} /> |
| 197 | − </CardStackEntryDescription> | |
| 201 | + </p> | |
| 198 | 202 | ) : null} |
| 199 | 203 | {needsReconsent ? ( |
| 200 | 204 | <CardStackEntryDescription className="mt-1 text-xs text-muted-foreground"> |