can1357/oh-my-pi · error · ToolError

Drag selector did not resolve to a visible element: ${target

Error message

Drag selector did not resolve to a visible element: ${target}

What it means

`#dragPoint` resolves a string drag target to a bounding box via `#selectorBox`; when the selector resolves to nothing visible (no box), it throws this ToolError. Dragging needs geometry (x/y center), so a detached, display:none, or non-existent element cannot be used as a drag source or destination.

Source

Thrown at packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts:1031

			const rect = element.getBoundingClientRect();
			if (rect.width <= 0 || rect.height <= 0) return null;
			return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
		})()`;
		const value = await this.#evalScript<unknown>(script);
		if (!value || typeof value !== "object") return null;
		const object = value as Record<string, unknown>;
		return {
			x: numberFrom(object.x, 0),
			y: numberFrom(object.y, 0),
			width: numberFrom(object.width, 0),
			height: numberFrom(object.height, 0),
		};
	}

	async #dragPoint(target: DragTarget): Promise<{ x: number; y: number }> {
		if (typeof target === "string") {
			const box = await this.#selectorBox(this.#selectorSpec(target));
			if (!box) throw new ToolError(`Drag selector did not resolve to a visible element: ${target}`);
			return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
		}
		if (Number.isFinite(target.x) && Number.isFinite(target.y)) {
			return { x: target.x, y: target.y };
		}
		throw new ToolError("Drag target must be a selector string or { x: number, y: number } point");
	}

	async #installResponseObserver(): Promise<void> {
		await this.#evalScript<boolean>(RESPONSE_OBSERVER_SCRIPT);
	}

	async #responseCursor(): Promise<number> {
		const value = await this.#evalScript<unknown>(
			"(() => Math.max(0, ((globalThis.__ompCmuxResponses && globalThis.__ompCmuxResponses.nextId) || 1) - 1))()",
		);
		return numberFrom(value, 0);
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Confirm the selector resolves to a visible element (check offsetWidth/offsetHeight) and wait for it first with tab.waitFor.
  2. Switch to explicit coordinates: pass { x, y } instead of a selector when the element has no stable box.
  3. Fix the selector or scroll the element into view before dragging.

Example fix

// before
tab.drag('.item', '.dropzone');
// after
await tab.waitFor('.item');
await tab.drag('.item:not(.hidden)', '.dropzone'); // target the actually visible node
Defensive patterns

Strategy: validation

Validate before calling

const box = await tab.evaluate(`(() => { const el = document.querySelector(${JSON.stringify(sel)}); if (!el) return null; const r = el.getBoundingClientRect(); return (r.width > 0 && r.height > 0) ? { x: r.x, y: r.y, w: r.width, h: r.height } : null; })()`);
if (!box) throw new Error(`drag source not visible: ${sel}`);

Type guard

function isVisibleBox(box: { width: number; height: number } | null | undefined): boolean { return !!box && box.width > 0 && box.height > 0; }

Prevention

When it happens

Trigger: Dragging from/to a selector that matches no visible element: hidden element, zero-size element, wrong selector, or element not yet rendered when the drag starts.

Common situations: Drag-and-drop libraries that render only during drag (proxy handles); elements collapsed until hover; selector scoped to the wrong frame/page; animation delay leaving the element with an empty box.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/2d4f2b9f19125e2a. Report an issue: GitHub.