Skip to content

feat: add human.selectText primitive + generator capture#63

Merged
totigm merged 7 commits into
mainfrom
feat/select-text
Jun 16, 2026
Merged

feat: add human.selectText primitive + generator capture#63
totigm merged 7 commits into
mainfrom
feat/select-text

Conversation

@totigm

@totigm totigm commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Adds human.selectText(target, { text }) — humanized text selection, done as one combined unit (primitive + MCP + codegen + generator capture). The cursor moves to the element, then selects.

Primitive

human.selectText(target) selects the element's whole text. Pass { text } to select just a substring — HumanJS finds it inside the element (whitespace-tolerant, mapped back to exact text-node offsets so the range can span inline children like a <b>) and selects only that, falling back to the whole element if the text isn't found. Reproduced by the text itself, not coordinates — consistent with the by-what-you-see selector philosophy. speed: 'instant' skips the motion and still applies the selection.

Wired across the surface

  • @humanjs/core — new 'selectText' KnownActionType (plugins observe it).
  • @humanjs/playwright — the primitive + codegen: toPlaywright / toHumanJS render await human.selectText(...), with { text } when the capture was partial.
  • @humanjs/mcphuman_selectText tool, with the optional text arg.
  • @humanjs/skill — added to the primitives table.
  • @humanjs/generator — captures the gesture: highlighting an element's whole text records a plain selectText(target); highlighting part of it records selectText(target, { text }) with the exact substring.

Capture-quality fixes (generator)

  • A drag-select no longer leaves a stray click step.
  • A triple-click's leading click no longer leaks ahead of the selectText — a click on non-interactive content is deferred and retracted by the following multi-click / selectText (order preserved for everything else).

Also riding in this PR (main-level fixes)

Bundled here rather than split into their own PR:

  • fix: align react to react-dom 19.2.7 — the generator dashboard rendered blank because React 19 throws on mismatched react / react-dom versions (a dependabot bump had moved only react-dom).
  • chore(turbo): widen the build task inputs — add dashboard/** + vite.config.ts so the bundled dashboard isn't served from a stale Turbo cache.

Verified end-to-end

  • Capture → codegen → replay: a partial highlight records { text } and a whole highlight records plain; replay selects exactly, spans inline nodes, and falls back to the whole element when the text is absent.
  • Gestures: triple-click / double-click / drag-select → selectText only (no stray click); plain-text and button clicks still record, in order.
  • @humanjs/playwright 233 tests, @humanjs/generator 40 tests; typecheck / lint / build green.

Release

Changeset: @humanjs/playwright / @humanjs/core / @humanjs/mcp minor, @humanjs/skill patch. The generator capture rides in its pending 0.1.0 (its initial-release changeset now lists "select text"). The react / turbo fixes need no changeset — apps/web is private, the generator's react / react-dom are devDeps (the dashboard is pre-bundled, no runtime impact), and turbo.json is root config.

README roadmap: the Visual generator (@humanjs/generator) item is now checked off.

Element-scoped text-selection primitive: human.selectText(target) moves the
cursor to the element (humanized) then selects its text via locator.selectText();
instant mode skips the motion. Wired across the surface — new 'selectText'
KnownActionType (@humanjs/core), the codegen renders it (toPlaywright/toHumanJS),
a human_selectText MCP tool, and the @humanjs/skill primitives table. The
generator captures it too: a triple-click / select-all / whole-element drag
emits a selectText step (multi-clicks no longer record as N clicks); free-form
cross-element ranges stay uncaptured by design. Verified end-to-end (primitive
selects text in both speed modes; capture records element-scoped only; the MCP
tool lists).
@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
humanjs Ready Ready Preview, Comment Jun 16, 2026 4:27am

So the generator's 0.1.0 changelog mentions the selectText capture (patch —
the bump stays 0.1.0 from the pending initial-release minor).
…ank)

The dependabot react-dom bump moved react-dom to 19.2.7 but left react at
19.2.6, and React 19 throws an incompatible-versions error at runtime when they
differ — so the generator's React SPA crashed to a blank page (the static
server, MIME types, and asset hashes were all correct). Bump react to ^19.2.7
across the workspace to match. Also add dashboard/** + vite.config.ts to the
turbo build inputs so dashboard edits actually invalidate the build cache (they
weren't tracked, risking a stale dist/dashboard).
…sture

A drag-select fired a click event the recorder logged alongside (or instead of)
the selection — e.g. a partial drag-select recorded only a junk click, and a
full-element drag-select recorded a click plus the selectText. A click on
non-interactive content while text is selected is part of a selection gesture,
not a navigation click, so it's now skipped. Verified: full-element drag-select
records selectText only; a partial selection records nothing; normal clicks
(interactive or not, with no selection) still record.
A real text highlight is usually part of an element's text ("es una" in
"Esto es una prueba"), which the element-scoped primitive couldn't reproduce
— so the generator recorded nothing useful for partial selections.

selectText now takes an optional { text }: it finds that text inside the
element (whitespace-tolerant, mapped back to exact text-node offsets so a
range can span inline children) and selects just it, falling back to the whole
element when the text isn't found. Reproduced by the text itself, not by
coordinates — consistent with the by-what-you-see selector philosophy.

The generator captures it: highlighting an element's whole text records a
plain selectText; highlighting part records selectText(target, { text }) with
the exact substring. Mirrored in the human_selectText MCP tool (optional text
arg), the codegen, the skill table, and the API docs.
A triple-click forms a text selection but its first click (detail=1) fires
before the selection exists, so it leaked as a stray click step ahead of the
selectText. Defer a click on non-interactive content briefly: the second click
of a multi-click (detail>=2) or the forming selectText retracts it, and any
other recorded action flushes it first so order is preserved. A lone click on
plain text still records after the short window. Also note select-text capture
in the generator's initial-release changeset.
@totigm totigm merged commit 13ca334 into main Jun 16, 2026
6 checks passed
@totigm totigm deleted the feat/select-text branch June 16, 2026 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant