Skip to content

feat(generator): @humanjs/generator v0.1 — visual recorder + editor#59

Merged
totigm merged 24 commits into
mainfrom
feat/generator
Jun 15, 2026
Merged

feat(generator): @humanjs/generator v0.1 — visual recorder + editor#59
totigm merged 24 commits into
mainfrom
feat/generator

Conversation

@totigm

@totigm totigm commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Ships @humanjs/generatornpx @humanjs/generator <url> launches a real Chromium window and a local (loopback-only) dashboard, captures your clicks/typing/scrolls/navigation with role-first selectors, and exports a clean, humanized Playwright test from a full in-browser editor.

What's in it

@humanjs/generator (new package, → 0.1.0)

  • CLI launches headed Chromium + a localhost dashboard (HTTP serves the built SPA, falls back to an embedded placeholder; WebSocket carries live state).
  • In-page recorder (bundled IIFE injected via addInitScript) with ranked selector inference: ARIA role + accessible name → label → text → test id → #id → CSS → XPath.
  • Vite + React editor: drag-to-reorder (framer-motion), delete, relabel, edit captured values, a per-step selector picker, point-and-add assertions (toBeVisible/toHaveText/toHaveURL), a secret toggle (→ process.env.X), a personality switcher, and a live, syntax-highlighted code preview. Themed to match humanjs.dev (warm/orange).
  • Export to a @humanjs/playwright/test spec (.spec.ts) or a standalone HumanJS script (.ts); the curated timeline runs through @humanjs/playwright's codegen, so generated specs stay in lockstep with the library.

@humanjs/playwright (→ minor)

  • Export generateHumanJS / generatePlaywrightTest from the package root (the codegen the generator builds on).
  • generatePlaywrightTest renders explicit assert timeline events.
  • createHuman installs the visual cursor overlay by default (opt out with cursor: false; the test fixture opts out in CI).

Capture-quality fixes (from real-world testing)

  • Navigations caused by a recorded gesture (clicked link, form submit, search-as-you-type) no longer double-record as a goto.
  • Only real user scrolls (wheel / trackpad / touch / scrollbar drag) are recorded; programmatic scrolls (SPA scroll-restoration, gallery resets, anchor jumps) are dropped — detected by patching the page's scroll APIs.
  • A scrollbar drag is no longer mis-recorded as a content drag (xpath=//).
  • A drag that fires a trailing click on release no longer records a stray click.
  • Password fields are always masked.

Tests

  • @humanjs/generator: 40 tests (timeline store edit ops, export/codegen integration, dashboard server incl. asset serving + path-traversal rejection, URL normalization). Capture pipeline verified end-to-end against local fixtures in a real browser.
  • @humanjs/playwright: full suite green (incl. the new assert codegen + cursor default).

Release

Changesets queue: @humanjs/playwright minor (codegen exports, assert events, cursor default), @humanjs/generator 0.1.0 initial, plus dependency-bump patches for mcp/recorder.

Note

The README editor screenshot/GIF is intentionally omitted for now — to be added later.

totigm added 23 commits June 2, 2026 19:08
…aywright

Milestone 1 of the visual recorder v0.1: package shell (mirrors @humanjs/mcp
bin-package template, version 0.0.0) with the CLI entry, README, and ROADMAP.
Also surfaces generateHumanJS / generatePlaywrightTest from the @humanjs/playwright
root so the generator can turn a curated Timeline into code without reimplementing
codegen.
Milestone 2: `npx @humanjs/generator <url>` now starts a loopback-only
dashboard server (HTTP serves the built SPA when present, else an embedded
placeholder; WebSocket carries the live channel), launches a real headed
Chromium at the target URL, opens the dashboard in the default browser, and
ties browser-close / SIGINT to a clean shutdown. URL normalization defaults
bare hosts to https (loopback to http). Transport covered by integration
tests (placeholder serving, 404, WS broadcast).
Milestone 3: an injected IIFE recorder (bundled from src/injected via a second
tsup target) captures click / rightClick / type / check / uncheck /
selectOption / press / scroll in the page and posts each through the
__humanjsEmit binding; the Node side adds main-frame navigations as goto steps,
stamps tMs, and streams the live timeline to the dashboard over WS. Selector
inference ranks ARIA role+name, aria-label, visible text, test id, unique #id,
CSS path, then XPath — pure and unit-tested via happy-dom. The placeholder
dashboard now renders the live steps.
…esses

Field feedback: typing wasn't recorded (keyed off the unreliable change event)
and Backspace/Delete inside a field spammed press steps. Text capture now keys
off input (debounced, flushed on blur and before any following action so
type-then-click order holds); password values stay masked. Editing keys
(Backspace/Delete/arrows) no longer emit press inside fields — only
Enter/Tab/Escape do. Adds drag capture: native HTML5 DnD plus pointer drags
(movement threshold + text-selection guard).
generatePlaywrightTest now maps assert timeline events to @playwright/test
assertions — visible -> toBeVisible, text -> toHaveText, url -> toHaveURL —
interleaved in recorded order, pulling page + expect into the test. The
standalone generateHumanJS script export filters them out. Lets timeline-
producing tooling (the generator) emit intentional assertions.
Milestone 4: generate a .spec.ts / .ts from the captured timeline via the
shared codegen. New export.ts wraps the timeline with the chosen
personality/seed/speed and substitutes process.env.* for secret-flagged
type/paste steps (sentinel round-trip, works even when the value was masked);
formatFromFilename maps .spec/.test -> spec. The CLI streams a live code
preview over WS and writes humanjs-recording.spec.ts / .ts on an export
command; the placeholder dashboard gained a preview pane and Export buttons.
Covered by unit tests (format, personality, secrets, assertions) and verified
end-to-end against a real captured flow.
Milestone 5a: the CLI now owns a canonical, id-stamped TimelineStore (append /
delete / move / update / addAssert), and the WS protocol carries editor
commands (delete, move, update, addAssert, setPersonality, export) with the CLI
broadcasting a full state snapshot (steps + personality + live code) after every
capture or edit. Store logic is unit-tested. The placeholder dashboard now
renders the state snapshot read-only; the React editor UI replaces it next.
…crets

Milestone 5b: a Vite + React dashboard built into dist/dashboard and served by
the CLI (placeholder remains the fallback). Renders the live timeline with
delete, drag-reorder, relabel, captured-value editing, a per-step selector
picker over the ranked candidates, point-and-add assertions, a secret toggle
(emit process.env.*), a personality switcher, and a live code preview that
refreshes on every edit. Dedicated vitest.config keeps tests at the package
root (vite.config sets the dashboard root); the server gains a testable
dashboardDir, with tests for real asset serving and traversal rejection.
Editor polish: replace native <select> with a themed, animated, keyboard-
accessible Select (button-based options); the live preview is now
syntax-highlighted via a small dependency-free TS tokenizer (HTML-escaped,
safe for captured values); and reordering uses framer-motion's Reorder so
steps slide out of the way live as you drag a handle, with the CLI applying a
new full-order 'reorder' command. Aligns the dashboard to React 19 +
framer-motion to match the landing.
Editor refinements: the custom select panel now sizes to its content (short
options like 'visible' no longer wrap) with an aligned check column, and opens
upward when there isn't room below. The live preview renders line-by-line with
framer-motion layout animation, so lines slide to their new positions when the
timeline is reordered (new lines fade in, removed fade out).
…ollbars

Three editor fixes from testing: (1) a pointer-drag on a carousel/slider that
fires a trailing click on release no longer records a stray click step — the
recorder swallows the click that immediately follows a captured drag; (2) the
step list smooth-scrolls to the bottom as new steps stream in, but only when
the user is already at the bottom (scrolled-up inspection is left alone); and
(3) themed thin scrollbars across the editor, preview, and dropdowns.
Long role=...[name="…"] selectors gave the preview's no-wrap code lines a
huge min-content width; CSS grid's default min-width:auto then let the preview
track grow and squeeze the editor. Set min-width:0 on both columns so they
hold their 1fr split and long lines scroll horizontally inside the preview.
Autoscroll now defers the scroll to the next animation frame so it measures
scrollHeight after the new row lays out (framer-motion was mid-animation), so
it reaches the actual bottom instead of stopping short. The live preview
soft-wraps long lines (with a hanging indent for continuations) instead of
scrolling horizontally — a read-only preview reads better top-to-bottom,
especially with the very long selector names some pages produce.
…eset

Milestone 6: rewrite the README for the v0.1 launch (how-it-works, editor
capabilities, sample output, requirements, screenshot slot), update the
docs/DESIGN generator section to reflect it's shipping with a cross-link, and
add the minor changeset that publishes @humanjs/generator@0.1.0.
Re-theme the dashboard from the placeholder emerald to the landing's actual
palette (apps/web/app/globals.css): warm dark surfaces (#060604 / #0c0b0a /
#110f0d), warm text (#f0ece5 / #8a857c), and the orange accent (#f5a55c, bright
#ffb87a). Warmed the field/dropdown/scrollbar neutrals to suit. Syntax-highlight
token colors are intentionally left as their own balanced code palette (an
all-orange code pane would be unreadable). Fallback placeholder updated too.
createHuman now calls installMouseHelper on its page by default, so humanized
motion is visible in headed runs and recordings without a manual call — the
generator's / Recording.toHumanJS() exported scripts now show the cursor when
run. Opt out with cursor: false (for speed: 'instant' / CI, where there's no
motion and the injected cursor would pollute test DOM/screenshots); pass an
options object to style it. The test fixture opts out automatically in CI. The
install is page-scoped, idempotent, and skipped on page objects lacking
addInitScript (unit-test mocks).
…ded gesture

Clicking a link (or submitting a form, or search-as-you-type) was captured as
both the action AND a goto for the resulting navigation, so replays navigated
twice. The recorder now pings a throttled gesture sentinel on pointerdown /
keydown, and the CLI skips a main-frame goto that lands within 2.5s of the last
gesture — that navigation is reproduced by replaying the gesture. Navigations
with no recent gesture (initial load, address-bar nav) are still captured.
Verified end-to-end: a link click records the click only, no trailing goto.
SPA routers reset scroll to (0,0) on client-side navigation, firing a scroll
event the recorder captured as a no-op scroll({to:0}) step. The CLI now tracks
the last main-frame navigation and drops a scroll-to-top (offset <= 3) that
lands within 1.5s of one — it's scroll-restoration, not a user scroll. Gated on
navigation timing (not gesture) so genuine keyboard scrolls (PageUp/Home) are
kept. Verified end-to-end: a real scroll-down is recorded, the post-pushState
scroll-to-0 is dropped.
Generalizes the navigation scroll-to-top fix: the recorder now records a scroll
only when it was physically driven by the user (a wheel / trackpad / touch
input). Programmatic scrolls — SPA scroll-restoration on navigation, a gallery
resetting page scroll on arrow keys, anchor jumps, scroll-to-top buttons — have
no scroll input and are already reproduced by the action that caused them, so
they're no longer captured as no-op scroll steps. Keyboard scrolling stays
covered by the recorded key press. Replaces the narrower CLI-side nav-gated
scroll-to-0 suppression. Verified: a wheel scroll is recorded; programmatic
scrollTo calls are not.
…ic/keyboard scrolls

Flips the scroll model so it records every real user scroll — wheel, trackpad,
touch, AND dragging the scrollbar (which fires no wheel and, with overlay
scrollbars, no pointer event either) — and drops only scrolls it can prove are
side-effects: programmatic ones (the recorder patches window/Element
scroll/scrollTo/scrollBy/scrollIntoView and the scrollTop/scrollLeft setters to
timestamp themselves) or keyboard ones (already reproduced by the recorded
press). Replaces the wheel-only allowlist, which missed scrollbar drags.
Verified: wheel kept; scrollTo and scrollTop= suppressed; PageDown → press.
Dragging the scrollbar was caught by the pointer-drag detector and emitted a
bogus drag('xpath=//','xpath=//') (the gesture's element is the root <html>,
which has no real selector) alongside the correct scroll. The pointer-drag
detector now skips a gesture that scrolled the page during the press (scrollbar
drag / drag-to-edge autoscroll — a scroll, not a content drag) or that starts
on the root/body (no meaningful source, degenerate selector). Verified: a real
content drag still records; a scrollbar-style gesture records only the scroll.
@vercel

vercel Bot commented Jun 15, 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 15, 2026 10:50am

@totigm totigm merged commit 39d87f3 into main Jun 15, 2026
6 checks passed
@totigm totigm deleted the feat/generator branch June 15, 2026 10:55
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