{"record":{"id":"e6479b40142fd26d","repo":"Hmbown/CodeWhale","slug":"hold-key-on-harmony-supports-only-hold-key-text-click","errorCode":null,"errorMessage":"hold_key on harmony supports only hold_key({\"text\":\"click\"}) = longClick","messagePattern":"hold_key on harmony supports only hold_key\\((.+?)\\) = longClick","errorType":"validation","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/harmonyos.mjs","lineNumber":252,"sourceCode":"      const dy = direction === \"up\" ? dist : direction === \"down\" ? -dist : 0;\n      return uiInput([\"swipe\", String(Math.round(target.x)), String(Math.round(target.y)), String(Math.round(target.x + dx)), String(Math.round(target.y + dy)), \"400\"]);\n    },\n    type: async ({ text }) => {\n      if (!text) return { action_sent: false, note: \"empty text\" };\n      await uiInput([\"inputText\", \"300\", \"300\", escDeviceText(text)]).catch(async (e) => {\n        // Some builds require coordinates of the focused field; retry with a click-first pattern.\n        throw e;\n      });\n      return { action_sent: true, chars: text.length, note: \"inputText at 300,300 — click the field first for focused input\" };\n    },\n    key: ({ text }) => {\n      const KEYMAP = { enter: \"Enter\", return: \"Enter\", escape: \"Esc\", esc: \"Esc\", back: \"Back\", home: \"Home\", backspace: \"Back\", delete: \"Del\", tab: \"Tab\", left: \"DPAD_LEFT\", right: \"DPAD_RIGHT\", up: \"DPAD_UP\", down: \"DPAD_DOWN\", power: \"Power\", menu: \"Menu\" };\n      const k = KEYMAP[String(text).toLowerCase()] ?? String(text);\n      if (!/^[A-Za-z0-9_]+$/.test(k)) throw new ExecError(`unsupported key \"${text}\" on harmony backend`);\n      return uiInput([\"keyEvent\", k]);\n    },\n    hold_key: async ({ text, duration }) => {\n      if (String(text).toLowerCase() !== \"click\") throw new ExecError('hold_key on harmony supports only hold_key({\"text\":\"click\"}) = longClick');\n      const d = Math.max(1, Math.min(5, Number(duration) || 1));\n      return uiInput([\"longClick\", \"300\", \"300\"]);\n    },\n    set_value: async ({ target, value }) => {\n      const b = await centerOf(target);\n      await uiInput([\"click\", String(b.cx), String(b.cy)]);\n      await new Promise((r) => setTimeout(r, 300));\n      await uiInput([\"inputText\", String(b.cx), String(b.cy), escDeviceText(String(value))]);\n      return { action_sent: true, strategy: \"uitest-element\" };\n    },\n    select_text: async () => { throw new ExecError(\"select_text is not exposed by uitest dumpLayout/uiInput on the harmony backend\"); },\n    perform_action: async ({ target, action }) => {\n      const b = await centerOf(target);\n      if (action === \"longClick\") return uiInput([\"longClick\", String(b.cx), String(b.cy)]);\n      return uiInput([\"click\", String(b.cx), String(b.cy)]);\n    },\n    read_clipboard: async () => { throw new ExecError(\"clipboard read is not exposed by hdc on current HarmonyOS builds\"); },\n    write_clipboard: async () => { throw new ExecError(\"clipboard write is not exposed by hdc on current HarmonyOS builds\"); },","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/harmonyos.mjs#L234-L270","documentation":"hold_key on the harmony backend is implemented as a single uiInput longClick at fixed coordinates (300,300) and therefore only accepts text === \"click\". Any other text throws this ExecError. The duration parameter is clamped 1-5 but the harmony uitest path does not actually vary hold length per key, so non-click holds are unsupported.","triggerScenarios":"Calling hold_key({text:\"enter\"}) or hold_key({text:\"a\", duration:3}) on the harmonyos backend — anything except text \"click\" (case-insensitive).","commonSituations":"Reusing desktop/mobile automation scripts that long-press keyboard keys; assuming hold_key generalizes key like on other backends; trying to long-press a specific element without realizing coordinates are hardcoded to 300,300.","solutions":["Use hold_key({text:\"click\"}) for long-press, and target the element first if 300,300 is not the right spot","For keyboard keys use key() instead of hold_key","Use perform_action({target, action:\"longClick\"}) to long-press a located element at its center"],"exampleFix":"// before\nawait backend.hold_key({ text: \"enter\", duration: 2 });\n// after\nawait backend.perform_action({ target: \"#send\", action: \"longClick\" });","handlingStrategy":"validation","validationCode":"if (action === \"hold_key\" && String(opts.text).toLowerCase() !== \"click\") {\n  throw new Error(\"harmony backend: hold_key supports only text=click (longClick)\");\n}","typeGuard":"const isHarmonyHoldKey = (opts) => opts && typeof opts.text === \"string\" && opts.text.toLowerCase() === \"click\";","tryCatchPattern":"try {\n  await backend.hold_key({ text: \"click\" });\n} catch (e) {\n  if (String(e.message).includes(\"hold_key on harmony\")) await backend.perform_action({ target, action: \"longClick\" });\n  else throw e;\n}","preventionTips":["Only call hold_key with text \"click\" on harmony","Prefer perform_action({target, action:\"longClick\"}) for element long-presses","Do not expect duration to control hold length on this backend"],"tags":["harmonyos","hold-key","unsupported"],"backgroundTag":"unsupported-operation","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}