PROXY EXEC/ agent navigation works after page navigation. The frame identity lived only in the?__devtool_frame=URL marker and the injector's first-load inline script, so alocation.assign/reloadleft the re-injected page with no frame id — every exec from then on targeted a ghost frame and hung (interimstate:"running"status, never delivered).frames.jsnow persists the frame id to sessionStorage on the first wrapped load and recovers it on later documents in the same frame (content-role only — same-origin frames share the tab's sessionStorage, so the shell must never read it). Multi-hop agent navigation works.
- Design-mode preview iframe follows the page theme.
renderInFramehardcodedbackground:#fff, so alternatives for dark-themed apps previewed as dark text on a forced white sheet. The frame and srcdoc now inherit the page body's computed background and color. design.applyAlternative()no longer corrupts state when called with no argument. The index was unguarded, so a bare call setcurrentIndextoundefinedand the panel badge renderedNaN / N. Non-numeric or out-of-range indexes are now a no-op; the docs note the function is preview navigation only (shipping an alternative is a source edit + HMR).
- Design mode payloads now carry page-level and neighborhood context.
design_stategains a whole-page thumbnail (saved as a JPEG,page_thumb_path), the parent container's slot geometry (box + grid tracks / flex mode), and up to two same-signature exemplar components from elsewhere on the page.design_requestanddesign_chatgain an explicitconstraintsblock:preserve(scheme axes that stay fixed),vary(layout,hierarchy,density,affordance— UX variation over core-UI variation), andsteer(the user's latest message, highest precedence). Alternatives now default to staying on-scheme and on-slot while remaining steerable by the prompt.
saveScreenshotpicks the file extension from the data-URL MIME instead of hardcoding.png(design page thumbnails are JPEG).
- Sketch mode default ink is now theme-aware. The Excalidraw-style
default stroke
#1e1e1eis invisible on dark UIs — most of what sketch mode gets opened on.sketch.init()now measures the page's effective background through the frame-context adapter (__devtool_context.contentFrame()) and opens with light ink (#e8e9ee) when the page is dark. The override only applies while the factory default is untouched, so a saved or chosen stroke color is never overridden.
auditDesign— delay-loaded design anti-pattern audit (__devtool.audit.auditDesign). Backed by the vendored Impeccable browser detector (Apache-2.0): 59 deterministic rules for the design tells AI-generated frontends share — overused fonts, purple-to-blue gradients, gradient text, cards nested in cards, gray text on colored backgrounds, side-tab borders, low contrast. Only Impeccable's live-DOM engine is bound; the package's static-HTML and source-tree engines are deliberately out of scope, because an audit answers for the rendered page. The ~366KB detector never enters the injected instrumentation: the first call injects it from/__devtool_impeccable(servedcharset=utf-8— the bundle carries UTF-8 regex character classes) with auto-scan disabled. Advisory findings surface asinfoand never score. Not folded intoauditAll, which would make the aggregate's first run silently heavyweight.
-
auditAnimations— compositor-load audit (__devtool.audit.auditAnimations, newaudit-animationsmodule in the injected bundle). Finds the performance class every JS profiler is blind to: work that runs entirely on the compositor thread. An infinite CSS animation (Tailwindanimate-pulseon a status dot is the canonical case) never touches the DOM, never runs script, and reads as an ordinary visible element — yet it forces a compositor commit at display refresh rate forever, pegging the browser's GPU process from a visually static page on high-refresh HiDPI displays and draining batteries on mobile.Detectors:
infinite-animation(the frame pump, fromdocument.getAnimations()— the one registry MutationObserver and visual-state snapshots cannot see),layout-property-animation(width/top/margin animations forcing main-thread layout per frame),viewport-overlay-amplifier(full-viewport noise/grain layers),backdrop-filter-amplifier(large-area filters only — a small composer-bar blur is not flagged), andwill-change-overuse. Amplifiers escalate toerroronly while a frame pump is live; without one they cost a single paint and report asinfo.{sampleMs: N}adds a bounded requestAnimationFrame idle sample (frameSample.effectiveFpsat refresh rate on a static page = conviction; ≤5 fps = the page idles). The probe is time-bounded and honest: a backgrounded/occluded tab resolvesrafStarved: trueand reports the sample inconclusive instead of hanging or faking an idle reading; a browser withoutdocument.getAnimations()returnsnotApplicable, never an unmeasured passing grade.Because the audit ships inside the proxy-injected instrumentation, it runs on-device — point a phone at the proxy URL (or a tunnel) and the same call answers for that device's own refresh rate, animation registry, and GPU. No USB debugging, no desktop inspector attach. Guide with a full reproduction of the T3 Code / "Fable Broke My App" incident:
docs-site/docs/guides/gpu-compositor-debugging-ai.md.
automation {action:"evaluate"}no longer silently runs content-scoped scripts in the proxy shell. The content-frame wrapper used to fall back towindowwhen the app iframe was not yet available, so an evaluate racing a navigation executed in the SHELL realm — the exact wrong-frame failure the content default exists to remove. A wrapped shell now waits (bounded, 5s) for the app frame to become ready — its realm carries the injected content role, or a non-instrumented document has fully loaded offabout:blank— and on timeout fails loud, naming the real states and theframe:"top"escape hatch. Only a genuinely unwrapped page still evaluates inwindowdirectly.
-
get_errorsis gone.get_incidentsis the only error surface. The migration was gated on a measured superset, not a judgement call: an oracle drove both tools' real filter builders and projections over one seeded state and reduced every difference to a recorded divergence. It was allowed to land only once no divergence remained where information was unreachable.Closing the last four blockers changed real behaviour, so read this even if you never called
get_errors:- Incidents now carry
context.location(file:line:col, resolved by the browser adapter at ingest) andcontext.frame_id. - Browser incident fingerprints changed. The emitting content frame is part of the fingerprint, so the same error raised in two content frames is now two incidents rather than one. Incidents with no frame attribution fingerprint exactly as before.
get_incidentsreturnscollection_warnings, naming every way the view is partial — events the bus dropped before reaching any inbox, anddetail:"full"payloads the blob store could not hydrate. The latter was previously swallowed and rendered as an incident that simply had no payload.- The readiness gate's
agnt_proxy_not_ready503 is filtered at ingest (incident.FromHTTPEntry) instead of inget_errors. Without that move, retiring the tool would have filled the inbox with agnt's own retry signal for the whole startup race.
Migration is a rename in most cases:
get_incidentsalready acceptederror_id/tag/actionwithget_errorsspelling. Two things do not carry over —include_warnings:falsebecomesseverity:["critical","error"], and there is noglobal, because the inbox is per-session hard-isolated, which is a stronger guarantee than the project scopingglobaloverrode.proc {action:"snapshot"}is unchanged: it kept the per-source collectors, now ininternal/tools/unified_error.go, because it is project-scoped and may be global where the inbox cannot be. - Incidents now carry
-
ALERTS PIN/UNPIN/CLEARand the alert-store pin are gone with it.get_errorswas their only client, so nothing could set an alert-store pin any more —internal/alert/pinned.go, thepinnedfield onALERTS QUERY, and theAlertPin/AlertUnpin/AlertCleardaemonclient methods were unreachable code. Pinning lives onget_incidents, which is per-session rather than per-project. The automatic retention triggers (alerts.retention) are untouched; they call the store directly and never used these verbs.
- Background
runno longer opens a dedicated daemon connection. Only the foreground modes, which block until the process exits, need to stay off the shared connection's per-request mutex. Background runs reuse the resilient client and keep its reconnect/retry behavior. - Sessions with no project path get the reconnect grace period. A dropped connection used to reap an acp one-shot / cooked-REPL session's process group inline, so a client that reconnected within the grace window had its process tree killed out from under it — and the hub's disconnect callback blocked for the SIGTERM→SIGKILL escalation. Session-host sessions are never reaped on disconnect, which surviving client disconnect is the whole point of.
- Session-host PTY fd is closed exactly once. A
SESSION-HOST KILLracing the child's own exit reached both close sites; once the fd number was recycled, the second close landed on whatever then owned it. - Shutdown breadcrumbs no longer evict startup errors. The startup-log ring
is shared across projects and does not outlive the process, so ~36 daemon-wide
info entries emitted during
Stopwere displacing real project errors for a reader that no longer exists. Warnings and errors during shutdown still land. - Subprocess accept backoff no longer delays
Stop(go-cli-server v0.5.2).
-
Daemon socket moved to a per-uid subdirectory:
/tmp/agnt-<uid>/agnt.sock, replacing/tmp/agnt-<uid>.sock. The hardened socket bind requires the socket's parent directory to be uid-owned and0700, and/tmpis root-owned.Upgrade note: a daemon started by a pre-0.13.32 binary keeps listening on the old path, where the new client cannot see it — it would linger holding its managed processes and their ports. On startup the daemon now finds that legacy socket, asks the old daemon to shut down gracefully (stopping the processes it owns rather than orphaning them), and removes the stale file. If the old daemon cannot be stopped, the reason is recorded in the startup log rather than swallowed. Only applies to the default socket path; an explicit
AGNT_SOCKETor--socketis left alone.
get_incidentsno longer drops the oldest incident. The inbox returns the oldest unseen page; the hub truncated it to the newest entries and then published a cursor above the record it had just discarded, so with a forward-onlysincecursor that incident could never be returned again.AUTOSTART RECONCILEandOVERLAY FORWARDINGnow dispatch. Both were routable but unregistered as sub-verbs, so the parser left the token in the argument list. Reconcile answered "unknown action" — live.agnt.kdlreconcile never ran — and forwarding silently fell through toOVERLAY GET, reporting success while pausing nothing. Sub-verbs are now derived from the router that dispatches them, so the two lists cannot drift.- Editing a script's
runin.agnt.kdlno longer stops it restarting. The script registry rejects a re-register under a changed config; live reconcile relies on exactly that re-register, so the script never came back. - Scheduler retry backoff no longer panics on a negative attempt count read from a persisted task file, nor overflows into a past deadline (hot retry loop).
DETECTandDOCTORreject a malformed payload instead of silently falling back to the daemon's own working directory.snapshot {raw: true}reports partial-collection failures. They were only appended to the text rendering, so a raw consumer read an incomplete snapshot as a clean one.get_incidentsrejects an unparseablesinceinstead of ignoring it and returning the whole inbox.- Daemon client leak:
agnt runcould create a resilient client that nothing ever closed, leaving its reconnect loop retrying for the life of the process.
- go-cli-server upgraded to v0.5.1 and the vendored tree un-patched. It carried
local modifications to the hub command registry that were never upstream, so
go install github.com/standardbeagle/agnt/cmd/agnt@latest— which ignoresvendor/— could not build. That API is now released upstream.
agnt skillscommand — one-shot install of the agnt agent skills + agnt MCP registration. Uses Vercel's open skills CLI (npx -y skills add standardbeagle-tools/agnt --all -a claude-code) then registers the MCP server (claude mcp add agnt -s user -- agnt mcpfor Claude Code; prints the MCP config for other agents).--agent/--sourceoverride the target agent and skills source. Requires Node.js (npx).- First-run setup flow for
agnt run. Whenagnt run claudeis invoked in a project with no.agnt.kdl, agnt drives a one-time setup run (the agent configures the project via theagnt:setup-projectskill) then relaunches the coding agent with autostart enabled, replaying the original arguments.- Per-project first-run marker under XDG state; positive outcome is permanent,
a declined setup re-nudges after a configurable TTL
(
setup { renudge-ttl-days 7 }, default 7 days). - Per-agent install guidance from a support matrix (docs/agent-support-matrix.md): each agent is classified marketplace-install / skill-file / none with the correct install text.
- Per-project first-run marker under XDG state; positive outcome is permanent,
a declined setup re-nudges after a configurable TTL
(
agnt initcommand — runs only the setup phase (configure the project, write.agnt.kdl) without relaunching into a coding session.agnt initdefaults to claude;agnt init <agent>uses another agent. A successful init records the permanent marker so a lateragnt runskips the setup nudge.idparameter alias across single-reference MCP tools.currentpage,proxylog,responsive_audit,channel_reply,snapshot, andprocnow acceptidas an alias for their canonicalproxy_id/process_idparameter. Canonical name wins when both are set. Skill docs updated to guide toward the preferred form.snapshot {action:"screenshot"}captures the current page of a running proxy via__devtool.screenshot()in one call. Acceptsproxy_id(oridalias), optionalname,selector, andfull_page. File path is returned in the nextproxylog {types:["screenshot"]}entry. Daemon mode only.- Incident pipeline opt-in (
alerts.incident-pipelinein.agnt.kdl): New alert path ininternal/incident/that normalises signals from 11 sources (browser JS, HTTP 5xx/4xx, process crashes, build failures, port conflicts, hook stop-failures, etc.) into a deduped, coalesced, priority-ordered inbox with four severity bands.get_incidentsMCP tool: cursor-based pull withremediation_hintandnext_toolsfields. Supersedesget_errorswhen the pipeline is enabled.get_errorsis retained as a legacy shim; it continues to work unchanged whenalerts.incident-pipeline false(the default).- Feature flag is all-or-nothing per session; daemon restart required to
toggle. Phase A default is
false(opt-in only).
- Debug logging infrastructure: Comprehensive debug logging for proxy and tools
- Enable with
AGNT_DEBUG=1environment variable - Logs to stderr to avoid interfering with MCP stdio communication
- Enable with
- Enhanced indicator metadata: Attachment metadata in panel messages for richer context
- 'run' shorthand for script commands: Simplified
.agnt.kdlconfiguration withrun "command"syntax - SVG wireframe generation: Generate SVG wireframes from DOM elements via
__devtoolAPI - AI-optimized audit output: New default output format designed for token efficiency
- Grouped issues by type with limited examples
- Use
raw: truefor verbose detailed format
- Process/proxy restart functionality:
proc {action: "restart"}andproxy {action: "restart"} - forAutomation mode: AI-powered analysis with
forAutomation: trueflag for audit functions - Audit system overhaul: Action-oriented output with clear remediation steps
- User interaction recorder: Script for tracking and replaying user interactions
- Response stream UI components: Visual components for streaming responses
- Shell completion command:
agnt completion [bash|zsh|fish|powershell] - Feature licensing framework: Foundation for premium feature licensing
- Activity monitoring: Output preview broadcast for real-time activity tracking
- EADDRINUSE auto-recovery: Automatic port conflict recovery for script startup
- Multi-level data store: MCP and JS API for structured data storage
- Modern compression support: Brotli (br) and Zstandard (zstd) decompression in proxy
- Automatic decompression for HTML injection
- Pass-through with logging for unsupported compression formats
- Maintains backward compatibility with gzip and deflate
- Accessibility audit overhaul: Action-oriented output with clear pass/fail criteria
- Refactored overlay code: Reduced cyclomatic complexity in high-risk functions
- Progressive disclosure in currentpage tool:
action: "list"now returns lightweight summaries- Returns only counts (interaction_count, mutation_count, error_count, resource_count)
- Omits detailed arrays to prevent token bloat (was sending 11.7k tokens for 69 interactions)
- Use
action: "get"with specificsession_idfor full interaction/mutation details - Typical token reduction: ~90% for list views with many interactions
- Code quality improvements eliminating shotgun surgery patterns
- Sketch-driven development: Fully integrated Excalidraw-like sketch mode for wireframing
- Automatic page screenshot capture when entering sketch mode
- Background image with 40% overlay for drawing context
- Wireframe elements: buttons, input fields, sticky notes, image placeholders
- Shape tools: rectangle, ellipse, line, arrow, freehand drawing, text
- JSON export/import for sketch persistence
- Save & Send integration to send sketches to MCP for AI processing
- Sketches include full page context so AI can understand component placement
- New proxylog types documented: interaction, mutation, panel_message, sketch
- AI agents can now query
proxylog {types: ["sketch"]}to retrieve wireframes
- AI agents can now query
- MCP client CWD resolution:
detectandruntools now resolve relative paths (like.) to absolute paths before sending to daemon, ensuring the daemon uses the MCP client's working directory instead of its own - Sketch overlay visibility: Reduced background opacity and improved grid dot visibility
- Enhanced diagnostics panel with tabbed interface: Floating indicator now features a comprehensive 7-tab diagnostics dashboard
- Overview tab: Health cards showing framework detection, error counts, failed API calls, DOM update rates, and React-specific metrics (rerender rate, input lag)
- Errors tab: Deduplicated error tracking with console.error/console.warn interception and JS error aggregation
- Network tab: API call history with status codes, timing, and URL sanitization for sensitive params
- Performance tab: DOM mutation rate analysis across multiple time windows (1s, 5s, 30s)
- Quality tab: Placeholder for upcoming quality audits
- Interactions tab: User interaction history tracking
- Compose tab: Original message composition interface
- Active tab persistence via localStorage
- Real-time badge updates showing error counts, failed calls, and performance status
- Auto-refresh every second while panel is expanded
- Framework detection module (
framework-detector.js): Automatic detection of frontend frameworks (React, Vue, Angular, Svelte, etc.) with version extraction - API call tracking module (
api-tracker.js): Intercepts fetch and XMLHttpRequest to track API calls with sanitization for sensitive parameters (tokens, API keys, passwords) - Error buffer API (
window.__devtool_errors): In-memory error tracking with deduplication, statistics, and examplesgetJSErrors(),getConsoleErrors(),getConsoleWarnings()getDeduplicatedErrors()returns grouped errors with counts, timestamps, and stack tracesgetStats()returns total counts across all error typesclear()resets all error buffers
- Console override: Enhanced
console.errorandconsole.warnto capture console output for diagnostics
- Automation processor: Removed deprecated
TimeoutSecsparameter from claude-go API calls - Script module ordering: Framework detector and API tracker now load before indicator module
- go.mod formatting: Added trailing newline for consistency
- Error buffers: Circular buffer with max 100 entries per type (JS errors, console errors, console warnings)
- Deduplication key: First 100 characters of error message
- Tab content updates: Only active tab refreshes every 1 second to minimize performance impact
- React-specific metrics: Correlate input events with DOM mutations to detect rerender hotspots and input lag
- API sanitization: Automatically truncates sensitive query parameters (token, api_key, key, secret, password, auth)
- Session-scoped API: Processes and proxies are now scoped to sessions, preventing interference between multiple AI coding sessions
SESSION FIND: Locate session by directory ancestry (walks up the directory tree)SESSION ATTACH: Attach MCP client to an existing session for shared resource accessDirectoryFilterextended withSessionCodefor session-based filtering- Auto-attach behavior: MCP clients automatically find and attach to sessions in parent directories
--no-attachCLI flag to disable auto-attach and operate globallyproc listandproxy listnow filter by session code when attached
- PID tracking for orphan cleanup: Implemented persistent PID tracking to prevent orphaned processes after daemon crashes
- Tracks process PIDs to
~/.local/state/devtool-mcp/pids.jsonwith daemon PID for crash detection - Automatic cleanup of orphaned processes on daemon startup
- Changed Unix process group behavior: children now inherit daemon's PGID for automatic cleanup on daemon termination
- Comprehensive test suite (7 tests) for PID tracking operations
- Documentation:
docs/orphan-cleanup.mdwith implementation details and scenarios
- Tracks process PIDs to
- AI channel model configuration: Claude Code CLI now defaults to
haikumodel for fast, cost-effective summaries- Model is configurable via
Config.Modelfield - Added
--modelflag to Claude Code CLI invocations
- Model is configurable via
- AI channel error handling:
SendAndParsenow returnsErrAgentErrorwhen Claude Code reports an error (is_error: true)- Errors are reported to users instead of being passed to downstream LLM calls
- Error includes subtype context (e.g., "agent error (error): message")
- JSON extraction improvements: Enhanced response parsing with two distinct extraction strategies
extractClaudeCodeJSON: For Claude Code CLI wrapped responses (findstype: "result"objects)extractEmbeddedJSON: For AI-embedded JSON in prose responses- Helper functions
BuildEmbeddedJSONPromptandBuildEmbeddedJSONSystemPromptfor structured data extraction
--no-autostartflag: Skip auto-starting scripts and proxies from.agnt.kdlwhen runningagnt run claude- Spinner animation: Status summary now shows animated spinner while loading
- Production release build: New
make releasetarget with optimized flags (-s -w, -trimpath) - Setup project improvements: Updated
/setup-projectcommand with better documentation about:- Status bar information and CTRL+Y overlay menu
- OAuth redirect URL configuration for both dev and proxy ports
- How to skip autostart and restart services
- Process/proxy filtering after
agnt runrestart: Fixed issue where processes and proxies started fromagnt run claudewould disappear fromproc listandproxy listafter restarting the CLI. Root cause was MCP server using its own working directory instead of the original project directory.- Added
AGNT_PROJECT_PATHenvironment variable, injected byagnt runinto child processes - MCP tools now use
AGNT_PROJECT_PATHto filter by the correct project directory - Fixed both Unix (
run.go) and Windows (run_windows.go) implementations
- Added
- Windows path case sensitivity: Fixed path comparison issues on Windows where
C:\Usersandc:\userswere treated as different directories- Added case normalization (lowercase) for Windows paths in both daemon and MCP tools
- UNC paths (
\\server\share) handled correctly
- Version parsing with daemon status: Fixed
--versionoutput parsing in upgrade tests where multi-line output (including daemon status) caused test failures
- Tests for
getProjectPath()function covering environment variable handling, path edge cases - Tests for
normalizePath()function covering Windows case-insensitivity, UNC paths, special characters - Session-scoped resource cleanup: When a client that registered a session disconnects, only resources (processes, proxies) for that session's project path are cleaned up
- Added
sessionCodefield to Connection to track which session each connection registered - Added
StopByProjectPath()to ProxyManager (matching existing ProcessManager method) - Added
CleanupSessionResources()to Daemon for session-targeted cleanup - Comprehensive test
TestSessionBasedCleanupverifying isolation between sessions - Fixes issue where exiting one session would leave orphaned processes, causing port conflicts for new dev servers
- Added
- Hash-based default proxy port: Proxy now auto-assigns a stable port based on FNV-1a hash of target URL (range 10000-60000)
- Same target URL always gets the same port (consistent across restarts)
- Different URLs get different ports (avoids conflicts)
- Avoids well-known ports, registered ports, and ephemeral port ranges
- Port parameter is now optional - only specify if you need a specific port
- The
listen_addrresponse field always shows the assigned port
- Screenshot Firefox compatibility: Switch from
html2canvas@1.4.1tohtml2canvas-pro@1.5.8to support modern CSS color functions (lab(),oklch(),oklab(),lch()) that Firefox and modern browsers use in computed styles
- Progressive disclosure for __devtool API documentation:
proxy {action: "exec", help: true}- Full API overview with all 60+ functions grouped by categoryproxy {action: "exec", describe: "functionName"}- Detailed documentation for individual functions- New
internal/tools/apidocs.gowith comprehensive API documentation
- New proxy input parameters:
helpanddescribefor accessing API documentation without executing code - Updated proxy tool description with common __devtool examples for screenshot, logging, interactions, mutations, inspection, and accessibility auditing
- Background daemon for persistent state across MCP client disconnections
- Session handoff: Multiple MCP clients can interact with the same processes/proxies
- Auto-start: Daemon starts automatically on first tool call
- Socket-based IPC: Text protocol for client-daemon communication
- New
daemonMCP tool with status, info, start, stop, restart actions
- npm package:
@standardbeagle/devtool-mcpwith automatic binary download - PyPI package:
devtool-mcpfor pip/uv installation - Bash installer: One-liner installation via curl
- GitHub Actions: Automated release workflow for all platforms
- Reorganized Frontend API docs into hierarchical categories
- Added daemon tool documentation
- Fixed MDX parsing issues in documentation
- Version bumped to 0.3.0
- Makefile uses
install -m 755instead ofcpfor proper permissions - CLAUDE.md refocused on development guidance
npm:
npm install -g @standardbeagle/devtool-mcppip/uv:
pip install devtool-mcp
# or
uv pip install devtool-mcpBash (one-liner):
curl -fsSL https://raw.githubusercontent.com/standardbeagle/devtool-mcp/main/install.sh | bashFrom source:
git clone https://github.com/standardbeagle/devtool-mcp.git
cd devtool-mcp
make build
make install-local- Changed:
proxy exectool now waits for JavaScript execution results instead of fire-and-forget - Added: Result channels for pending executions (
sync.Mapin ProxyServer) - Added: 30-second timeout for execution responses
- Result: Users now receive immediate feedback with execution results:
JavaScript executed successfully. Result: "My Page Title" Duration: 2.5ms
- Added: New log type
LogTypeResponsefor tracking MCP client responses - Added:
ExecutionResponsestruct with execution metadata - Added:
LogResponse()method in TrafficLogger - Updated:
proxylogtool now supportstypes: ["response"]filter - Result: Full audit trail of JavaScript executions and their responses
- Query:
proxylog {proxy_id: "dev", types: ["response"]}
Full Page Screenshots:
window.__devtool.screenshot()- Auto-generated namewindow.__devtool.screenshot('homepage')- Custom name
Element Screenshots:
window.__devtool.screenshot('#selector')- Capture specific elementwindow.__devtool.screenshot('button', '.my-button')- Element with custom name- Smart parameter detection: Automatically detects CSS selectors (starting with
.,#,[) - Error handling: Returns clear errors for invalid selectors or missing elements
- Scroll compensation: Properly handles scroll offsets for accurate captures
Screenshot Metadata:
- Added
Selectorfield to Screenshot struct - Logs now include which element was captured (
bodyfor full page, or CSS selector) - Query:
proxylog {proxy_id: "dev", types: ["screenshot"]}
Architecture Changes:
ProxyServer.pendingExecs- Lock-freesync.Mapfor execution trackingExecuteJavaScript()signature change: Returns(string, <-chan *ExecutionResult, error)- WebSocket handler notifies waiting channels when results arrive
- Tool handler blocks until result received or timeout
JavaScript Enhancements:
- html2canvas configured for full-page and element capture
- Automatic scroll offset compensation
- Comprehensive error handling for selectors
- Flexible parameter combinations for screenshot API
Logging Improvements:
- All MCP responses now logged for audit trail
- Timeout responses logged as failed executions
- Screenshot logs include selector information
- Response logs separate from execution logs for clarity
Execute JavaScript and Get Result:
proxy {action: "exec", id: "dev", code: "document.title"}
// Returns: "JavaScript executed successfully.\nResult: \"My Page\"\nDuration: 1.2ms"Capture Full Page:
proxy {action: "exec", id: "dev", code: "window.__devtool.screenshot('homepage')"}Capture Specific Element:
proxy {action: "exec", id: "dev", code: "window.__devtool.screenshot('#header')"}Query All Responses:
proxylog {proxy_id: "dev", types: ["response"], limit: 50}- None - All changes are backward compatible
- Old logs and execution patterns continue to work
- New features are opt-in through new log types
- Minimal: Execution tracking uses lock-free
sync.Map - Timeout default: 30 seconds (configurable)
- Channel cleanup automatic on result or timeout
- Screenshot performance depends on html2canvas (typically <1s)
Implemented ~50 primitive, composable JavaScript functions in window.__devtool that enable LLMs to perform comprehensive DOM inspection, layout analysis, visual debugging, and interactive diagnostics. All primitives are designed to be small, focused, and composable.
- Primitives over monoliths: Small, focused functions (~20-30 lines each)
- Composability: Functions return rich data structures that other functions consume
- Synchronous by default: Async only when necessary (screenshots, user interaction)
- Error resilient: Return partial results with error fields, don't throw
- Selector flexibility: Accept CSS selectors, elements, or arrays
resolveElement(selector)- Convert selector/element to elementgenerateSelector(element)- Create unique CSS selector for elementsafeGetComputed(element, properties)- Safe getComputedStyle wrapper- Overlay management system with SVG-based rendering
getElementInfo(selector)→{ element, selector, tag, id, classes, attributes }getPosition(selector)→{ rect, viewport, document, scroll }getComputed(selector, properties)→{ property: computedValue }getBox(selector)→{ margin, border, padding, content }getLayout(selector)→{ display, position, flexbox, grid, float }getContainer(selector)→{ type, size, name }(CSS containment)getStacking(selector)→{ context, zIndex, order, parent }getTransform(selector)→{ matrix, translate, rotate, scale }getOverflow(selector)→{ x, y, scrollWidth, scrollHeight }
walkChildren(selector, depth, filter)→{ elements, count }walkParents(selector)→{ parents, count }findAncestor(selector, condition)→{ element, selector }
isVisible(selector)→{ visible, reason, area }isInViewport(selector)→{ intersecting, ratio, rect }checkOverlap(selector1, selector2)→{ overlaps, area, percentage }
findOverflows()→{ overflows, count }findStackingContexts()→{ contexts, count }findOffscreen()→{ offscreen, count }
highlight(selector, config)→highlightIdconfig:{ color, borderColor, duration, pulse, label }- Renders visual overlay showing element boundaries
removeHighlight(highlightId)→voidclearAllOverlays()→void
selectElement()→Promise<{ element, selector }>- Full interactive element picker with hover preview
- Click to select, Escape to cancel
measureBetween(sel1, sel2)→{ distance: { x, y, diagonal }, direction }waitForElement(selector, timeout)→Promise<element>- Uses MutationObserver to wait for dynamic elements
ask(question, options)→Promise<answer>- Shows modal dialog for user interaction
- Returns selected option or cancelled
captureDOM()→{ snapshot: HTML, hash, timestamp, url, size }captureStyles(selector)→{ computed, inline, timestamp }captureState(keys)→{ localStorage, sessionStorage, cookies }captureNetwork()→{ resources, count, timestamp }
getA11yInfo(selector)→{ role, aria, tabindex, focusable, label }getContrast(selector)→{ fg, bg, ratio, passes: { AA, AAA } }- Implements WCAG 2.0 contrast ratio calculation
getTabOrder(container)→{ elements, count }getScreenReaderText(selector)→stringauditAccessibility()→{ errors, warnings, score }- Scans for missing alt text, unlabeled buttons, missing labels
- Returns score 0-100 based on issues found
Built from primitives - high-value for LLMs:
-
inspect(selector)- Comprehensive element inspection{ info: getElementInfo(), position: getPosition(), box: getBox(), layout: getLayout(), stacking: getStacking(), container: getContainer(), visibility: isVisible(), viewport: isInViewport() }
-
diagnoseLayout(selector)- Find layout issues{ overflows: findOverflows(), stackingContexts: findStackingContexts(), offscreen: findOffscreen() }
-
showLayout(config)- Visual debugging overlay// Combines highlight() with smart defaults { overlayId, active: { borders, boxes } }
- Created
test-diagnostics.htmlcomprehensive test page - Includes test buttons for all primitive categories
- Examples of Flex, Grid, Stacking, Overflow, Transform layouts
- Hidden elements for visibility testing
- Console usage examples
Browser API Usage:
getBoundingClientRect()- Element positioninggetComputedStyle()- CSS property valuesIntersectionObserver- Viewport visibility (future)MutationObserver- Dynamic element detection- Container Query APIs - CSS containment detection
- WCAG 2.0 formulas - Accessibility contrast ratios
Error Handling Pattern: All primitives follow consistent error handling:
function primitive(selector) {
try {
var el = resolveElement(selector);
if (!el) return { error: 'Element not found' };
// Do work
return { /* data */ };
} catch (e) {
return { error: e.message };
}
}ES5 Compatibility:
- All code uses ES5 syntax for broad browser support
- No arrow functions, template literals, or modern features
- Tested in modern browsers (Chrome, Firefox, Safari)
Comprehensive Element Inspection:
proxy {action: "exec", id: "dev", code: "window.__devtool.inspect('#my-element')"}
// Returns 8+ data structures with complete element analysisInteractive Element Selection:
proxy {action: "exec", id: "dev", code: "window.__devtool.selectElement()"}
// User clicks element, returns selector and element referenceAccessibility Audit:
proxy {action: "exec", id: "dev", code: "window.__devtool.auditAccessibility()"}
// Returns: { errors: [...], warnings: [...], score: 85 }Layout Diagnostics:
proxy {action: "exec", id: "dev", code: "window.__devtool.diagnoseLayout()"}
// Finds all overflows, stacking contexts, offscreen elementsContrast Checking:
proxy {action: "exec", id: "dev", code: "window.__devtool.getContrast('.my-button')"}
// Returns: { ratio: 4.52, passes: { AA: true, AAA: false } }- LLM Composability: LLMs can create unlimited combinations from primitives
- Debuggability: Rich data structures for analysis instead of strings
- Interactivity: Ask questions, select elements, measure distances
- Visual Feedback: Overlays show layout structure before screenshots
- Performance: All primitives O(1) or O(n) where n is small
- Maintainability: Small functions with clear responsibilities
- Accessibility: Built-in WCAG compliance checking
- None - All changes are backward compatible
- Existing screenshot and logging functionality unchanged
- New primitives are purely additive
- Code size: +~3500 lines JavaScript (~100KB uncompressed, ~30KB gzipped)
- Runtime: All primitives complete in <10ms on typical pages
- Memory: Minimal - most functions are stateless
- Interactive functions (selectElement, ask) wait for user input