Commit Graph

1008 Commits

Author SHA1 Message Date
Tommy D. Rossi e9339a3ecc fix: send auth token on remote status check routes
When using `--token` with a remote `--host`, the CLI and executor
status check fetches (`/extensions/status`, `/extension/status`) were
not sending the bearer token. The host validation middleware rejects
non-localhost requests without a valid token, so remote `session new`
and `session list` would silently fail with "no connected browsers".

**cli.ts**: `fetchExtensionsStatus` now accepts `{ host, token }` and
passes auth headers via `buildAuthHeaders` to both status endpoints.

**executor.ts**: `checkExtensionStatus` reads `token` from
`this.cdpConfig` (falling back to `PLAYWRITER_TOKEN` env) and attaches
`Authorization: Bearer` header to all status fetch calls.

`relay-client.ts` left unchanged since those functions are hardcoded
to `127.0.0.1` (local-only, always passes host validation).

Closes #91
Fixes #85

Session: ses_1b4c572a4ffev2Dct63TjK77gJ
extension@0.0.94
2026-05-21 17:53:18 +02:00
Tommy D. Rossi 613f6b99d9 updates 2026-05-19 16:11:58 +02:00
Tommy D. Rossi bc0e044095 updates 2026-05-19 16:07:16 +02:00
Tommy D. Rossi 70f8deac7b Add Host header validation to prevent DNS rebinding attacks
DNS rebinding lets an attacker serve a page from http://evil.com:19988,
then rebind the DNS to 127.0.0.1. The browser treats subsequent requests
as same-origin, bypassing Sec-Fetch-Site, CORS preflight, and
Content-Type enforcement. This gives full access to privileged HTTP
routes like /cli/execute without any existing defense catching it.

The new global middleware rejects requests whose Host header hostname is
not localhost, 127.0.0.1, [::1], or ::1. Requests carrying a valid
bearer/query token bypass the check so remote clients still work.

The Host parser handles IPv6 brackets, bare ::1, case-insensitive
hostnames, and port suffixes via indexOf instead of a fragile regex.

Unit tests use raw http module (Node fetch forbids overriding Host) to
verify evil.com is rejected and all localhost variants pass, including
uppercase LOCALHOST.

Session: ses_1bfec4b08ffeL3NJunw0EC9plq
2026-05-19 14:03:02 +02:00
Tommy D. Rossi 668d97eb00 release: playwriter@0.2.0
Merges all changes since 0.1.0 (last published npm version):

- New -f/--file flag for executing JS from files
- React component inspection for pinned elements
- Performance profiling guide resource
- Shell tab completions via goke
- Security: token required on all requests regardless of source
- --token works on every remote subcommand
- Fixed Next.js webpack layer prefixes in React source paths
- Skill docs require absolute paths for saved artifacts

Session: ses_1bff6e68fffe5Ls42bfN8RUyVp
playwriter@0.2.0
2026-05-19 13:46:21 +02:00
Tommy D. Rossi c49d3e5d60 Update index.mdx 2026-05-18 19:34:17 +02:00
Tommy D. Rossi 6a3f2c481c Update docs.json 2026-05-18 19:34:15 +02:00
Tommy D. Rossi 7eafb25c3c Update index.mdx 2026-05-18 18:54:32 +02:00
Tommy D. Rossi 614087dab6 Update docs.json 2026-05-18 18:51:49 +02:00
Tommy D. Rossi 23fe8be8ef updates 2026-05-18 18:50:12 +02:00
Tommy D. Rossi 5e247bce25 updates 2026-05-18 17:37:04 +02:00
Tommy D. Rossi aa1183c23d feat(extension): update pin copy toast to guide users toward agent prompt usage
The old toast just said "Copied pin #N" which gave no hint about what to do next. Now it says "Copied playwriter element reference, use it in your agent prompt" so users know to paste it into their AI agent.

Bump extension to 0.0.94.

Session: ses_1d03be05affeS58tMdQDs6GMbj
2026-05-16 09:52:49 +02:00
Tommy D. Rossi 014fd520ce docs(skill): add -f/--file execute-from-file section
Add "Execute from file" subsection to skill.md documenting the -f flag
for executing JavaScript from a file instead of inline -e code.

Session: ses_1d0419706ffelv53Gc4OrfwZ4L
2026-05-16 09:48:52 +02:00
Tommy D. Rossi a77f5ad861 chore(deps): bump vite ^8.0.3 to ^8.0.12 and update lockfile
Update vite in root and extension packages from ^8.0.3 to ^8.0.12.

Lockfile reflects all transitive dependency updates from this session:
goke 6.12.1, @holocron.so/vite 0.8.0, dompurify 3.4.3, katex 0.16.46,
and various @csstools patches.
2026-05-16 09:45:37 +02:00
Tommy D. Rossi a9f8a0770f feat(website): upgrade @holocron.so/vite to 0.8.0 and update docs
Upgrade @holocron.so/vite from ^0.7.1 to ^0.8.0.

Use the new `<Logo>` component in the hero section instead of a plain
`<h1>`, and switch `<img>` to the framework `<Image>` component.

Add icon fields to the GitHub and Releases anchors in docs.json
(github, log). Fix docs.json indentation.

Add website/styles.css with Tailwind `@source` directive for the
new holocron vite plugin version.
2026-05-16 09:45:33 +02:00
Tommy D. Rossi d02fa5df17 feat(cli): add -f/--file flag, shell completions, and unknown command errors
Add `-f, --file <path>` option so users can execute JavaScript from a file
instead of inline `-e` strings. The two flags are mutually exclusive.

Enable `cli.completions()` from goke for shell tab completion support.

Change `cli.parse()` to `await cli.parse()` so async command actions
properly propagate errors and exit codes.

Add tests for unknown command and unknown subcommand error handling,
verifying they exit with code 1 and print helpful messages.

Bump goke from ^6.6.0 to ^6.12.1 which includes the completions API
and improved error messages for unknown commands.
2026-05-16 09:45:25 +02:00
Tommy D. Rossi 4de435ef21 update playwright submodule pointer: vite ^8.0.12
Session: ses_1da6d3925ffeK4qAOv03OUyIfl
2026-05-14 11:20:37 +02:00
Tommy D. Rossi d49afbb845 update pnpm-lock.yaml after website migration
Lockfile reflects:
- removed holocron local package and its deps
- added @holocron.so/vite and transitive deps (mermaid, holocron components, etc.)
- updated spiceflow 1.18.0-rsc.22 -> 1.25.1-rsc.0
- updated vite 8.0.3 -> 8.0.12 (rolldown 1.0.0-rc.12 -> 1.0.0)
- updated react 19.2.3 -> 19.2.6
- various transitive dep bumps

Session: ses_1da6d3925ffeK4qAOv03OUyIfl
2026-05-14 10:35:42 +02:00
Tommy D. Rossi 9e94ada7d7 migrate website to @holocron.so/vite
Replaced the custom editorial page (spiceflow + tailwind + safe-mdx + prism)
with the published @holocron.so/vite docs framework.

Changes:
- vite.config.ts: replaced spiceflow/tailwind/tsconfigPaths plugins with holocron()
- package.json: swapped deps (removed safe-mdx, prismjs, tailwind, sharp, image-size,
  orama; added @holocron.so/vite and zod). Updated spiceflow to 1.25.1-rsc.0
- Added docs.json holocron config (logo, nav, colors, footer)
- Added src/index.mdx as the single docs page (Hero, Install, Quick start, etc.)
- tsconfig.json: simplified (removed paths, allowJs, allowArbitraryExtensions)
- .gitignore: added /dist, removed .react-router

Session: ses_1da6d3925ffeK4qAOv03OUyIfl
2026-05-14 10:35:35 +02:00
Tommy D. Rossi e2dab3d8e8 remove local holocron/ package
The holocron docs framework has been published as @holocron.so/vite on npm.
The local package is no longer needed in this repo.

Removed files:
- holocron/src/ (vite plugin, components, styles, config, sync, mdx processing)
- holocron/AGENTS.md
- holocron/package.json, tsconfig.json, schema.json

Session: ses_1da6d3925ffeK4qAOv03OUyIfl
2026-05-14 10:35:23 +02:00
Tommy D. Rossi bcd1e25ea2 docs: document changesets workflow for agents
Replace the old changelog instructions with the repo changesets workflow so future package changes add .changeset entries instead of editing generated CHANGELOG.md files or manually bumping public package versions.

Keep the Chrome extension manifest bump requirement separate because extension releases are not driven by npm changesets, and add a .changeset/readme.md so agents can quickly see what belongs in the folder.

Move the pending playwriter typecheck emit change into a changeset and revert the manual package version bump from the prior commit, leaving version updates to the publish flow.

Session: ses_1f763e4ecffenZcbrWQbx0Oh5F
2026-05-09 08:38:06 +02:00
Tommy D. Rossi a3ce2146ff fix: keep playwriter typecheck emitting dist
Run the playwriter package typecheck script through plain tsc instead of tsc --noEmit so local validation keeps compiled dist files up to date.

Bump the package version to 0.1.7 so the local dist-backed shim reports the updated package version after rebuilding.

Session: ses_1f763e4ecffenZcbrWQbx0Oh5F
2026-05-08 19:43:31 +02:00
Tommy D. Rossi d1346e7ca4 docs: add profano CPU profiling tips to performance resource
Mention .cpuprofile recording via CDP Profiler.* commands and
profano CLI for inspecting hot functions when vitals alone are
not enough to diagnose performance issues.

Session: ses_208943d84ffenqdbjxK0cO0iak
2026-05-05 11:39:01 +02:00
Tommy D. Rossi 7195359b22 fix: strip Next.js webpack layer prefixes from React source paths
bippy.normalizeFileName strips "/app-pages-browser/" but not the
parenthesized form "/(app-pages-browser)/" that Next.js webpack
actually uses in source maps. Added cleanFileName/cleanName that
runs after normalizeFileName and strips any /(layer-name)/ prefix
using regex, plus trailing "./".

Covers all Next.js webpack layer names: (app-pages-browser), (ssr),
(rsc), (action-browser), (pages-dir-browser), (pages-dir-edge),
(pages-dir-node).

Applied in both:
- Extension context menu handler (background.ts)
- MCP react-source.ts (getReactSource + getReactComponentInfo)

Also applied oracle review fixes:
- userGesture: true on clipboard Runtime.evaluate calls to prevent
  NotAllowedError when page loses focus after context menu
- Defensive fileName null check before building clipboard text

Session: ses_208943d84ffenqdbjxK0cO0iak
2026-05-05 11:38:36 +02:00
Tommy D. Rossi d0c93e0f3a feat(extension): add "Copy React Component Source Path" right-click menu item
New context menu item that copies the React component source file path
to clipboard when right-clicking an element on a React page.

Uses bippy (React fiber introspection library) to walk the fiber tree
from the right-clicked DOM element up to the nearest composite component
with source location info. Tries getSource() first (direct __source prop
from JSX transform), then falls back to getOwnerStack() for builds with
source maps.

Copies "path/to/file.tsx:42" format to clipboard on success (green flash),
shows red flash if the element is not part of a React app or no source
maps are available.

Also refactored all inline JS strings in the context menu handlers to use
string-dedent with descriptive variable names for syntax highlighting.

extension@0.0.93

Session: ses_208943d84ffenqdbjxK0cO0iak
2026-05-05 11:24:28 +02:00
Tommy D. Rossi 10790c7392 auth: drop loopback bypass, attach token from env in in-process callers
Reviewer flagged the loopback-bypass added in 0.1.5 as a full auth
bypass under the recommended remote-access topology: tunnel agents
(traforo, ngrok, cloudflared) run as local processes and forward
public traffic to localhost:19988, so every public request reaches
the relay from 127.0.0.1. The bypass made `/cli/execute` (RCE),
`/cli/session/*`, `/recording/*` (file writes via outputPath), and
`/mcp-log` callable by anyone with the public tunnel URL.

Fix
---
1. cdp-relay.ts: privilegedRouteMiddleware now requires the token on
   every request, regardless of source. Loopback bypass removed.
2. cli.ts (`serve` action): exports `process.env.PLAYWRITER_TOKEN` at
   startup so in-process callers can attach Authorization: Bearer ...
   when calling the relay's own privileged endpoints.
3. screen-recording.ts: new `recordingHeaders()` helper reads
   PLAYWRITER_TOKEN from env and attaches the Bearer header to all
   four /recording/* calls (start, stop, status, cancel). Mirrors
   what mcp.ts already does for /mcp-log.
4. cli.ts (`executeCode`): refactored the inline header-build into
   the shared `buildAuthHeaders` helper so the six /cli/* fetch
   sites stay in sync.
5. connect-cdp-demo.ts: replaced the hardcoded 'secret_token'
   fallback with a hard error so the demo can never accidentally be
   the source of a leaked credential warning.

Bumps playwriter to 0.1.6 + CHANGELOG entry. Adds a MEMORY.md note
so the next person doesn't re-introduce the bypass.

Session: ses_20d25dcc7ffeJhVK7aC8Zh7OkW
2026-05-04 17:09:28 +02:00
Tommy D. Rossi db3280d8e7 Regenerate AGENTS.md with changesets workflow
Replaces old CHANGELOG.md instructions with changesets. Also picks up
latest shared snippet updates from agentsdotmd@1.1.5.

Session: ses_20d092d48ffey0lmjedA7ZMTed
2026-05-04 15:21:26 +02:00
Tommy D. Rossi 3d6f2d4239 auth: thread token through every remote CLI command + protect /mcp-log
Builds on Ivan Leo's auth-header pass with the missing pieces needed to
actually run `playwriter serve --token` end to end against a remote relay.

Fixes
-----
1. `--token` (or PLAYWRITER_TOKEN) now plumbed through every remote
   subcommand: `session new/list/delete/reset` and `browser list`. Until
   now only `playwriter -e` sent the Bearer header, so against a token-
   protected relay every other subcommand returned 401.
2. In-process `ExecutorManager` carries `token` in its `cdpConfig`, so the
   executor's own `chromium.connectOverCDP(ws://127.0.0.1:19988/cdp/<id>)`
   call passes the relay's own auth check.
3. `/mcp-log` is now under `privilegedRouteMiddleware`. Previously open,
   so any reachable client could spam the log file.
4. `mcp.ts` `sendLogToRelayServer` sends `Authorization: Bearer …` when
   `PLAYWRITER_TOKEN` is set.
5. Loopback connections (127.0.0.1, ::1, ::ffff:127.0.0.1) bypass the
   token check on `/cli/*`, `/recording/*`, and `/mcp-log`. They can only
   originate from in-process executor code which already runs downstream
   of an authenticated `/cdp/*` WebSocket, so re-checking the token would
   mean smuggling it into every internal helper for no real security
   gain. Resolves the broken `/recording/*` calls from
   `screen-recording.ts`.
6. New `connect-cdp-demo.ts` script: minimal example showing an external
   Playwright client connecting through `/cdp/<id>?token=<token>` over a
   tunnel.

Bumps `playwriter` to 0.1.5 and updates CHANGELOG.

Co-authored-by: Ivan Leo <ivanleomk@gmail.com>

Session: ses_20d25dcc7ffeJhVK7aC8Zh7OkW
2026-05-04 14:10:42 +02:00
Tommy D. Rossi ae136e91ec fix: copy pinned element command directly
Remove the natural-language wrapper around pinned element clipboard text so toolbar and context-menu pins copy a shell-ready playwriter command. This keeps the copied text compact while still delegating DOM and React inspection to inspectPinnedElement.

Session: ses_221fdd7c5ffexkyCmOtfmgwjoI
2026-04-30 17:26:21 +02:00
Tommy D. Rossi fabd25e8f6 feat: add React component info for pinned elements
Expose getReactComponentInfo so agents can inspect the React component name, parent hierarchy, sanitized props, and source metadata for a locator or element handle. Non-React elements return null so the helper is safe to call from generic DOM inspection flows.

Update toolbar and context-menu pinned element copy to use a short inspectPinnedElement(url, expression) call. The wrapper resolves the pinned element, logs outerHTML plus React info internally, and keeps copied prompts small.

Session: ses_221fdd7c5ffexkyCmOtfmgwjoI
2026-04-30 15:51:45 +02:00
Tommy D. Rossi 480c1b9d64 docs: add performance profiling guide resource
Add a generated website resource that shows how to profile a real site with Playwriter using CDP, Navigation Timing, and PerformanceObserver.

The new guide focuses on the questions people actually ask during performance work: whether rendering is good or bad, which requests dominate transfer cost, what usually blocks first paint or LCP, and what is most likely blocking interactivity. It also includes ready-to-run Playwriter commands plus reusable TypeScript examples so the profiling workflow can be copied directly.

Session: ses_22252b7b8ffee2VFXqmaCzA2NR
2026-04-30 12:24:34 +02:00
Tommy D. Rossi e9918fcadc docs: require absolute paths for saved artifacts
Document that Playwright artifact APIs resolve paths inside Playwright client code instead of the sandboxed fs, so relative paths can land under the relay cwd.

Update the skill examples to use absolute paths for screenshots, PDFs, downloads, resize inputs, and recordings, and bump playwriter to 0.1.1 so the guidance ships with the release notes.

Session: ses_2411fc3d3ffe2bK1VDhxJd0u15
2026-04-24 13:11:31 +02:00
Tommy D. Rossi a145e5c5d5 simplify pinned element clipboard snippet to just outerHTML
Removed the verbose describeElement() summary (tag, id, classes, role, aria,
href, type, text, rect, visible) — all redundant since the agent runs the code
and sees outerHTML directly. Also fixed the evaluate call to use a static
property access (globalThis.playwriterPinnedElem1) instead of passing the
pin number as a runtime argument.

Bumps extension to 0.0.90.

Session: ses_25fb67764ffeSaDgE3xne8F5sB
extension@0.0.90
2026-04-18 13:20:43 +02:00
Tommy D. Rossi b7a2811caf release: playwriter@0.1.0 playwriter@0.1.0 2026-04-16 12:01:54 +02:00
Tommy D. Rossi 6b1ecb729b chore: update playwright submodule for shared dialog race fix
Record the Playwright fork commit that moves shared JavaScript dialog race handling into Dialog.close().

This follows the previous root commit that added the playwriter package version bump, changelog entry, and integration repro test. With this submodule pointer update, the repository now fully captures both layers of the change:
- playwriter root: user-facing release notes + regression coverage
- playwright fork: abstraction-level dialog close fix

Submodule commit included:
- 0dcc58955 fix: ignore duplicate auto-close races for shared dialogs
2026-04-14 10:57:52 +02:00
Tommy D. Rossi 9c2bac37d3 fix: handle shared dialog auto-close races in dialog abstraction
Move the JavaScript dialog race fix down into the Playwright fork instead of teaching the relay about a specific CDP error.

When multiple connectOverCDP clients share the same Playwriter tab, they all observe the same dialog and may all attempt the automatic best-effort close. Chrome accepts the first Page.handleJavaScriptDialog and rejects later attempts with "No dialog is showing". That follow-up failure should not crash the process because the dialog is already closed and the cleanup goal was achieved.

This commit:
- updates @xmorse/playwright-core Dialog.close() to ignore benign duplicate close races
- bumps playwright-core to 1.59.9 and documents the behavior in its changelog
- bumps playwriter to 0.0.112 and documents the user-visible fix
- adds a relay integration test that attaches two CDP clients to the same tab, opens an alert, and verifies both clients remain usable without an unhandled rejection

Why this abstraction is better:
- Dialog.close() is already the best-effort cleanup boundary
- the relay no longer needs protocol-specific knowledge of Page.handleJavaScriptDialog failures
- the behavior also protects any other shared-target setup that triggers the same close race

Validation:
- pnpm playwright:build
- pnpm typecheck (playwriter)
- pnpm test --run relay-core.test.ts -t "should ignore duplicate dialog dismissals from multiple CDP clients"
2026-04-14 10:57:03 +02:00
Tommy D. Rossi 19b0959585 fix: wrap all ghost cursor code in try/catch so it never crashes main logic
Every cursor code path that touches page.evaluate, DOM manipulation, or
runs inside a Playwright callback is now wrapped so errors stay cosmetic:

- onMouseAction callback: outer try/catch prevents cursor errors from
  propagating to Playwright's input dispatch (would break page.click)
- show()/hide(): swallow errors from closed/navigating pages
- enableGhostCursor/disableGhostCursor: same
- applyGhostCursorMouseAction: same
- Browser DOMContentLoaded auto-enable: inner try/catch
2026-04-13 09:26:26 +02:00
Tommy D. Rossi f7499bd1d9 fix: two-element cursor split so move and press transitions don't interfere
The cursor DOM is now outer (translate, move timing) + inner (scale+opacity,
press timing). Previously setPressed() overrode transitionDuration to 140ms
which retargeted in-flight 1500ms move transitions, causing clicks to
'sprint' the cursor to the target.

Also:
- Fix toast animation jump when positioned above anchor (toolbar.ts):
  keyframes now use --toast-transform CSS variable so translateY(-100%)
  base transform is included during animation, not just after.
- Remove stale navigation-rehydration entry from CHANGELOG (was cut in
  the simplification pass but the changelog entry remained).
2026-04-13 09:21:25 +02:00
Tommy D. Rossi 7455abbf5a feat: always-on ghost cursor injected by extension on tab attach
The Chrome extension now injects the ghost-cursor-client.js bundle into
every attached tab via CDP Page.addScriptToEvaluateOnNewDocument +
Runtime.evaluate. The cursor auto-enables on load (top frame only, skips
iframes) and stays on the last spot Playwright clicked or moved to.

Key changes:

- Extension injects cursor bundle via ?raw vite import, inlined into
  background.js at build time. Torn down on detachTab.
- RecordingGhostCursorController renamed to GhostCursorController with
  attachToPage/detachFromPage instead of enableForRecording/disableForRecording.
  Single instance on PlaywrightExecutor, wired in setupPageListeners.
- Recording no longer toggles the cursor on/off.
- 5s idle debounce: cursor fades out (600ms) after no activity, wakes on
  next action by teleporting to the new target and fading back in (140ms).
- Animation polish (Emil Kowalski guidelines): easeInOutCubic for moves,
  dedicated 140ms press duration with strong ease-out, subtler 0.95 press
  scale, transform-origin anchored to cursor hotspot.
- Slower cinematic defaults: 220ms min, 1500ms max, 1.2 px/ms.
- Test: verify cursor element exists on attached tab without explicit enable.
2026-04-13 09:10:34 +02:00
Tommy D. Rossi 8ee26ffcb4 update @modelcontextprotocol/sdk to 1.29.0 and hono to 4.12.12 2026-04-13 09:00:31 +02:00
Tommy D. Rossi b9c96dba25 feat(toolbar): wrap clipboard in natural-language prompt + bash-safe \u0027
Clipboard now copies a prompt-style message instead of a bare JS snippet:

    see the element I pinned in the playwriter tab `playwriter -e '<code>'`

The user pastes this into a chat with their agent. The agent reads it as
"user pinned an element, here is the code to run" and executes it with
their own session (via `-s <id>` or `PLAYWRITER_SESSION`). Makes the
intent obvious when the clipboard lands in a chat window — the agent
sees the goal plus the exact code without needing instructions.

Also fixes a latent bash-safety bug in `buildInspectionCode`. The code
is wrapped in bash `'...'` so single quotes are the only character we
cannot have inside it. `JSON.stringify` does NOT escape literal `'`, so
element text containing an apostrophe (e.g. "Don't save", aria labels
like "I'm loading", URLs with `?q=what's+new`) leaked a literal `'` into
the code and broke the outer bash wrapper.

Fix: replace every `'` in the JSON-stringified URL and summary with
`\u0027`. This is a valid JSON escape that the JS engine parses back to
`'`, so the string value is unchanged at runtime, but the generated
code is now guaranteed to contain zero literal single quotes regardless
of page content.

Single vs double bash quotes comparison:

    '...'   literal everything except '          ← 1 problem char
    "..."   $foo expands, `cmd` runs,            ← 4 problem chars
            \ escapes, " terminates

Our code is full of " (JSON strings) and can contain $ from element
text, so double quotes are a non-starter. Single quotes with the
\u0027 substitution is the simplest robust solution.

Verified with hostile input:
- url with "what's+new" in query → 0 literal ' in code
- summary with "Don't save 'now'" → 0 literal ' in code
- JS eval of the escaped code reproduces the original strings

Bumps extension to 0.0.85. Bundle 72.79 kB → 72.90 kB (+110 bytes for
the new prose prefix and the two .replace calls).
2026-04-11 16:47:45 +02:00
Tommy D. Rossi 9e33e21484 refactor(toolbar): drop session plumbing, copy raw eval code on pin-click
The previous implementation fetched the CLI session list from the relay via
a new GET /extension/sessions endpoint, plumbed it into the MAIN-world
toolbar via chrome.scripting.executeScript args, cached it in a closure,
and baked `playwriter -s <id> -e '<code>'` into the clipboard. The
picked session id was fragile: sessions[0] is the oldest by insertion
order, which is almost never the session the agent is actually using.
The agent also has its own active session already — guessing one is
the wrong job for a browser extension.

Drop all of it. The toolbar now copies only the JS eval code. The agent
wraps it in their own `playwriter -s <session> -e '<paste>'` using
whichever session they already have (or they create a new one). The
code is JSON.stringify'd so it contains no single quotes and slots
into any shell quoting strategy the agent prefers.

Removed:
- `GET /extension/sessions` on the relay (playwriter/src/cdp-relay.ts)
- `fetchSessionSummary` helper in background.ts
- `ToolbarConfig` interface + import + args plumbing
- Async wrapping around `chrome.scripting.executeScript` in both
  injection sites (attachTab + webNavigation.onDOMContentLoaded)
- `currentSessions` / `nextSuggested` closure vars in toolbar.ts
- `window.__playwriterUpdateSessions` setter
- `pickSessionId()` helper
- `shellSingleQuote()` helper (agent does their own shell quoting)
- Re-init update routing through `window.__playwriterUpdateSessions`

`onClick` now just:

    copyText(buildInspectionCode(n, url, summary))
    showToast(`Copied inspection code (pin #${n})`)

The eval code shape is unchanged:

    state.page=context.pages().find(x=>x.url()===URL)||context.pages()[0];
    console.log(SUMMARY + "\n\nouterHTML:\n" +
      await state.page.evaluate(n => globalThis["playwriterPinnedElem"+n]?.outerHTML, N))

Bundle drops from 74.35 kB to 72.79 kB (-1.56 kB). Bumps extension to
0.0.84 and playwriter to 0.0.108 with CHANGELOG entries in both packages.
2026-04-11 14:45:42 +02:00
Tommy D. Rossi 2cbb943b00 feat(toolbar): copy full playwriter -s X -e inspection command on pin-click
The in-page toolbar's pin button used to copy a plain
`see playwriter reference: globalThis.playwriterPinnedElem1` string that
still required the agent to wrap it in their own `-e` call. It now copies
a complete, ready-to-run shell command:

    playwriter -s 1 -e 'state.page=context.pages().find(x=>x.url()===URL)||context.pages()[0]; console.log(SUMMARY+"\n\nouterHTML:\n"+await state.page.evaluate(n=>globalThis["playwriterPinnedElem"+n]?.outerHTML,N))'

Two `;`-separated statements: pick the right page by URL with first-page
fallback, assign state.page for follow-up calls, log a pre-baked element
summary plus the current outerHTML of `globalThis.playwriterPinnedElemN`
fetched via one `page.evaluate`. No `getCleanHTML`, no
`getLocatorStringForElement`, no ElementHandle dance, no locator round-
trips — the generated JS is runnable against any playwriter version.

Everything the eval code doesn't strictly need a Playwright sandbox for is
pre-computed at pin time in MAIN world (synchronous DOM access) and baked
into the command via `JSON.stringify`:

- page URL from `location.href`
- element summary: tag, id, classes, role, aria-label, name, href, type,
  truncated text, bounding rect, visibility
- pin index

Session-id plumbing:

- New public `GET /extension/sessions` endpoint on the relay returns
  `{ sessions: [{ id, stateKeys }], nextSuggested }`. It sits outside the
  `/cli/*` privileged middleware, which rejects the cross-site
  `Sec-Fetch-Site` that chrome-extension fetches to 127.0.0.1 send. It
  exposes the same data as `playwriter session list`, so no new attack
  surface.
- `background.ts` `fetchSessionSummary()` calls the endpoint with a
  1500ms timeout and falls back to `{ sessions: [], nextSuggested: '1' }`
  on any failure (relay down, old relay without the endpoint, timeout,
  malformed JSON). The extension stays backward-compatible with older
  relays.
- Both toolbar injection sites (`attachTab` + `webNavigation.onDOMContentLoaded`
  re-inject) now fetch the summary and pass it as
  `chrome.scripting.executeScript({ args: [summary] })`.
- The guarded re-init path in `initPlaywriterToolbar` now calls
  `window.__playwriterUpdateSessions?.(config)` so re-injects refresh the
  closure cache instead of becoming no-ops.
- `pickSessionId()` prefers the first existing session id, falling back
  to `nextSuggested` (typically "1") so the agent stays in their current
  state when one exists.

Toolbar runtime behavior does not depend on the relay: sessions are
cached in closure at inject time, clipboard writes stay offline, and
all failure modes surface as visible `undefined` in the command's
output rather than exceptions or timeouts.

Bumps extension to 0.0.83 and playwriter to 0.0.107 with CHANGELOG
entries in both packages.
2026-04-11 14:29:20 +02:00
Tommy D. Rossi 365e5ba3ca fix: restore toolbar size to previous (26px buttons, 3px padding) 2026-04-11 13:23:24 +02:00
Tommy D. Rossi f5c513df1c fix: logo icon visibility in active state, toast font/padding 2026-04-11 13:02:01 +02:00
Tommy D. Rossi 794ff593e0 fix: further reduce toolbar size 2026-04-11 13:00:00 +02:00
Tommy D. Rossi 1c44c7ae99 fix: reduce toolbar padding and button size 2026-04-11 12:54:33 +02:00
Tommy D. Rossi 0dfb73879d feat: polish toolbar pin-element UX
- Deactivate pin mode after each click (toggle off after pin)
- Green flash outline: 3px → 1px to match overlay edge thickness
- Clipboard text prefixed: 'see playwriter reference: globalThis.playwriterPinnedElemN'
  so both user and AI agent understand the string's purpose at a glance
- Replace clipboard icon with Playwriter logo-square SVG (inlined)
2026-04-11 12:53:59 +02:00
Tommy D. Rossi af0f4aabe5 chore(deps): bump goke to ^6.6.0 and update --direct flag handling
goke 6.6.0 normalizes optional-value flags from 'string | boolean'
to 'string | undefined', with '' meaning 'flag present, no value'.
Update the --direct [endpoint] handler to match the new shape:
  - directEndpoint is just 'options.direct || null'
  - bare --direct check uses === '' instead of === true
2026-04-11 10:00:21 +02:00
Tommy D. Rossi 389e3cf23a chore(deps): bump goke to ^6.5.2 2026-04-11 09:32:15 +02:00