Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"mode": "pre",
"tag": "beta",
"initialVersions": {
"@agent-devtools/docs": "0.0.5",
"@agent-devtools/example-angular-vite": "0.0.0",
"@agent-devtools/example-html": "0.0.0",
"@agent-devtools/example-next": "0.0.0",
"@agent-devtools/example-next-pages": "0.0.0",
"@agent-devtools/example-nuxt": "0.0.0",
"@agent-devtools/example-nuxt2": "0.0.0",
"@agent-devtools/example-react-vite": "0.0.0",
"@agent-devtools/example-svelte-vite": "0.0.0",
"@agent-devtools/example-sveltekit": "0.0.0",
"@agent-devtools/example-vue-vite": "0.0.0",
"@agent-devtools/example-vue2-vite": "0.0.0",
"@agent-devtools/angular": "1.2.1",
"@agent-devtools/core": "1.2.1",
"@agent-devtools/e2e": "0.0.0",
"@agent-devtools/harness-core": "1.2.1",
"@agent-devtools/html": "1.2.1",
"@agent-devtools/next": "1.2.1",
"@agent-devtools/next-pages": "1.2.1",
"@agent-devtools/nuxt": "1.2.1",
"@agent-devtools/nuxt2": "1.2.1",
"@agent-devtools/react": "1.2.1",
"@agent-devtools/svelte": "1.2.1",
"@agent-devtools/sveltekit": "1.2.1",
"@agent-devtools/vite": "1.2.1",
"@agent-devtools/vue": "1.2.1",
"@agent-devtools/vue2": "1.2.1",
"@agent-devtools/widget-core": "1.2.1"
},
"changesets": []
}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ coverage/
# MCP tool artifacts (Playwright snapshots, etc.)
.playwright-mcp/

# Playwright E2E run output + marketing demo capture scratch (the final GIF
# is committed under assets/; the .webm/frames are regenerable via
# packages/e2e/scripts/capture-demo.mjs)
packages/e2e/test-results/
packages/e2e/playwright-report/
packages/e2e/demo-capture/

# npm pack smoke-test output
.pack-out/

Expand Down
10 changes: 9 additions & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,22 @@

![agent-devtools demo: launcher → picker → composer → live edit](./assets/demo.gif)

위젯에서 비활성화된 "장바구니 담기" 버튼을 픽하고 "사이즈를 골랐는데 왜 계속 disabled 야?" 라고 묻는다. 에이전트는 React fiber 체인을 거슬러 `ProductDetail` 부모까지 따라가고, picker 가 함께 실어 보낸 import 들(`useCart`, `selectors/inventory.ts`)을 따라가며 핸들러와 selector 실제 코드를 읽는다. 그러고 나서 빠져 있는 의존을 설명하거나 곧바로 `Edit` 으로 고친다. "이 리스트가 mutation 후에 왜 갱신 안 돼?", "이 폼이 validation 에러를 삼키는데 어디서 잡고 있어?" 같은 질문도 같은 흐름으로 처리된다 — picker 가 이미 컨텍스트를 패키징했기에 에이전트가 grep 부터 시작하지 않는다.
Checkout 카드의 터무니없이 부풀려진 **Grand total** 을 픽하고 _"이게 $11,691 일 리 없다, $1,400 근처여야 한다 — 숫자가 어느 파일에서 잘못되는지 추적해서 고쳐줘"_ 라고 묻는다. picker 가 컴포넌트 체인과 소스 라인을 함께 실어 보냈기에, 에이전트는 `OrderSummary.tsx` 를 읽고 import 를 따라 `cart.ts` 로 들어간 뒤 `money.ts` 를 열어 단위 계약(모든 금액은 정수 cents, `applyTaxCents` 는 이미 cents 를 반환, `formatCents` 가 100 으로 한 번만 나눔)을 확인한다. 그리고 세금을 한 번 더 100 배 하는 군더더기 `* 100` 을 범인으로 지목해, 정작 클릭하지도 않은 `cart.ts` 에 `Edit` 을 적용한다. Vite HMR 이 같은 탭 안에서 빨간 합계를 올바른 초록 `$1,402.92` 로 무너뜨린다 — IDE 없이. "이 리스트가 mutation 후에 왜 갱신 안 돼?", "이 폼이 validation 에러를 삼키는데 어디서 잡고 있어?" 같은 질문도 같은 흐름으로 처리된다 — picker 가 이미 컨텍스트를 패키징했기에 에이전트가 grep 부터 시작하지 않는다.

<p align="center">
<img src="./assets/gallery-picker-evidence.png" alt="picker 가 PickedEvidence(컴포넌트 체인·소스 파일·셀렉터)를 에이전트에게 그대로 실어 보낸다" width="840" />
</p>

- 사용자 가이드 (en / ko): <https://agent-devtools-docs.vercel.app/>
- 작동 원리 (한 장의 다이어그램): <https://agent-devtools-docs.vercel.app/guides/how-it-works/>
- 컨텍스트·스코프: [`CONTEXT.md`](./CONTEXT.md)

## 카테고리 안에서의 위치

<p align="center">
<img src="./assets/gallery-category.png" alt="agent-devtools 의 위치: 에이전트가 페이지 안에서 직접 편집 — IDE 불필요, 추가 구독 없음, 프로덕션 바이트 0" width="840" />
</p>

가장 가까운 이웃 도구들과의 사실 기반 비교. 우열이 아니라 축이 다르다.

| 축 | agent-devtools | In-page → IDE 포워더 (예: Stagewise) | 브라우저 devtools 확장 (예: React DevTools) | In-app 피드백 위젯 (예: ProductLift, Pastel) |
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,22 @@

![agent-devtools demo: launcher → picker → composer → live edit](./assets/demo.gif)

Pick the disabled "Add to cart" button in the widget and ask "why does this stay disabled after I pick a size?" The agent walks the React fiber chain back to the parent `ProductDetail`, follows the imports the picker shipped along (`useCart`, `selectors/inventory.ts`), reads the actual handler and selector source, then either explains the dependency it's missing or applies an `Edit` to fix it. The same flow handles "why doesn't this list refresh after a mutation?" or "this form swallows the validation error — where is it caught?" — context the picker already packaged so the agent does not have to grep first.
Pick the wildly inflated **Grand total** in the Checkout card and ask _"this should be about $1,400, not $11,691 — trace where the number goes wrong across the files and fix it."_ The picker ships the component chain and the source line, so the agent reads `OrderSummary.tsx`, follows the import into `cart.ts`, then opens `money.ts` to learn the unit contract (every amount is integer cents, `applyTaxCents` already returns cents, `formatCents` divides by 100 once), pins the fault to a stray `* 100` that scales the tax a second time, and applies an `Edit` to `cart.ts` — a file you never clicked. Vite HMR collapses the red total to the correct green `$1,402.92` in the same tab, no IDE involved. The same flow handles "why doesn't this list refresh after a mutation?" or "this form swallows the validation error — where is it caught?" — context the picker already packaged so the agent does not have to grep first.

<p align="center">
<img src="./assets/gallery-picker-evidence.png" alt="The picker ships PickedEvidence — component chain, source file, and selector — straight to the agent" width="840" />
</p>

- User guide (en / ko): <https://agent-devtools-docs.vercel.app/>
- How it works (single-diagram walk-through): <https://agent-devtools-docs.vercel.app/en/guides/how-it-works/>
- Context and scope: [`CONTEXT.md`](./CONTEXT.md)

## Where this sits in the category

<p align="center">
<img src="./assets/gallery-category.png" alt="Where agent-devtools sits: the agent edits inside the page — no IDE, no extra subscription, zero production bytes" width="840" />
</p>

A factual placement next to the closest neighbors — these tools all do something useful, the axes are just different.

| Axis | agent-devtools | In-page → IDE forwarder (e.g. Stagewise) | Browser devtools extension (e.g. React DevTools) | In-app feedback widget (e.g. ProductLift, Pastel) |
Expand Down
Binary file modified assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/gallery-category.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/gallery-picker-evidence.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions examples/react-vite/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, type FormEvent, type JSX } from 'react';
import { OrderSummary } from './checkout/OrderSummary.js';

interface User {
id: number;
Expand Down Expand Up @@ -33,6 +34,8 @@ export function App(): JSX.Element {
question, and watch the streamed response.
</p>

<OrderSummary />

<Counter
count={count}
onIncrement={() => setCount((n) => n + 1)}
Expand Down
60 changes: 60 additions & 0 deletions examples/react-vite/src/checkout/OrderSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { JSX } from 'react';
import { computeOrderTotals, LINE_ITEMS } from './cart.js';
import { formatCents } from './money.js';

export function OrderSummary(): JSX.Element {
const totals = computeOrderTotals(LINE_ITEMS);
// A grand total more than double the subtotal can't be right for an 8%
// tax — flag it so an obviously-off number doesn't slip past review.
const looksInflated = totals.totalCents > totals.subtotalCents * 2;

return (
<section className="card" id="checkout-card">
<h2>Checkout</h2>
<p>
Review your order before paying. An 8% tax on a $1,299.00 subtotal should land near $1,400.
</p>
<table className="order-summary">
<thead>
<tr>
<th>Item</th>
<th className="num">Qty</th>
<th className="num">Amount</th>
</tr>
</thead>
<tbody>
{LINE_ITEMS.map((item) => (
<tr key={item.id}>
<td>{item.name}</td>
<td className="num">{item.qty}</td>
<td className="num">{formatCents(item.priceCents * item.qty)}</td>
</tr>
))}
</tbody>
<tfoot>
<tr>
<td colSpan={2}>Subtotal</td>
<td className="num" data-testid="subtotal">
{formatCents(totals.subtotalCents)}
</td>
</tr>
<tr>
<td colSpan={2}>Tax (8%)</td>
<td className="num" data-testid="tax">
{formatCents(totals.taxCents)}
</td>
</tr>
<tr className={looksInflated ? 'grand-total-row is-wrong' : 'grand-total-row is-ok'}>
<td colSpan={2}>
Grand total
{looksInflated && <span className="warn"> ⚠ looks off</span>}
</td>
<td className="num" data-testid="grand-total">
{formatCents(totals.totalCents)}
</td>
</tr>
</tfoot>
</table>
</section>
);
}
37 changes: 37 additions & 0 deletions examples/react-vite/src/checkout/cart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { addCents, applyTaxCents } from './money.js';

export interface LineItem {
id: string;
name: string;
qty: number;
/** Unit price, in cents. */
priceCents: number;
}

export const TAX_RATE = 0.08;

/** Order contents. Subtotal is exactly 129900¢ ($1,299.00). */
export const LINE_ITEMS: LineItem[] = [
{ id: 'kbd', name: 'Mechanical keyboard', qty: 1, priceCents: 89900 },
{ id: 'mse', name: 'Wireless mouse', qty: 1, priceCents: 24900 },
{ id: 'pad', name: 'Desk mat', qty: 2, priceCents: 7550 },
];

export interface OrderTotals {
subtotalCents: number;
taxCents: number;
totalCents: number;
}

/** Sum of every line item (unit price × quantity), in cents. */
export function computeSubtotalCents(items: LineItem[]): number {
return items.reduce((sum, item) => addCents(sum, item.priceCents * item.qty), 0);
}

/** Subtotal, tax, and grand total for an order — all in cents. */
export function computeOrderTotals(items: LineItem[]): OrderTotals {
const subtotalCents = computeSubtotalCents(items);
const taxCents = applyTaxCents(subtotalCents, TAX_RATE);
const totalCents = addCents(subtotalCents, taxCents);
return { subtotalCents, taxCents, totalCents };
}
32 changes: 32 additions & 0 deletions examples/react-vite/src/checkout/money.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Money domain — the single source of truth for the unit contract.
*
* INVARIANT: every monetary amount in this app is an integer number of
* CENTS. Dollars never exist as a float anywhere except at the very edge,
* inside `formatCents`, purely for display.
*
* - `applyTaxCents` takes an amount in cents and returns the tax portion
* ALREADY in cents.
* - `formatCents` divides by 100 exactly once, at the display boundary,
* to render a `$1,299.00` string.
*
* A stray `* 100` or `/ 100` anywhere outside this file is almost always a
* unit bug — the conversion already happened here.
*/

/** Add two cents amounts. */
export function addCents(a: number, b: number): number {
return a + b;
}

/** Tax portion of `amountCents`, returned in cents. */
export function applyTaxCents(amountCents: number, rate: number): number {
return Math.round(amountCents * rate);
}

const usd = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });

/** Render a cents amount as a `$1,299.00` string (the only `/ 100`). */
export function formatCents(cents: number): string {
return usd.format(cents / 100);
}
53 changes: 53 additions & 0 deletions examples/react-vite/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,59 @@ h1 {
color: #b6bcc6;
}

.order-summary {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}

.order-summary th,
.order-summary td {
padding: 8px 12px;
border-bottom: 1px solid #262a33;
}

.order-summary th {
color: #9aa1ab;
font-weight: 500;
}

.order-summary .num {
text-align: right;
font-variant-numeric: tabular-nums;
}

.order-summary tfoot td {
border-bottom: 0;
}

.order-summary tfoot tr:first-child td {
border-top: 1px solid #2c3140;
padding-top: 12px;
}

.order-summary .grand-total-row td {
font-size: 19px;
font-weight: 700;
padding-top: 12px;
transition: color 200ms ease;
}

.order-summary .is-wrong td {
color: #ff6b6b;
}

.order-summary .is-ok td {
color: #4ade80;
}

.order-summary .warn {
margin-left: 8px;
font-size: 12px;
font-weight: 600;
color: #ff6b6b;
}

.counter {
display: flex;
align-items: center;
Expand Down
Loading
Loading