diff --git a/.changeset/generator-initial-release.md b/.changeset/generator-initial-release.md index bac9039..d66381c 100644 --- a/.changeset/generator-initial-release.md +++ b/.changeset/generator-initial-release.md @@ -4,7 +4,7 @@ Initial release of `@humanjs/generator` — a visual recorder that turns a real browsing session into a clean, humanized Playwright test. -`npx @humanjs/generator ` opens a real Chromium window and a local (loopback-only) dashboard. As you click, type, scroll, drag, and navigate, each action streams into the dashboard as a step captured with a role-first selector (ARIA role + accessible name → label → text → test id → `#id` → CSS → XPath). The dashboard is a full editor: +`npx @humanjs/generator ` opens a real Chromium window and a local (loopback-only) dashboard. As you click, type, scroll, drag, select text, and navigate, each action streams into the dashboard as a step captured with a role-first selector (ARIA role + accessible name → label → text → test id → `#id` → CSS → XPath). The dashboard is a full editor: - **drag to reorder**, delete, relabel (label → comment), and edit captured values - a per-step **selector picker** over the ranked candidates diff --git a/.changeset/select-text-primitive.md b/.changeset/select-text-primitive.md new file mode 100644 index 0000000..efb9236 --- /dev/null +++ b/.changeset/select-text-primitive.md @@ -0,0 +1,11 @@ +--- +"@humanjs/playwright": minor +"@humanjs/core": minor +"@humanjs/mcp": minor +"@humanjs/generator": patch +"@humanjs/skill": patch +--- + +Add `human.selectText(target, options?)` — highlight text inside an element. The cursor moves to the element (humanized), then the text is selected — the "select this" gesture before copying, replacing, or triggering a highlight menu. Selects the element's whole text by default; pass `{ text }` to select just that substring, located inside the element whitespace-tolerantly and mapped to exact offsets (first match, falling back to the whole element if not found) — so it's reproduced by the text itself, not brittle coordinates. In `speed: 'instant'` the cursor motion is skipped; the selection is still applied. + +Mirrored as the **`human_selectText`** MCP tool (with the optional `text` arg), rendered by the recorder code generators (`toPlaywright` / `toHumanJS`), documented in the `@humanjs/skill` primitives table, and backed by a new `'selectText'` `KnownActionType` in `@humanjs/core`. `@humanjs/generator` captures the gesture too: highlighting an element's whole text records a plain `selectText`, and highlighting part of it records `selectText(target, { text })` with the exact substring. diff --git a/CLAUDE.md b/CLAUDE.md index 8f65695..43dba6b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,6 +82,8 @@ await human.clear(selector); // wipe a field: select-all + delete ( await human.check(selector); // tick a checkbox/radio (clicks only if needed) await human.uncheck(selector); // untick a checkbox await human.selectOption(selector, value);// native ` (cursor moves to it, then sets the value). | +| `human.selectText(target, options?)` | Highlight text inside an element (cursor moves to it, then selects). Selects all of it by default; pass `{ text }` to select just that substring. | | `human.upload(target, files)` | Attach file(s) to a file input (no OS dialog). | | `human.press(key)` | Single key (`'Tab'`) or chord (`'Mod+S'` — `Mod` = Meta on macOS, Control elsewhere). | | `human.read(target)` | Dwell based on word count. | diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fa941f..47392f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,28 +52,28 @@ importers: version: link:../../packages/core '@vercel/analytics': specifier: ^2.0.1 - version: 2.0.1(next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.6))(react@19.2.6))(react@19.2.6) + version: 2.0.1(next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) '@vercel/speed-insights': specifier: ^2.0.0 - version: 2.0.0(next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.6))(react@19.2.6))(react@19.2.6) + version: 2.0.0(next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) clsx: specifier: ^2.1.1 version: 2.1.1 framer-motion: specifier: ^12.40.0 - version: 12.40.0(react-dom@19.2.7(react@19.2.6))(react@19.2.6) + version: 12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) lucide-react: specifier: ^1.18.0 - version: 1.18.0(react@19.2.6) + version: 1.18.0(react@19.2.7) next: specifier: ^16.2.6 - version: 16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.6))(react@19.2.6) + version: 16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: - specifier: ^19.2.6 - version: 19.2.6 + specifier: ^19.2.7 + version: 19.2.7 react-dom: specifier: ^19.2.7 - version: 19.2.7(react@19.2.6) + version: 19.2.7(react@19.2.7) devDependencies: '@tailwindcss/postcss': specifier: ^4.3.0 @@ -140,16 +140,16 @@ importers: version: 4.7.0(vite@6.4.3(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.22.3)) framer-motion: specifier: ^12.40.0 - version: 12.40.0(react-dom@19.2.7(react@19.2.6))(react@19.2.6) + version: 12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) happy-dom: specifier: ^15.11.6 version: 15.11.7 react: - specifier: ^19.2.6 - version: 19.2.6 + specifier: ^19.2.7 + version: 19.2.7 react-dom: - specifier: ^19.2.6 - version: 19.2.7(react@19.2.6) + specifier: ^19.2.7 + version: 19.2.7(react@19.2.7) vite: specifier: ^6.0.7 version: 6.4.3(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.22.3) @@ -2796,8 +2796,8 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react@19.2.6: - resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} read-yaml-file@1.1.0: @@ -4482,15 +4482,15 @@ snapshots: dependencies: '@types/node': 25.9.1 - '@vercel/analytics@2.0.1(next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.6))(react@19.2.6))(react@19.2.6)': + '@vercel/analytics@2.0.1(next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': optionalDependencies: - next: 16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.6))(react@19.2.6) - react: 19.2.6 + next: 16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 - '@vercel/speed-insights@2.0.0(next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.6))(react@19.2.6))(react@19.2.6)': + '@vercel/speed-insights@2.0.0(next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': optionalDependencies: - next: 16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.6))(react@19.2.6) - react: 19.2.6 + next: 16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 '@vitejs/plugin-react@4.7.0(vite@6.4.3(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.22.3))': dependencies: @@ -5016,14 +5016,14 @@ snapshots: forwarded@0.2.0: {} - framer-motion@12.40.0(react-dom@19.2.7(react@19.2.6))(react@19.2.6): + framer-motion@12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: motion-dom: 12.40.0 motion-utils: 12.39.0 tslib: 2.8.1 optionalDependencies: - react: 19.2.6 - react-dom: 19.2.7(react@19.2.6) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) fresh@2.0.0: {} @@ -5317,9 +5317,9 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@1.18.0(react@19.2.6): + lucide-react@1.18.0(react@19.2.7): dependencies: - react: 19.2.6 + react: 19.2.7 magic-string@0.30.21: dependencies: @@ -5373,16 +5373,16 @@ snapshots: negotiator@1.0.0: {} - next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.6))(react@19.2.6): + next@16.2.6(@playwright/test@1.60.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@next/env': 16.2.6 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.31 caniuse-lite: 1.0.30001793 postcss: 8.4.31 - react: 19.2.6 - react-dom: 19.2.7(react@19.2.6) - styled-jsx: 5.1.6(react@19.2.6) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + styled-jsx: 5.1.6(react@19.2.7) optionalDependencies: '@next/swc-darwin-arm64': 16.2.6 '@next/swc-darwin-x64': 16.2.6 @@ -5542,14 +5542,14 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - react-dom@19.2.7(react@19.2.6): + react-dom@19.2.7(react@19.2.7): dependencies: - react: 19.2.6 + react: 19.2.7 scheduler: 0.27.0 react-refresh@0.17.0: {} - react@19.2.6: {} + react@19.2.7: {} read-yaml-file@1.1.0: dependencies: @@ -5794,10 +5794,10 @@ snapshots: strip-bom@3.0.0: {} - styled-jsx@5.1.6(react@19.2.6): + styled-jsx@5.1.6(react@19.2.7): dependencies: client-only: 0.0.1 - react: 19.2.6 + react: 19.2.7 sucrase@3.35.1: dependencies: diff --git a/turbo.json b/turbo.json index 845d986..8f74df6 100644 --- a/turbo.json +++ b/turbo.json @@ -4,7 +4,14 @@ "tasks": { "build": { "dependsOn": ["^build"], - "inputs": ["src/**", "tsconfig.json", "tsup.config.ts", "package.json"], + "inputs": [ + "src/**", + "dashboard/**", + "tsconfig.json", + "tsup.config.ts", + "vite.config.ts", + "package.json" + ], "outputs": ["dist/**"] }, "dev": {