Inspiration
Every frontend session with a coding agent hits the same wall. The agent writes a page, I open it, and something is off — but I can only say "this looks cheap." So I screenshot it, paste it into a chat, and the agent guesses. It cannot see that the padding is 13px while thirty-one other surfaces use 20px. It cannot measure a 3.1:1 contrast ratio from a JPEG. It has never seen the hover state, because a screenshot cannot hold one.
The browser knows all of this. It just had no way to say it.
What it does
Askew is a development-only Vite plugin that turns a running React app into a shared debugging surface for a browser AI agent.
- Click one element in your live app.
- Tell the agent something vague — "this looks cheap."
- The agent reads the real runtime truth through WebMCP: the computed cascade, the design scale the page is actually rendering, the measured contrast ratio, and how many visible instances share that source.
- It stages three Tailwind alternatives. Hovering A/B/C morphs the real component in place. Nothing has been written.
- You click Apply. One static class string changes in source, Vite HMR reconnects the selection, and the change is undoable.
Two sentences and two clicks. No screenshots, no rebuilds.
How WebMCP makes it possible
Every Askew tool had to pass one test: it must return state that exists only in this browser tab, right now, and is unavailable from any repo, API, screenshot, or MCP server.
Four things Askew reads that nothing else can hand an agent:
- Live DOM selection — what the human pointed at, one second ago
- Computed cascade — the resolved outcome of every competing declaration, not the source declarations
- Runtime design scale — spacing, radius, and colour frequencies derived from the rendered page, because
tailwind.configis aspirational and the DOM is the truth - Held interaction state —
:hoverand native focus, pinned and measured without a pointer
An agent with perfect repo access is useless here. That is the whole argument.
The design decision I am most pleased with: file writes are not an agent capability. The four tools inspect, diagnose, and stage. askew_stage_variants returns sourceWritten: false. Only the human's Apply click in Askew's own UI crosses the write boundary. Tool descriptions are the only channel for steering an agent whose system prompt you never see, so they are written as instructions — diagnose before proposing, never invent a finding when Askew reports uncertainty, warn when multiple visible instances share a source.
How I built it
npm workspaces, strict TypeScript. The plugin is apply: "serve", so it does not exist in production — production-build.test.ts runs a real vite build and asserts that no instrumentation, endpoint, or capability header survives.
Element-to-source identity comes from code-inspector-plugin with its editor server disabled. React 19 removed _debugSource, which quietly broke every click-to-component tool built on fiber inspection, so compile-time attributes were the only reliable route.
The write path is contained by construction: the client never sends a filesystem path, only a sourceId resolved against an in-memory index of already-transformed modules, then realpath-canonicalised, root-contained, extension-gated, schema-closed, and applied as a one-for-one token replacement that must still match the full expected class string or fail 409 and write nothing.
Challenges
Page JavaScript cannot force :hover. That is a DevTools capability. Askew parses direct hover: utilities and applies the equivalent declarations inline, then measures. It resolves literal colour values only — anything else returns an explicit UNSUPPORTED_STATE rather than a confident no-op, because a wrong measurement is worse than a declined one.
A false positive is worse than a missing finding. A dominant design value requires at least three samples and no tie. Contrast requires a resolvable opaque background or it reports ambiguity. A healthy element is reported as consistent, not silently skipped.
A public write endpoint. The capability token is served to every visitor, so it is not authentication. I accepted that early and made containment carry the security model instead: an unprivileged secret-free process, two allowlisted files, an explicit token allowlist, rate limiting, and automatic baseline restore.
What I learned
Scope discipline is the whole game. The spec named thirteen things I would not build — multi-select, general CSS attribution, breakpoints, group-hover, AST mutation — and holding that line is why one path works end to end instead of six paths working halfway.
What's next
Design-token file writing so fixes become the system rather than one-off patches, and broader source-shape support beyond static single-line className literals.
AI usage disclosure
Built with OpenAI Codex — this hackathon's sponsor — used throughout planning, PRD/spec iteration, implementation, test writing, deployment hardening, and live browser verification. Claude Code independently exercised the running golden path and identified selection/actionability gaps; those findings were reproduced and fixed. Gemini then performed a separate architecture and edge-case review, whose valid color parsing, token validation, selection, and HTTP-status findings were verified against the code and incorporated. I made the product, scope, security, and submission decisions and reviewed the resulting changes; the assistants accelerated implementation and acted as independent code reviewers.
Notes for judges
The hosted demo at https://askew.xpworx.com is a deliberately writable, disposable, single-user development environment. It restores itself on startup, after three idle minutes, and from the visible Reset Demo button. If two people explore at once, changes will conflict and fail stale rather than merge — press Reset Demo and try again. The video is the authoritative uninterrupted proof.
Built With
- nginx
- npm
- playwright
- pm2
- react
- tailwind
- typescript
- vite
- vitest
- webmcp
Log in or sign up for Devpost to join the conversation.