Skip to content

Commit 28f1c31

Browse files
committed
Clip the inspector to the device; guard the page against our own controls
Two things the inspector got wrong on a scrollable screen. Native frames routinely extend past the display — a horizontally scrollable chip row, list content below the fold — and the overlay drew them unclipped, so boxes floated in the black beside the device describing elements nobody could point at. The overlay now clips to the device and its corners, and nodes lying wholly off-screen are skipped rather than drawn. The guard is the more important half. The device page is a test target, so it must contain nothing but the device: any control of ours living there would appear in an agent's snapshot beside the app's own elements, indistinguishable from them, and an agent told to tap a button could drive DeviceDeck instead of the app under test. Checked by pointing playwright-mcp at the page — every ref it minted was an app element, the only non-app node being the mirror root — and the test now fails if a button, input, link or select ever appears there. Our controls belong on the console, which is a surface for people.
1 parent f4e77cd commit 28f1c31

3 files changed

Lines changed: 38 additions & 0 deletions

File tree

‎internal/web/static/app.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,10 @@ function renderOverlay(nodes) {
502502
const f = node.frame;
503503
if (node.depth === 0 || f.width <= 0 || f.height <= 0) continue;
504504
if (!node.identifier && !node.label && !node.hittable) continue;
505+
// Wholly off-screen: a recycled list cell parked below the fold, or
506+
// a scrollable row's overflow. It is in the tree and cannot be
507+
// pointed at, so drawing it only adds boxes with nothing under them.
508+
if (f.x >= app.width || f.y >= app.height || f.x + f.width <= 0 || f.y + f.height <= 0) continue;
505509
const el = document.createElement("div");
506510
el.className = "node";
507511
el.style.left = `${(f.x / app.width) * 100}%`;

‎internal/web/static/style.css‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ main { flex: 1; display: flex; min-height: 0; }
8787
#overlay {
8888
position: absolute;
8989
pointer-events: none;
90+
/* Clipped to the device, and to its corners. Native frames routinely
91+
extend past the screen — a horizontally scrollable row, list content
92+
below the fold — and drawn unclipped those boxes float in the black
93+
beside the device, describing elements nobody can point at. */
94+
overflow: hidden;
95+
border-radius: 24px;
9096
}
9197

9298
#overlay .node {

‎internal/web/web_test.go‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,31 @@ func TestConsoleHeaderWraps(t *testing.T) {
7373
t.Error("the app-id input must be allowed to shrink")
7474
}
7575
}
76+
77+
// The device page is a test target, so it must contain nothing but the
78+
// device. Any control of ours living there would appear in an agent's
79+
// snapshot beside the app's own elements — indistinguishable from them —
80+
// and an agent told to "tap the button" could drive DeviceDeck instead
81+
// of the app under test. The console is where our controls belong.
82+
func TestDevicePageCarriesNoControlsOfOurOwn(t *testing.T) {
83+
body := get(t, "/device/EB69B42A-4763-4A33-AF0F-CD233F721951").Body.String()
84+
// Everything between <body> and the scripts is the test target.
85+
start := strings.Index(body, "<body>")
86+
end := strings.Index(body, "<script")
87+
if start < 0 || end < 0 {
88+
t.Fatal("device page shape changed; this guard needs updating")
89+
}
90+
markup := body[start:end]
91+
for _, forbidden := range []string{"<button", "<input", "<select", "<textarea", "<a "} {
92+
if strings.Contains(markup, forbidden) {
93+
t.Errorf("device page carries %q — an agent would see it as part of the app:\n%s",
94+
forbidden, markup)
95+
}
96+
}
97+
// The mirror and the video are the only things that belong.
98+
for _, want := range []string{`id="video"`, `id="mirror"`} {
99+
if !strings.Contains(markup, want) {
100+
t.Errorf("device page is missing %s", want)
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)