From f8b54807cfe9a3dd36b85de476b4e824ebbd688a Mon Sep 17 00:00:00 2001 From: Aiden Bai Date: Wed, 8 Apr 2026 07:50:41 -0700 Subject: [PATCH] demo: replace social feed with spreadsheet app, update overlay and docs - Replace social feed demo with spreadsheet grid demo app - Update browser overlay cursor pointer and constants - Refine website demo recording scripts and constants - Update CLI README, SKILL.md docs, and browser testing rules - Update infer-agent provider detection --- .agents/skills/expect/SKILL.md | 12 +- .cursor/rules/browser-testing.mdc | 11 +- .mcp.json | 5 +- apps/cli/README.md | 61 ++-- apps/demo/index.html | 2 +- apps/demo/src/app.tsx | 16 +- apps/demo/src/components/avatar.tsx | 41 --- apps/demo/src/components/compose-box.tsx | 49 --- apps/demo/src/components/layout.tsx | 111 +++---- apps/demo/src/components/post-card.tsx | 102 ------ apps/demo/src/components/reply-thread.tsx | 85 ----- apps/demo/src/components/spreadsheet-grid.tsx | 296 ++++++++++++++++++ apps/demo/src/error-boundary.tsx | 16 +- apps/demo/src/index.css | 86 ++--- apps/demo/src/pages/dashboard.tsx | 9 + apps/demo/src/pages/feed.tsx | 19 -- apps/demo/src/pages/login.tsx | 90 ------ apps/demo/src/pages/post-detail.tsx | 99 ------ apps/demo/src/pages/profile.tsx | 63 ---- apps/demo/src/seed.ts | 172 +++++----- apps/demo/src/store.ts | 163 +++++----- apps/demo/src/types.ts | 25 +- apps/website/lib/demo-events.ts | 20 +- apps/website/lib/demo-trace.ts | 8 +- apps/website/lib/demo/constants.ts | 36 +-- apps/website/scripts/record-demo.ts | 90 +++--- .../overlay/components/cursor-pointer.tsx | 12 +- .../browser/src/runtime/overlay/index.tsx | 37 ++- .../src/runtime/overlay/lib/constants.ts | 8 +- packages/expect-skill/SKILL.md | 10 + packages/shared/src/infer-agent.ts | 19 +- 31 files changed, 735 insertions(+), 1038 deletions(-) delete mode 100644 apps/demo/src/components/avatar.tsx delete mode 100644 apps/demo/src/components/compose-box.tsx delete mode 100644 apps/demo/src/components/post-card.tsx delete mode 100644 apps/demo/src/components/reply-thread.tsx create mode 100644 apps/demo/src/components/spreadsheet-grid.tsx create mode 100644 apps/demo/src/pages/dashboard.tsx delete mode 100644 apps/demo/src/pages/feed.tsx delete mode 100644 apps/demo/src/pages/login.tsx delete mode 100644 apps/demo/src/pages/post-detail.tsx delete mode 100644 apps/demo/src/pages/profile.tsx diff --git a/.agents/skills/expect/SKILL.md b/.agents/skills/expect/SKILL.md index 0332abe5d..6eb79c627 100644 --- a/.agents/skills/expect/SKILL.md +++ b/.agents/skills/expect/SKILL.md @@ -4,7 +4,7 @@ description: "Use when editing .tsx/.jsx/.css/.html, React components, pages, ro license: MIT metadata: author: millionco - version: "2.3.0" + version: "2.4.0" --- # Expect @@ -13,6 +13,14 @@ You verify code changes in a real browser before claiming they work. No browser Use the expect MCP tools (`open`, `playwright`, `screenshot`, etc.) for all browser interactions. Do not use raw browser tools (Playwright MCP, chrome tools, etc.) unless the user explicitly asks. +## Subagent Usage + +Browser verification is best run in a subagent (Task tool) or background shell so the main thread stays free for code edits. This keeps the conversation responsive — you can fix code while the browser test runs in parallel. Strongly prefer launching a subagent for browser work, especially when the test involves multiple steps or long interactions. If the test is truly trivial (single screenshot check), inline is acceptable. + +## Resuming Browser State + +Before opening a new browser, check if one is already running. Use `browser_tabs` (action `list`) or the expect `screenshot` tool to see if a session is still active. If a tab is already open at the target URL, reuse it — don't close and reopen. When re-verifying after a code fix, prefer navigating or refreshing the existing session over starting from scratch. + ## Compounding The `playwright` tool takes a `code` string with `ref()` to resolve snapshot refs to Locators. One call can do an entire interaction — fills, clicks, AND data collection. Use that. @@ -56,6 +64,8 @@ Use `return` to collect data. Response: `{ result: , resultFile: "` | Natural language instruction for what to test | - | -| `-f, --flow ` | Reuse a saved flow by its slug | - | -| `-y, --yes` | Run immediately without confirmation | - | -| `-a, --agent ` | Agent provider (`claude`, `codex`, `copilot`, `gemini`, `cursor`, `opencode`, `droid`, `pi`) | auto-detect | -| `-t, --target ` | What to test: `unstaged`, `branch`, or `changes` | `changes` | -| `-u, --url ` | Base URL(s) for the dev server (skips port picker) | - | -| `--browser-mode ` | Browser mode: `headed` or `headless` | `headed` | -| `--cdp ` | Connect to an existing Chrome via CDP WebSocket URL | - | -| `--profile ` | Reuse a Chrome profile by name (e.g. Default) | - | -| `--no-cookies` | Skip system browser cookie extraction | - | -| `--ci` | Force CI mode: headless, no cookies, auto-yes, 30-min timeout | - | -| `--timeout ` | Execution timeout in milliseconds | - | -| `--output ` | Output format: `text` or `json` | `text` | -| `--verbose` | Enable verbose logging | - | -| `-v, --version` | Print version | - | -| `-h, --help` | Display help | - | +| Flag | Description | Default | +| ----------------------------- | -------------------------------------------------------------------------------------- | ----------- | +| `-m, --message ` | Natural language instruction for what to test | - | +| `-f, --flow ` | Reuse a saved flow by its slug | - | +| `-y, --yes` | Run immediately without confirmation | - | +| `-a, --agent ` | Agent provider (`claude`, `codex`, `copilot`, `gemini`, `cursor`, `opencode`, `droid`) | auto-detect | +| `-t, --target ` | What to test: `unstaged`, `branch`, or `changes` | `changes` | +| `-u, --url ` | Base URL(s) for the dev server (skips port picker) | - | +| `--browser-mode ` | Browser mode: `headed` or `headless` | `headed` | +| `--cdp ` | Connect to an existing Chrome via CDP WebSocket URL | - | +| `--profile ` | Reuse a Chrome profile by name (e.g. Default) | - | +| `--no-cookies` | Skip system browser cookie extraction | - | +| `--ci` | Force CI mode: headless, no cookies, auto-yes, 30-min timeout | - | +| `--timeout ` | Execution timeout in milliseconds | - | +| `--output ` | Output format: `text` or `json` | `text` | +| `--verbose` | Enable verbose logging | - | +| `-v, --version` | Print version | - | +| `-h, --help` | Display help | - | ## Supported Agents Expect works with the following coding agents. It auto-detects which agents are installed on your `PATH`. If multiple are available, it defaults to the first one found. Use `-a ` to pick a specific agent. -| Agent | Flag | Install | -| ------------------------------------------------------------- | ------------- | ---------------------------------------------- | -| [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | `-a claude` | `npm install -g @anthropic-ai/claude-code` | -| [Codex](https://github.com/openai/codex#readme) | `-a codex` | `npm install -g @openai/codex` | -| [GitHub Copilot](https://github.com/features/copilot/cli) | `-a copilot` | `npm install -g @github/copilot` | -| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `-a gemini` | `npm install -g @google/gemini-cli` | -| [Cursor](https://cursor.com) | `-a cursor` | [cursor.com](https://cursor.com) | -| [OpenCode](https://opencode.ai) | `-a opencode` | `npm install -g opencode-ai` | -| [Factory Droid](https://factory.ai) | `-a droid` | `npm install -g droid` | -| [Pi](https://github.com/mariozechner/pi-coding-agent) | `-a pi` | `npm install -g @mariozechner/pi-coding-agent` | +| Agent | Flag | Install | +| ------------------------------------------------------------- | ------------- | ------------------------------------------ | +| [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | `-a claude` | `npm install -g @anthropic-ai/claude-code` | +| [Codex](https://github.com/openai/codex#readme) | `-a codex` | `npm install -g @openai/codex` | +| [GitHub Copilot](https://github.com/features/copilot/cli) | `-a copilot` | `npm install -g @github/copilot` | +| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `-a gemini` | `npm install -g @google/gemini-cli` | +| [Cursor](https://cursor.com) | `-a cursor` | [cursor.com](https://cursor.com) | +| [OpenCode](https://opencode.ai) | `-a opencode` | `npm install -g opencode-ai` | +| [Factory Droid](https://factory.ai) | `-a droid` | `npm install -g droid` | ## Resources & Contributing Back @@ -86,6 +85,12 @@ We expect all contributors to abide by the terms of our [Code of Conduct](https: **[→ Start contributing on GitHub](https://github.com/millionco/expect/blob/main/CONTRIBUTING.md)** +### Acknowledgements + +Expect wouldn't exist without the ideas and work of others: + +- [**dev-browser**](https://github.com/SawyerHood/dev-browser) by Sawyer Hood — the Playwright-first ("bitter lesson") approach that inspired Expect's core design: give the agent real browser APIs instead of screenshots and coordinates. + ### License FSL-1.1-MIT © [Million Software, Inc.](https://million.dev) diff --git a/apps/demo/index.html b/apps/demo/index.html index 0449baaf5..b780811b6 100644 --- a/apps/demo/index.html +++ b/apps/demo/index.html @@ -3,7 +3,7 @@ - Chirp — Demo + Sheets diff --git a/apps/demo/src/app.tsx b/apps/demo/src/app.tsx index cf3091e4d..63646f2f7 100644 --- a/apps/demo/src/app.tsx +++ b/apps/demo/src/app.tsx @@ -1,10 +1,22 @@ +import { useState } from "react"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import { ErrorBoundary } from "./error-boundary"; -import { LoginPage } from "./pages/login"; +import { Layout } from "./components/layout"; +import { DashboardPage } from "./pages/dashboard"; export const App = () => { + const [, setTick] = useState(0); + const forceUpdate = () => setTick((previous) => previous + 1); + return ( - + + + }> + } /> + + + ); }; diff --git a/apps/demo/src/components/avatar.tsx b/apps/demo/src/components/avatar.tsx deleted file mode 100644 index 18c6d40b3..000000000 --- a/apps/demo/src/components/avatar.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Avatar as ShadAvatar, AvatarFallback } from "@/components/ui/avatar"; -import { cn } from "@/lib/utils"; - -interface UserAvatarProps { - name: string; - color: string; - size?: "sm" | "md" | "lg"; - className?: string; -} - -const SIZES = { - sm: "size-8", - md: "size-10", - lg: "size-20", -}; - -const TEXT_SIZES = { - sm: "text-xs", - md: "text-sm", - lg: "text-2xl", -}; - -export const UserAvatar = ({ name, color, size = "md", className }: UserAvatarProps) => { - const initials = name - .split(" ") - .map((word) => word[0]) - .join("") - .toUpperCase() - .slice(0, 2); - - return ( - - - {initials} - - - ); -}; diff --git a/apps/demo/src/components/compose-box.tsx b/apps/demo/src/components/compose-box.tsx deleted file mode 100644 index 4f22b0387..000000000 --- a/apps/demo/src/components/compose-box.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useState } from "react"; -import { store } from "@/store"; -import { UserAvatar } from "./avatar"; -import { Button } from "@/components/ui/button"; -import { Textarea } from "@/components/ui/textarea"; -import { Separator } from "@/components/ui/separator"; - -export const ComposeBox = ({ onUpdate }: { onUpdate: () => void }) => { - const [text, setText] = useState(""); - const currentUser = store.getCurrentUser(); - - if (!currentUser) return null; - - const handleSubmit = () => { - if (!text.trim()) return; - store.createPost(text.trim()); - setText(""); - onUpdate(); - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) { - handleSubmit(); - } - }; - - return ( - <> -
- -
-