aboutsummaryrefslogtreecommitdiff
path: root/src/domains/form-batch.ts
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmail.com>2026-07-28 19:55:53 +0530
committerAman Kumar <145339995+amankumarsingh77@users.noreply.github.com>2026-08-02 19:12:46 +0530
commitcfa1892e17e3696da004c71a4942d5b12186b12c (patch)
tree228b15fc7ada4193bdcabeaf913421a2958215ce /src/domains/form-batch.ts
parent254de089ee468795bac175d99e09ddaec4ac1469 (diff)
fix(domains): one fill engine, so browser_fill stops lying about checkboxes
browser_fill and browser_fill_form carried separate in-page implementations that disagreed on the same element. The worst case was silent: browser_fill on a checkbox fired the HTMLInputElement value setter, which writes the submit value and leaves `checked` alone, then reported status ok — a no-op the agent had no reason to doubt. Both tools now share one engine, with browser_fill's documented <select> rejection kept as an explicit pre-check and per-tool focus behaviour kept as a parameter. Deliberate, user-approved behaviour changes: - browser_fill on a DISABLED input now returns an error instead of success. The old success was the bug; browser_fill_form already refused it. - browser_fill on a checkbox/radio now toggles `checked` instead of the value. - browser_fill_form on a contenteditable now uses execCommand("insertText"), so it drives ProseMirror/Slack-style editors as browser_fill already did. browser_fill keeps appending interactiveDiff and republishing refs; fill_form still does not. Report: docs/superpowers/reports/2026-07-26-bug-audit.md#f5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'src/domains/form-batch.ts')
-rw-r--r--src/domains/form-batch.ts55
1 files changed, 2 insertions, 53 deletions
diff --git a/src/domains/form-batch.ts b/src/domains/form-batch.ts
index c4b4df6..1d49cf6 100644
--- a/src/domains/form-batch.ts
+++ b/src/domains/form-batch.ts
@@ -1,61 +1,10 @@
import { Type } from "typebox";
import { type Result, err, ok } from "../util/result";
import { defineBrowserTool, type ToolErr, type ToolOk } from "../util/tool";
+import { fillDeclaration } from "./fill-engine";
import { detailsOf, resolveAndCall } from "./form";
-const FILL_FN = `
-function(value) {
- const el = this;
- if (!el || el.nodeType !== 1) return { ok: false, reason: "ref does not point to an element" };
- if (el.disabled) return { ok: false, reason: "element is disabled" };
- const tag = (el.tagName || "").toLowerCase();
- const type = (el.type || "").toLowerCase();
- try { el.focus(); } catch (e) {}
- const fire = function(t) { el.dispatchEvent(new Event(t, { bubbles: true })); };
-
- if (tag === "select") {
- const want = String(value);
- let matched = null;
- for (let i = 0; i < el.options.length; i++) {
- const o = el.options[i];
- if (o.value === want || o.label === want || o.text === want) { matched = o; break; }
- }
- if (!matched) {
- const opts = [];
- for (let i = 0; i < el.options.length; i++) opts.push({ value: el.options[i].value, text: el.options[i].text });
- return { ok: false, reason: "no matching option", kind: "select", options: opts };
- }
- el.value = matched.value;
- fire("input"); fire("change");
- return { ok: true, kind: "select", value: el.value, text: matched.text };
- }
-
- if (tag === "input" && (type === "checkbox" || type === "radio")) {
- const want = value === true || value === "true" || value === "on" || value === 1;
- if (el.checked !== want) {
- try { el.click(); } catch (e) {}
- if (el.checked !== want) { el.checked = want; fire("input"); fire("change"); }
- }
- return { ok: true, kind: type, checked: el.checked };
- }
-
- if (el.isContentEditable) {
- el.textContent = String(value);
- fire("input"); fire("change");
- return { ok: true, kind: "contenteditable", value: el.textContent };
- }
-
- if (tag === "input" || tag === "textarea") {
- const proto = tag === "textarea" ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype;
- const desc = Object.getOwnPropertyDescriptor(proto, "value");
- if (desc && desc.set) desc.set.call(el, String(value));
- else el.value = String(value);
- fire("input"); fire("change");
- return { ok: true, kind: tag, value: el.value };
- }
-
- return { ok: false, reason: "element is not a fillable field (tag=" + tag + ")" };
-}`;
+const FILL_FN = fillDeclaration({ rejectSelect: false, focusFirst: true });
const FieldValue = Type.Union([Type.String(), Type.Boolean()], {
description: "Value to set. String for text inputs / textareas / selects / contenteditable; boolean for checkboxes & radios.",