new mcp code. tests kind of work
This commit is contained in:
+136
-207
@@ -1,229 +1,158 @@
|
||||
import { createMCPClient } from './mcp-client.js'
|
||||
import { describe, it, expect, afterEach, beforeAll, afterAll } from 'vitest'
|
||||
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
||||
import { exec } from 'node:child_process'
|
||||
import { promisify } from 'node:util'
|
||||
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
function js(strings: TemplateStringsArray, ...values: any[]): string {
|
||||
return strings.reduce(
|
||||
(result, str, i) => result + str + (values[i] || ''),
|
||||
'',
|
||||
)
|
||||
}
|
||||
|
||||
async function killProcessOnPort(port: number): Promise<void> {
|
||||
try {
|
||||
const { stdout } = await execAsync(`lsof -ti:${port}`)
|
||||
const pid = stdout.trim()
|
||||
if (pid) {
|
||||
await execAsync(`kill -9 ${pid}`)
|
||||
console.log(`Killed process ${pid} on port ${port}`)
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
}
|
||||
} catch (error) {
|
||||
// No process running on port or already killed
|
||||
}
|
||||
}
|
||||
|
||||
describe('MCP Server Tests', () => {
|
||||
let client: Awaited<ReturnType<typeof createMCPClient>>['client']
|
||||
let cleanup: (() => Promise<void>) | null = null
|
||||
let client: Awaited<ReturnType<typeof createMCPClient>>['client']
|
||||
let cleanup: (() => Promise<void>) | null = null
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await createMCPClient()
|
||||
client = result.client
|
||||
cleanup = result.cleanup
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (cleanup) {
|
||||
await cleanup()
|
||||
cleanup = null
|
||||
}
|
||||
})
|
||||
|
||||
it('should capture console logs', async () => {
|
||||
// Connect first (open a new page)
|
||||
const connectResult = await client.callTool({
|
||||
name: 'new_page',
|
||||
arguments: {},
|
||||
beforeAll(async () => {
|
||||
await killProcessOnPort(9988)
|
||||
const result = await createMCPClient()
|
||||
client = result.client
|
||||
cleanup = result.cleanup
|
||||
})
|
||||
expect(connectResult.content).toBeDefined()
|
||||
expect(connectResult.content).toMatchInlineSnapshot(`
|
||||
|
||||
afterAll(async () => {
|
||||
if (cleanup) {
|
||||
await cleanup()
|
||||
cleanup = null
|
||||
}
|
||||
})
|
||||
|
||||
it('should execute code and capture console output', async () => {
|
||||
await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: js`
|
||||
const newPage = await context.newPage();
|
||||
state.page = newPage;
|
||||
`,
|
||||
},
|
||||
})
|
||||
|
||||
const result = await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: js`
|
||||
await state.page.goto('https://news.ycombinator.com');
|
||||
const title = await state.page.title();
|
||||
console.log('Page title:', title);
|
||||
return { url: state.page.url(), title };
|
||||
`,
|
||||
},
|
||||
})
|
||||
expect(result.content).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"text": "Created new page. URL: about:blank. Total pages: 20",
|
||||
"text": "Console output:
|
||||
[log] Page title: Hacker News
|
||||
|
||||
Return value:
|
||||
{
|
||||
"url": "https://news.ycombinator.com/",
|
||||
"title": "Hacker News"
|
||||
}",
|
||||
"type": "text",
|
||||
},
|
||||
]
|
||||
`)
|
||||
expect(result.content).toBeDefined()
|
||||
}, 30000)
|
||||
|
||||
// Navigate to a page and log something
|
||||
const result = await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: `
|
||||
await page.goto('https://news.ycombinator.com');
|
||||
await page.evaluate(() => {
|
||||
console.log('Test log message');
|
||||
console.error('Test error message');
|
||||
});
|
||||
`,
|
||||
},
|
||||
})
|
||||
expect(result.content).toBeDefined()
|
||||
expect(result.content).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"text": "Code executed successfully (no output)",
|
||||
"type": "text",
|
||||
it('should get accessibility snapshot of hacker news', async () => {
|
||||
await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: js`
|
||||
const newPage = await context.newPage();
|
||||
state.page = newPage;
|
||||
`,
|
||||
},
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
||||
// Get console logs
|
||||
const logsResult = (await client.callTool({
|
||||
name: 'console_logs',
|
||||
arguments: {
|
||||
limit: 10,
|
||||
},
|
||||
})) as CallToolResult
|
||||
|
||||
expect(logsResult.content).toBeDefined()
|
||||
expect(logsResult.content).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"text": "[log]: Test log message :1:32
|
||||
[error]: Test error message :2:32",
|
||||
"type": "text",
|
||||
const result = await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: js`
|
||||
await state.page.goto('https://news.ycombinator.com/item?id=1', { waitUntil: 'networkidle' });
|
||||
const snapshot = await state.page._snapshotForAI();
|
||||
return snapshot;
|
||||
`,
|
||||
},
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
||||
// Close the page opened
|
||||
await client.callTool({
|
||||
name: 'close_page',
|
||||
arguments: {},
|
||||
})
|
||||
}, 30000)
|
||||
const initialData =
|
||||
typeof result === 'object' && result.content?.[0]?.text
|
||||
? tryJsonParse(result.content[0].text)
|
||||
: result
|
||||
expect(initialData).toMatchFileSnapshot(
|
||||
'snapshots/hacker-news-initial-accessibility.md',
|
||||
)
|
||||
expect(result.content).toBeDefined()
|
||||
expect(initialData).toContain('table')
|
||||
expect(initialData).toContain('Hacker News')
|
||||
}, 30000)
|
||||
|
||||
it('should capture accessibility snapshot of hacker news', async () => {
|
||||
// Create new page
|
||||
await client.callTool({
|
||||
name: 'new_page',
|
||||
arguments: {},
|
||||
})
|
||||
it('should get accessibility snapshot of shadcn UI', async () => {
|
||||
await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: js`
|
||||
const newPage = await context.newPage();
|
||||
state.page = newPage;
|
||||
`,
|
||||
},
|
||||
})
|
||||
|
||||
// Navigate to a specific old Hacker News story that won't change
|
||||
await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: `await page.goto('https://news.ycombinator.com/item?id=1', { waitUntil: 'networkidle' })`,
|
||||
},
|
||||
})
|
||||
const snapshot = await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: js`
|
||||
await state.page.goto('https://ui.shadcn.com/', { waitUntil: 'networkidle' });
|
||||
const snapshot = await state.page._snapshotForAI();
|
||||
return snapshot;
|
||||
`,
|
||||
},
|
||||
})
|
||||
|
||||
// Get initial accessibility snapshot
|
||||
const initialSnapshot = await client.callTool({
|
||||
name: 'accessibility_snapshot',
|
||||
arguments: {},
|
||||
})
|
||||
expect(initialSnapshot.content).toBeDefined()
|
||||
|
||||
// Save initial snapshot
|
||||
const initialData =
|
||||
typeof initialSnapshot === 'object' && initialSnapshot.content?.[0]?.text
|
||||
? tryJsonParse(initialSnapshot.content[0].text)
|
||||
: initialSnapshot
|
||||
expect(initialData).toMatchFileSnapshot('snapshots/hacker-news-initial-accessibility.md')
|
||||
expect(initialData).toContain('table')
|
||||
expect(initialData).toContain('Hacker News')
|
||||
|
||||
// Focus on first link on the page
|
||||
await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: `
|
||||
// Find and focus the first link
|
||||
const firstLink = await page.$('a')
|
||||
if (firstLink) {
|
||||
await firstLink.focus()
|
||||
const linkText = await firstLink.textContent()
|
||||
console.log('Focused on first link:', linkText)
|
||||
}
|
||||
`,
|
||||
},
|
||||
})
|
||||
|
||||
// Get snapshot after focusing
|
||||
const focusedSnapshot = await client.callTool({
|
||||
name: 'accessibility_snapshot',
|
||||
arguments: {},
|
||||
})
|
||||
expect(focusedSnapshot.content).toBeDefined()
|
||||
|
||||
// Save focused snapshot
|
||||
const focusedData =
|
||||
typeof focusedSnapshot === 'object' && focusedSnapshot.content?.[0]?.text
|
||||
? tryJsonParse(focusedSnapshot.content[0].text)
|
||||
: focusedSnapshot
|
||||
expect(focusedData).toMatchFileSnapshot('snapshots/hacker-news-focused-accessibility.md')
|
||||
|
||||
// Verify the snapshot contains expected content
|
||||
expect(focusedData).toBeDefined()
|
||||
expect(focusedData).toContain('link')
|
||||
|
||||
// Press Tab to go to next item
|
||||
await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: `
|
||||
await page.keyboard.press('Tab')
|
||||
console.log('Pressed Tab key')
|
||||
`,
|
||||
},
|
||||
})
|
||||
|
||||
// Get snapshot after tab navigation
|
||||
const tabbedSnapshot = await client.callTool({
|
||||
name: 'accessibility_snapshot',
|
||||
arguments: {},
|
||||
})
|
||||
expect(tabbedSnapshot.content).toBeDefined()
|
||||
|
||||
// Save tabbed snapshot
|
||||
const tabbedData =
|
||||
typeof tabbedSnapshot === 'object' && tabbedSnapshot.content?.[0]?.text
|
||||
? tryJsonParse(tabbedSnapshot.content[0].text)
|
||||
: tabbedSnapshot
|
||||
expect(tabbedData).toMatchFileSnapshot('snapshots/hacker-news-tabbed-accessibility.md')
|
||||
|
||||
// Verify the snapshot is different
|
||||
expect(tabbedData).toBeDefined()
|
||||
expect(tabbedData).toContain('Hacker News')
|
||||
|
||||
// Close the page opened
|
||||
await client.callTool({
|
||||
name: 'close_page',
|
||||
arguments: {},
|
||||
})
|
||||
}, 30000)
|
||||
|
||||
it('should capture accessibility snapshot of shadcn UI', async () => {
|
||||
// Create new page
|
||||
await client.callTool({
|
||||
name: 'new_page',
|
||||
arguments: {},
|
||||
})
|
||||
|
||||
// Navigate to shadcn UI
|
||||
await client.callTool({
|
||||
name: 'execute',
|
||||
arguments: {
|
||||
code: `await page.goto('https://ui.shadcn.com/', { waitUntil: 'networkidle' })`,
|
||||
},
|
||||
})
|
||||
|
||||
// Get accessibility snapshot
|
||||
const snapshot = await client.callTool({
|
||||
name: 'accessibility_snapshot',
|
||||
arguments: {},
|
||||
})
|
||||
expect(snapshot.content).toBeDefined()
|
||||
|
||||
// Save snapshot
|
||||
const data =
|
||||
typeof snapshot === 'object' && snapshot.content?.[0]?.text ? tryJsonParse(snapshot.content[0].text) : snapshot
|
||||
expect(data).toMatchFileSnapshot('snapshots/shadcn-ui-accessibility.md')
|
||||
expect(data).toContain('shadcn')
|
||||
|
||||
// Close the page opened
|
||||
await client.callTool({
|
||||
name: 'close_page',
|
||||
arguments: {},
|
||||
})
|
||||
}, 30000)
|
||||
const data =
|
||||
typeof snapshot === 'object' && snapshot.content?.[0]?.text
|
||||
? tryJsonParse(snapshot.content[0].text)
|
||||
: snapshot
|
||||
expect(data).toMatchFileSnapshot('snapshots/shadcn-ui-accessibility.md')
|
||||
expect(snapshot.content).toBeDefined()
|
||||
expect(data).toContain('shadcn')
|
||||
}, 30000)
|
||||
})
|
||||
function tryJsonParse(str: string) {
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch {
|
||||
return str
|
||||
}
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
+66
-523
@@ -1,239 +1,78 @@
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
||||
import { z } from 'zod'
|
||||
import { Page, Browser, BrowserContext, chromium } from 'playwright-core'
|
||||
import { Page, Browser, chromium } from 'playwright-core'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
import { spawn } from 'child_process'
|
||||
import type { ChildProcess } from 'child_process'
|
||||
import { getBrowserExecutablePath } from './browser-config.js'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createRequire } from 'node:module'
|
||||
import vm from 'node:vm'
|
||||
|
||||
// Chrome executable finding logic moved to browser-config.ts
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
// Store for maintaining state across tool calls
|
||||
interface ToolState {
|
||||
isConnected: boolean
|
||||
page: Page | null
|
||||
browser: Browser | null
|
||||
chromeProcess: ChildProcess | null
|
||||
consoleLogs: Map<Page, ConsoleMessage[]>
|
||||
networkRequests: Map<Page, NetworkRequest[]>
|
||||
}
|
||||
|
||||
const state: ToolState = {
|
||||
isConnected: false,
|
||||
page: null,
|
||||
browser: null,
|
||||
chromeProcess: null,
|
||||
consoleLogs: new Map(),
|
||||
networkRequests: new Map(),
|
||||
}
|
||||
|
||||
interface ConsoleMessage {
|
||||
type: string
|
||||
text: string
|
||||
timestamp: number
|
||||
location?: {
|
||||
url: string
|
||||
lineNumber: number
|
||||
columnNumber: number
|
||||
}
|
||||
}
|
||||
const RELAY_PORT = 9988
|
||||
|
||||
interface NetworkRequest {
|
||||
url: string
|
||||
method: string
|
||||
status: number
|
||||
headers: Record<string, string>
|
||||
timestamp: number
|
||||
duration: number
|
||||
size: number
|
||||
requestBody?: any
|
||||
responseBody?: any
|
||||
}
|
||||
|
||||
const CDP_PORT = 9922
|
||||
|
||||
// Check if CDP is available on the specified port
|
||||
async function isCDPAvailable(): Promise<boolean> {
|
||||
async function isPortTaken(port: number): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${CDP_PORT}/json/version`)
|
||||
const response = await fetch(`http://localhost:${port}/`)
|
||||
return response.ok
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Launch Chrome with CDP enabled
|
||||
async function launchChromeWithCDP(): Promise<ChildProcess> {
|
||||
const userDataDir = path.join(os.homedir(), '.playwriter')
|
||||
if (!fs.existsSync(userDataDir)) {
|
||||
fs.mkdirSync(userDataDir, { recursive: true })
|
||||
async function ensureRelayServer(): Promise<void> {
|
||||
const portTaken = await isPortTaken(RELAY_PORT)
|
||||
|
||||
if (portTaken) {
|
||||
console.error('CDP relay server already running')
|
||||
return
|
||||
}
|
||||
|
||||
const executablePath = getBrowserExecutablePath()
|
||||
console.error('Starting CDP relay server...')
|
||||
|
||||
const chromeArgs = [
|
||||
`--remote-debugging-port=${CDP_PORT}`,
|
||||
`--user-data-dir=${userDataDir}`,
|
||||
'--no-first-run',
|
||||
'--no-default-browser-check',
|
||||
'--disable-session-crashed-bubble',
|
||||
'--disable-features=DevToolsDebuggingRestrictions',
|
||||
'--disable-blink-features=AutomationControlled',
|
||||
'--no-sandbox',
|
||||
'--disable-web-security',
|
||||
'--disable-infobars',
|
||||
'--disable-translate',
|
||||
'--disable-features=AutomationControlled', // disables --enable-automation
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-backgrounding-occluded-windows',
|
||||
'--disable-renderer-backgrounding',
|
||||
'--disable-window-activation',
|
||||
'--disable-focus-on-load',
|
||||
'--no-startup-window',
|
||||
'--window-position=0,0',
|
||||
'--disable-site-isolation-trials',
|
||||
'--disable-features=IsolateOrigins,site-per-process',
|
||||
]
|
||||
const scriptPath = require.resolve('../dist/start-relay-server.js')
|
||||
|
||||
const chromeProcess = spawn(executablePath, chromeArgs, {
|
||||
const serverProcess = spawn(process.execPath, [scriptPath], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
})
|
||||
|
||||
// Unref the process so it doesn't keep the parent process alive
|
||||
chromeProcess.unref()
|
||||
serverProcess.unref()
|
||||
|
||||
// Give Chrome time to start up
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
// wait for extension to connect
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
return chromeProcess
|
||||
console.error('CDP relay server started')
|
||||
}
|
||||
|
||||
// Ensure connection to Chrome via CDP
|
||||
async function ensureConnection(): Promise<{ browser: Browser; page: Page }> {
|
||||
if (state.isConnected && state.browser && state.page) {
|
||||
return { browser: state.browser, page: state.page }
|
||||
}
|
||||
|
||||
// Check if CDP is already available
|
||||
const cdpAvailable = await isCDPAvailable()
|
||||
await ensureRelayServer()
|
||||
|
||||
if (!cdpAvailable) {
|
||||
// Launch Chrome with CDP
|
||||
const chromeProcess = await launchChromeWithCDP()
|
||||
state.chromeProcess = chromeProcess
|
||||
}
|
||||
const cdpEndpoint = `ws://localhost:${RELAY_PORT}/cdp/${Date.now()}`
|
||||
const browser = await chromium.connectOverCDP(cdpEndpoint)
|
||||
|
||||
// Connect to Chrome via CDP
|
||||
const browser = await chromium.connectOverCDP(`http://127.0.0.1:${CDP_PORT}`)
|
||||
|
||||
// Get the default context
|
||||
const contexts = browser.contexts()
|
||||
let context: BrowserContext
|
||||
const context = contexts.length > 0 ? contexts[0] : await browser.newContext()
|
||||
|
||||
if (contexts.length > 0) {
|
||||
context = contexts[0]
|
||||
} else {
|
||||
context = await browser.newContext()
|
||||
}
|
||||
|
||||
// Generate user agent and set it on context
|
||||
const { default: UserAgent } = await import('user-agents')
|
||||
const userAgent = new UserAgent({
|
||||
platform: 'MacIntel',
|
||||
deviceCategory: 'desktop',
|
||||
})
|
||||
|
||||
// Get or create page
|
||||
const pages = context.pages()
|
||||
let page: Page
|
||||
|
||||
if (pages.length > 0) {
|
||||
page = pages[0]
|
||||
// Set user agent on existing page
|
||||
await page.setExtraHTTPHeaders({
|
||||
'User-Agent': userAgent.toString(),
|
||||
})
|
||||
} else {
|
||||
page = await context.newPage()
|
||||
// Set user agent on new page
|
||||
await page.setExtraHTTPHeaders({
|
||||
'User-Agent': userAgent.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
// Set up event listeners if not already set
|
||||
if (!state.isConnected) {
|
||||
page.on('console', (msg) => {
|
||||
// Get or create logs array for this page
|
||||
let pageLogs = state.consoleLogs.get(page)
|
||||
if (!pageLogs) {
|
||||
pageLogs = []
|
||||
state.consoleLogs.set(page, pageLogs)
|
||||
}
|
||||
|
||||
// Add new log
|
||||
pageLogs.push({
|
||||
type: msg.type(),
|
||||
text: msg.text(),
|
||||
timestamp: Date.now(),
|
||||
location: msg.location(),
|
||||
})
|
||||
|
||||
// Keep only last 1000 logs
|
||||
if (pageLogs.length > 1000) {
|
||||
pageLogs.shift()
|
||||
}
|
||||
})
|
||||
|
||||
// Clean up logs and network requests when page is closed
|
||||
page.on('close', () => {
|
||||
state.consoleLogs.delete(page)
|
||||
state.networkRequests.delete(page)
|
||||
})
|
||||
|
||||
page.on('request', (request) => {
|
||||
const startTime = Date.now()
|
||||
const entry: Partial<NetworkRequest> = {
|
||||
url: request.url(),
|
||||
method: request.method(),
|
||||
headers: request.headers(),
|
||||
timestamp: startTime,
|
||||
}
|
||||
|
||||
request
|
||||
.response()
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
entry.status = response.status()
|
||||
entry.duration = Date.now() - startTime
|
||||
entry.size = response.headers()['content-length'] ? parseInt(response.headers()['content-length']) : 0
|
||||
|
||||
// Get or create requests array for this page
|
||||
let pageRequests = state.networkRequests.get(page)
|
||||
if (!pageRequests) {
|
||||
pageRequests = []
|
||||
state.networkRequests.set(page, pageRequests)
|
||||
}
|
||||
|
||||
// Add new request
|
||||
pageRequests.push(entry as NetworkRequest)
|
||||
|
||||
// Keep only last 1000 requests
|
||||
if (pageRequests.length > 1000) {
|
||||
pageRequests.shift()
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Handle response errors silently
|
||||
})
|
||||
})
|
||||
}
|
||||
const page = pages.length > 0 ? pages[0] : await context.newPage()
|
||||
|
||||
state.browser = browser
|
||||
state.page = page
|
||||
@@ -242,319 +81,30 @@ async function ensureConnection(): Promise<{ browser: Browser; page: Page }> {
|
||||
return { browser, page }
|
||||
}
|
||||
|
||||
// Initialize MCP server
|
||||
function getCurrentPage(): Page {
|
||||
if (state.page) {
|
||||
return state.page
|
||||
}
|
||||
|
||||
if (state.browser) {
|
||||
const contexts = state.browser.contexts()
|
||||
if (contexts.length > 0) {
|
||||
const pages = contexts[0].pages()
|
||||
if (pages.length > 0) {
|
||||
return pages[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('No page available')
|
||||
}
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'playwriter',
|
||||
title: 'Playwright MCP Server',
|
||||
version: '1.0.0',
|
||||
})
|
||||
|
||||
// Tool 1: New Page - Creates a new browser page
|
||||
server.tool('new_page', 'Create a new browser page in the shared Chrome instance', {}, async () => {
|
||||
try {
|
||||
const { browser, page } = await ensureConnection()
|
||||
|
||||
// Always create a new page
|
||||
const context = browser.contexts()[0] || (await browser.newContext())
|
||||
const newPage = await context.newPage()
|
||||
|
||||
// Set user agent on new page
|
||||
const { default: UserAgent } = await import('user-agents')
|
||||
const userAgent = new UserAgent({
|
||||
platform: 'MacIntel',
|
||||
deviceCategory: 'desktop',
|
||||
})
|
||||
await newPage.setExtraHTTPHeaders({
|
||||
'User-Agent': userAgent.toString(),
|
||||
})
|
||||
|
||||
// Update state to use the new page
|
||||
state.page = newPage
|
||||
|
||||
// Set up event listeners on the new page
|
||||
newPage.on('console', (msg) => {
|
||||
// Get or create logs array for this page
|
||||
let pageLogs = state.consoleLogs.get(newPage)
|
||||
if (!pageLogs) {
|
||||
pageLogs = []
|
||||
state.consoleLogs.set(newPage, pageLogs)
|
||||
}
|
||||
|
||||
// Add new log
|
||||
pageLogs.push({
|
||||
type: msg.type(),
|
||||
text: msg.text(),
|
||||
timestamp: Date.now(),
|
||||
location: msg.location(),
|
||||
})
|
||||
|
||||
// Keep only last 1000 logs
|
||||
if (pageLogs.length > 1000) {
|
||||
pageLogs.shift()
|
||||
}
|
||||
})
|
||||
|
||||
// Clean up logs and network requests when page is closed
|
||||
newPage.on('close', () => {
|
||||
state.consoleLogs.delete(newPage)
|
||||
state.networkRequests.delete(newPage)
|
||||
})
|
||||
|
||||
newPage.on('request', (request) => {
|
||||
const startTime = Date.now()
|
||||
const entry: Partial<NetworkRequest> = {
|
||||
url: request.url(),
|
||||
method: request.method(),
|
||||
headers: request.headers(),
|
||||
timestamp: startTime,
|
||||
}
|
||||
|
||||
request
|
||||
.response()
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
entry.status = response.status()
|
||||
entry.duration = Date.now() - startTime
|
||||
entry.size = response.headers()['content-length'] ? parseInt(response.headers()['content-length']) : 0
|
||||
|
||||
// Get or create requests array for this page
|
||||
let pageRequests = state.networkRequests.get(newPage)
|
||||
if (!pageRequests) {
|
||||
pageRequests = []
|
||||
state.networkRequests.set(newPage, pageRequests)
|
||||
}
|
||||
|
||||
// Add new request
|
||||
pageRequests.push(entry as NetworkRequest)
|
||||
|
||||
// Keep only last 1000 requests
|
||||
if (pageRequests.length > 1000) {
|
||||
pageRequests.shift()
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Handle response errors silently
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Created new page. URL: ${newPage.url()}. Total pages: ${context.pages().length}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Failed to create new page: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Tool 2: Console Logs
|
||||
server.tool(
|
||||
'console_logs',
|
||||
'Retrieve console messages from the page',
|
||||
{
|
||||
limit: z.number().default(50).describe('Maximum number of messages to return'),
|
||||
type: z.enum(['log', 'info', 'warning', 'error', 'debug']).optional().describe('Filter by message type'),
|
||||
offset: z.number().default(0).describe('Start from this index'),
|
||||
},
|
||||
async ({ limit, type, offset }) => {
|
||||
try {
|
||||
const { page } = await ensureConnection() // Ensure we're connected first
|
||||
|
||||
// Get logs for current page
|
||||
const pageLogs = state.consoleLogs.get(page) || []
|
||||
|
||||
// Filter and paginate logs
|
||||
let logs = [...pageLogs]
|
||||
if (type) {
|
||||
logs = logs.filter((log) => log.type === type)
|
||||
}
|
||||
|
||||
const paginatedLogs = logs.slice(offset, offset + limit)
|
||||
|
||||
// Format logs to look like Chrome console output
|
||||
let consoleOutput = ''
|
||||
|
||||
if (paginatedLogs.length === 0) {
|
||||
consoleOutput = 'No console messages'
|
||||
} else {
|
||||
consoleOutput = paginatedLogs
|
||||
.map((log) => {
|
||||
const timestamp = new Date(log.timestamp).toLocaleTimeString()
|
||||
const location = log.location
|
||||
? ` ${log.location.url}:${log.location.lineNumber}:${log.location.columnNumber}`
|
||||
: ''
|
||||
return `[${log.type}]: ${log.text}${location}`
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
if (logs.length > paginatedLogs.length) {
|
||||
consoleOutput += `\n\n(Showing ${paginatedLogs.length} of ${logs.length} total messages)`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: consoleOutput,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Failed to get console logs: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Tool 3: Network History
|
||||
server.tool(
|
||||
'network_history',
|
||||
'Get history of network requests',
|
||||
{
|
||||
limit: z.number().default(50).describe('Maximum number of requests to return'),
|
||||
urlPattern: z.string().optional().describe('Filter by URL pattern (supports wildcards)'),
|
||||
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).optional().describe('Filter by HTTP method'),
|
||||
statusCode: z
|
||||
.object({
|
||||
min: z.number().optional(),
|
||||
max: z.number().optional(),
|
||||
})
|
||||
.optional()
|
||||
.describe('Filter by status code range'),
|
||||
includeBody: z.boolean().default(false).describe('Include request/response bodies'),
|
||||
},
|
||||
async ({ limit, urlPattern, method, statusCode, includeBody }) => {
|
||||
try {
|
||||
const { page } = await ensureConnection()
|
||||
|
||||
// Get requests for current page
|
||||
const pageRequests = state.networkRequests.get(page) || []
|
||||
|
||||
// If includeBody is requested, we need to fetch bodies for existing requests
|
||||
if (includeBody && pageRequests.length > 0) {
|
||||
// Note: In a real implementation, you'd store bodies during capture
|
||||
console.warn('Body capture not implemented in this example')
|
||||
}
|
||||
|
||||
// Filter requests
|
||||
let requests = [...pageRequests]
|
||||
|
||||
if (urlPattern) {
|
||||
const pattern = new RegExp(urlPattern.replace(/\*/g, '.*'))
|
||||
requests = requests.filter((req) => pattern.test(req.url))
|
||||
}
|
||||
|
||||
if (method) {
|
||||
requests = requests.filter((req) => req.method === method)
|
||||
}
|
||||
|
||||
if (statusCode) {
|
||||
requests = requests.filter((req) => {
|
||||
if (statusCode.min && req.status < statusCode.min) return false
|
||||
if (statusCode.max && req.status > statusCode.max) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
const limitedRequests = requests.slice(-limit)
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(
|
||||
{
|
||||
total: requests.length,
|
||||
requests: limitedRequests,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Failed to get network history: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Tool 4: Accessibility Snapshot - Get page accessibility tree as JSON
|
||||
server.tool('accessibility_snapshot', 'Get the accessibility snapshot of the current page as JSON', {}, async ({}) => {
|
||||
try {
|
||||
const { page } = await ensureConnection()
|
||||
|
||||
// Check if the method exists
|
||||
if (typeof (page as any)._snapshotForAI !== 'function') {
|
||||
// Fall back to regular accessibility snapshot
|
||||
const snapshot = await page.accessibility.snapshot({
|
||||
interestingOnly: true,
|
||||
root: undefined,
|
||||
})
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(snapshot, null, 2),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const snapshot = await (page as any)._snapshotForAI()
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: snapshot,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Accessibility snapshot error:', error)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Failed to get accessibility snapshot: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Tool 5: Execute - Run arbitrary JavaScript code with page and context in scope
|
||||
const promptContent = fs.readFileSync(path.join(path.dirname(new URL(import.meta.url).pathname), 'prompt.md'), 'utf-8')
|
||||
|
||||
server.tool(
|
||||
@@ -569,14 +119,15 @@ server.tool(
|
||||
timeout: z.number().default(3000).describe('Timeout in milliseconds for code execution (default: 3000ms)'),
|
||||
},
|
||||
async ({ code, timeout }) => {
|
||||
const { page } = await ensureConnection()
|
||||
await ensureConnection()
|
||||
|
||||
const page = getCurrentPage()
|
||||
const context = page.context()
|
||||
|
||||
console.error('Executing code:', code)
|
||||
try {
|
||||
// Collect console logs during execution
|
||||
const consoleLogs: Array<{ method: string; args: any[] }> = []
|
||||
|
||||
// Create a custom console object that collects logs
|
||||
const customConsole = {
|
||||
log: (...args: any[]) => {
|
||||
consoleLogs.push({ method: 'log', args })
|
||||
@@ -595,30 +146,27 @@ server.tool(
|
||||
},
|
||||
}
|
||||
|
||||
// Create a function that has page, context, and console in scope
|
||||
const executeCode = new Function(
|
||||
'page',
|
||||
'context',
|
||||
'console',
|
||||
`
|
||||
return (async () => {
|
||||
${code}
|
||||
})();
|
||||
`,
|
||||
)
|
||||
const vmContext = vm.createContext({
|
||||
page,
|
||||
context,
|
||||
state,
|
||||
console: customConsole,
|
||||
})
|
||||
|
||||
const wrappedCode = `(async () => { ${code} })()`
|
||||
|
||||
// Execute the code with page, context, and custom console with timeout
|
||||
const result = await Promise.race([
|
||||
executeCode(page, context, customConsole),
|
||||
vm.runInContext(wrappedCode, vmContext, {
|
||||
timeout,
|
||||
displayErrors: true,
|
||||
}),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error(`Code execution timed out after ${timeout}ms`)), timeout),
|
||||
),
|
||||
])
|
||||
|
||||
// Format the response with both console output and return value
|
||||
let responseText = ''
|
||||
|
||||
// Add console logs if any
|
||||
if (consoleLogs.length > 0) {
|
||||
responseText += 'Console output:\n'
|
||||
consoleLogs.forEach(({ method, args }) => {
|
||||
@@ -635,10 +183,13 @@ server.tool(
|
||||
responseText += '\n'
|
||||
}
|
||||
|
||||
// Add return value if any
|
||||
if (result !== undefined) {
|
||||
responseText += 'Return value:\n'
|
||||
responseText += JSON.stringify(result, null, 2)
|
||||
if (typeof result === "string") {
|
||||
responseText += result
|
||||
} else {
|
||||
responseText += JSON.stringify(result, null, 2)
|
||||
}
|
||||
} else if (consoleLogs.length === 0) {
|
||||
responseText += 'Code executed successfully (no output)'
|
||||
}
|
||||
@@ -656,7 +207,7 @@ server.tool(
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error executing code: ${error.message}\n${error.stack}`,
|
||||
text: `Error executing code: ${error.message}\n${error.stack || ''}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
@@ -672,25 +223,17 @@ async function main() {
|
||||
console.error('Playwright MCP server running on stdio')
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
async function cleanup() {
|
||||
console.error('Shutting down MCP server...')
|
||||
|
||||
if (state.browser) {
|
||||
try {
|
||||
// Close the browser connection but not the Chrome process
|
||||
// Since we're using CDP, closing the browser object just closes
|
||||
// the connection, not the actual Chrome instance
|
||||
await state.browser.close()
|
||||
} catch (e) {
|
||||
// Ignore errors during browser close
|
||||
}
|
||||
}
|
||||
|
||||
// Don't kill the Chrome process - let it continue running
|
||||
// The process was started with detached: true and unref()
|
||||
// so it will persist after this process exits
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,202 +1,318 @@
|
||||
Return value:
|
||||
- table [ref=e3]:
|
||||
- rowgroup [ref=e4]:
|
||||
- row "Hacker Newsnew | past | comments | ask | show | jobs | submit login" [ref=e5]:
|
||||
- cell "Hacker Newsnew | past | comments | ask | show | jobs | submit login" [ref=e6]:
|
||||
- row "Hacker Newsnew | threads | past | comments | ask | show | jobs | submit xmorse (70) | logout" [ref=e5]:
|
||||
- cell "Hacker Newsnew | threads | past | comments | ask | show | jobs | submit xmorse (70) | logout" [ref=e6]:
|
||||
- table [ref=e7]:
|
||||
- rowgroup [ref=e8]:
|
||||
- row "Hacker Newsnew | past | comments | ask | show | jobs | submit login" [ref=e9]:
|
||||
- row "Hacker Newsnew | threads | past | comments | ask | show | jobs | submit xmorse (70) | logout" [ref=e9]:
|
||||
- cell [ref=e10]:
|
||||
- link [ref=e11] [cursor=pointer]:
|
||||
- /url: https://news.ycombinator.com
|
||||
- img [ref=e12] [cursor=pointer]
|
||||
- cell "Hacker Newsnew | past | comments | ask | show | jobs | submit" [ref=e13]:
|
||||
- img [ref=e12]
|
||||
- cell "Hacker Newsnew | threads | past | comments | ask | show | jobs | submit" [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- link "Hacker News" [ref=e16] [cursor=pointer]:
|
||||
- /url: news
|
||||
- link "new" [ref=e17] [cursor=pointer]:
|
||||
- /url: newest
|
||||
- text: "|"
|
||||
- link "past" [ref=e18] [cursor=pointer]:
|
||||
- link "threads" [ref=e18] [cursor=pointer]:
|
||||
- /url: threads?id=xmorse
|
||||
- text: "|"
|
||||
- link "past" [ref=e19] [cursor=pointer]:
|
||||
- /url: front
|
||||
- text: "|"
|
||||
- link "comments" [ref=e19] [cursor=pointer]:
|
||||
- link "comments" [ref=e20] [cursor=pointer]:
|
||||
- /url: newcomments
|
||||
- text: "|"
|
||||
- link "ask" [ref=e20] [cursor=pointer]:
|
||||
- link "ask" [ref=e21] [cursor=pointer]:
|
||||
- /url: ask
|
||||
- text: "|"
|
||||
- link "show" [ref=e21] [cursor=pointer]:
|
||||
- link "show" [ref=e22] [cursor=pointer]:
|
||||
- /url: show
|
||||
- text: "|"
|
||||
- link "jobs" [ref=e22] [cursor=pointer]:
|
||||
- link "jobs" [ref=e23] [cursor=pointer]:
|
||||
- /url: jobs
|
||||
- text: "|"
|
||||
- link "submit" [ref=e23] [cursor=pointer]:
|
||||
- link "submit" [ref=e24] [cursor=pointer]:
|
||||
- /url: submit
|
||||
- cell "login" [ref=e24]:
|
||||
- link "login" [ref=e26] [cursor=pointer]:
|
||||
- /url: login?goto=item%3Fid%3D1
|
||||
- row [ref=e27]
|
||||
- row "upvote Y Combinator (ycombinator.com) 57 points by pg on Oct 9, 2006 | hide | past | favorite | 3 comments upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road? upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e28]:
|
||||
- cell "upvote Y Combinator (ycombinator.com) 57 points by pg on Oct 9, 2006 | hide | past | favorite | 3 comments upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road? upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e29]:
|
||||
- table [ref=e30]:
|
||||
- rowgroup [ref=e31]:
|
||||
- row "upvote Y Combinator (ycombinator.com)" [ref=e32]:
|
||||
- cell [ref=e33]
|
||||
- cell "upvote" [ref=e35]:
|
||||
- link "upvote" [ref=e37] [cursor=pointer]:
|
||||
- /url: vote?id=1&how=up&goto=item%3Fid%3D1
|
||||
- cell "Y Combinator (ycombinator.com)" [ref=e39]:
|
||||
- generic [ref=e40]:
|
||||
- link "Y Combinator" [ref=e41] [cursor=pointer]:
|
||||
- cell "xmorse (70) | logout" [ref=e25]:
|
||||
- generic [ref=e26]:
|
||||
- link "xmorse" [ref=e27] [cursor=pointer]:
|
||||
- /url: user?id=xmorse
|
||||
- text: (70) |
|
||||
- link "logout" [ref=e28] [cursor=pointer]:
|
||||
- /url: logout?auth=227f860372905c327e92615af914e1e41ec43fff&goto=item%3Fid%3D1
|
||||
- row [ref=e29]
|
||||
- row [ref=e30]:
|
||||
- cell [ref=e31]:
|
||||
- table [ref=e32]:
|
||||
- rowgroup [ref=e33]:
|
||||
- row "upvote Y Combinator (ycombinator.com)" [ref=e34]:
|
||||
- cell [ref=e35]
|
||||
- cell "upvote" [ref=e36]:
|
||||
- link "upvote" [ref=e38] [cursor=pointer]:
|
||||
- /url: vote?id=1&how=up&auth=e52b391ec392eb381ace51126fd80e19d48c1d7b&goto=item%3Fid%3D1
|
||||
- generic "upvote" [ref=e39]
|
||||
- cell "Y Combinator (ycombinator.com)" [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- link "Y Combinator" [ref=e42] [cursor=pointer]:
|
||||
- /url: http://ycombinator.com
|
||||
- generic [ref=e42]:
|
||||
- generic [ref=e43]:
|
||||
- text: (
|
||||
- link "ycombinator.com" [ref=e43] [cursor=pointer]:
|
||||
- link "ycombinator.com" [ref=e44] [cursor=pointer]:
|
||||
- /url: from?site=ycombinator.com
|
||||
- generic [ref=e44] [cursor=pointer]: ycombinator.com
|
||||
- text: )
|
||||
- row "57 points by pg on Oct 9, 2006 | hide | past | favorite | 3 comments" [ref=e45]:
|
||||
- row "57 points by pg on Oct 9, 2006 | hide | past | favorite | 16 comments" [ref=e45]:
|
||||
- cell [ref=e46]
|
||||
- cell "57 points by pg on Oct 9, 2006 | hide | past | favorite | 3 comments" [ref=e47]:
|
||||
- cell "57 points by pg on Oct 9, 2006 | hide | past | favorite | 16 comments" [ref=e47]:
|
||||
- generic [ref=e48]:
|
||||
- generic [ref=e49]: 57 points
|
||||
- text: by
|
||||
- link "pg" [ref=e50] [cursor=pointer]:
|
||||
- text: 57 points by
|
||||
- link "pg" [ref=e49] [cursor=pointer]:
|
||||
- /url: user?id=pg
|
||||
- link "on Oct 9, 2006" [ref=e52] [cursor=pointer]:
|
||||
- /url: item?id=1
|
||||
- generic "2006-10-09T18:21:51 1160418111" [ref=e50]:
|
||||
- link "on Oct 9, 2006" [ref=e51] [cursor=pointer]:
|
||||
- /url: item?id=1
|
||||
- text: "|"
|
||||
- link "hide" [ref=e54] [cursor=pointer]:
|
||||
- /url: hide?id=1&goto=item%3Fid%3D1
|
||||
- link "hide" [ref=e52] [cursor=pointer]:
|
||||
- /url: hide?id=1&auth=e52b391ec392eb381ace51126fd80e19d48c1d7b&goto=item%3Fid%3D1
|
||||
- text: "|"
|
||||
- link "past" [ref=e55] [cursor=pointer]:
|
||||
- link "past" [ref=e53] [cursor=pointer]:
|
||||
- /url: https://hn.algolia.com/?query=Y%20Combinator&type=story&dateRange=all&sort=byDate&storyText=false&prefix&page=0
|
||||
- text: "|"
|
||||
- link "favorite" [ref=e56] [cursor=pointer]:
|
||||
- /url: fave?id=1&auth=5328fcfde7333d68e67a8a2334e25acee5599932
|
||||
- link "favorite" [ref=e54] [cursor=pointer]:
|
||||
- /url: fave?id=1&auth=e52b391ec392eb381ace51126fd80e19d48c1d7b
|
||||
- text: "|"
|
||||
- link "3 comments" [ref=e57] [cursor=pointer]:
|
||||
- link "16 comments" [ref=e55] [cursor=pointer]:
|
||||
- /url: item?id=1
|
||||
- row [ref=e58]:
|
||||
- cell [ref=e59]
|
||||
- cell [ref=e60]
|
||||
- table [ref=e63]:
|
||||
- rowgroup [ref=e64]:
|
||||
- row "upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e65]:
|
||||
- cell "upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e66]:
|
||||
- table [ref=e67]:
|
||||
- rowgroup [ref=e68]:
|
||||
- row "upvote sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e69]:
|
||||
- cell [ref=e70]:
|
||||
- row [ref=e56]:
|
||||
- cell [ref=e57]
|
||||
- cell [ref=e58]
|
||||
- table [ref=e59]:
|
||||
- rowgroup [ref=e60]:
|
||||
- row "upvote sama on Oct 9, 2006 | [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e61]:
|
||||
- cell "upvote sama on Oct 9, 2006 | [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e62]:
|
||||
- table [ref=e63]:
|
||||
- rowgroup [ref=e64]:
|
||||
- row "upvote sama on Oct 9, 2006 | [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e65]:
|
||||
- cell [ref=e66]:
|
||||
- img
|
||||
- cell "upvote" [ref=e72]:
|
||||
- link "upvote" [ref=e74] [cursor=pointer]:
|
||||
- /url: vote?id=15&how=up&goto=item%3Fid%3D1
|
||||
- cell "sama on Oct 9, 2006 [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e76]:
|
||||
- generic [ref=e78]:
|
||||
- link "sama" [ref=e79] [cursor=pointer]:
|
||||
- cell "upvote" [ref=e67]:
|
||||
- link "upvote" [ref=e69] [cursor=pointer]:
|
||||
- /url: vote?id=15&how=up&auth=709295bd69cc8638fcf14a7b807bb0efabd69a2d&goto=item%3Fid%3D1#15
|
||||
- generic "upvote" [ref=e70]
|
||||
- cell "sama on Oct 9, 2006 | [–] \"the rising star of venture capital\" -unknown VC eating lunch on SHR" [ref=e71]:
|
||||
- generic [ref=e73]:
|
||||
- link "sama" [ref=e74] [cursor=pointer]:
|
||||
- /url: user?id=sama
|
||||
- link "on Oct 9, 2006" [ref=e81] [cursor=pointer]:
|
||||
- /url: item?id=15
|
||||
- link "[–]" [ref=e84] [cursor=pointer]:
|
||||
- /url: javascript:void(0)
|
||||
- generic [ref=e87]:
|
||||
- generic [ref=e88]: "\"the rising star of venture capital\" -unknown VC eating lunch on SHR"
|
||||
- generic "2006-10-09T19:51:01 1160423461" [ref=e75]:
|
||||
- link "on Oct 9, 2006" [ref=e76] [cursor=pointer]:
|
||||
- /url: item?id=15
|
||||
- generic [ref=e77]:
|
||||
- text: "|"
|
||||
- link [ref=e78] [cursor=pointer]:
|
||||
- /url: "#487171"
|
||||
- text: next
|
||||
- link "[–]" [ref=e79] [cursor=pointer]:
|
||||
- /url: javascript:void(0)
|
||||
- generic [ref=e80]:
|
||||
- generic [ref=e81]: "\"the rising star of venture capital\" -unknown VC eating lunch on SHR"
|
||||
- generic:
|
||||
- paragraph
|
||||
- row "upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road?" [ref=e92]:
|
||||
- cell "upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road?" [ref=e93]:
|
||||
- table [ref=e94]:
|
||||
- rowgroup [ref=e95]:
|
||||
- row "upvote pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road?" [ref=e96]:
|
||||
- cell [ref=e97]:
|
||||
- img [ref=e98]
|
||||
- cell "upvote" [ref=e99]:
|
||||
- link "upvote" [ref=e101] [cursor=pointer]:
|
||||
- /url: vote?id=17&how=up&goto=item%3Fid%3D1
|
||||
- cell "pg on Oct 9, 2006 | [–] Is there anywhere to eat on Sandhill Road?" [ref=e103]:
|
||||
- generic [ref=e105]:
|
||||
- link "pg" [ref=e106] [cursor=pointer]:
|
||||
- row "upvote pg on Oct 9, 2006 | | [–] Is there anywhere to eat on Sandhill Road?" [ref=e82]:
|
||||
- cell "upvote pg on Oct 9, 2006 | | [–] Is there anywhere to eat on Sandhill Road?" [ref=e83]:
|
||||
- table [ref=e84]:
|
||||
- rowgroup [ref=e85]:
|
||||
- row "upvote pg on Oct 9, 2006 | | [–] Is there anywhere to eat on Sandhill Road?" [ref=e86]:
|
||||
- cell [ref=e87]:
|
||||
- img [ref=e88]
|
||||
- cell "upvote" [ref=e89]:
|
||||
- link "upvote" [ref=e91] [cursor=pointer]:
|
||||
- /url: vote?id=17&how=up&auth=3c24b126f8f812f8c3a24a0b9143eac42ca15476&goto=item%3Fid%3D1#17
|
||||
- generic "upvote" [ref=e92]
|
||||
- cell "pg on Oct 9, 2006 | | [–] Is there anywhere to eat on Sandhill Road?" [ref=e93]:
|
||||
- generic [ref=e95]:
|
||||
- link "pg" [ref=e96] [cursor=pointer]:
|
||||
- /url: user?id=pg
|
||||
- link "on Oct 9, 2006" [ref=e108] [cursor=pointer]:
|
||||
- /url: item?id=17
|
||||
- generic [ref=e110]:
|
||||
- generic "2006-10-09T19:52:45 1160423565" [ref=e97]:
|
||||
- link "on Oct 9, 2006" [ref=e98] [cursor=pointer]:
|
||||
- /url: item?id=17
|
||||
- generic [ref=e99]:
|
||||
- text: "|"
|
||||
- link [ref=e111] [cursor=pointer]:
|
||||
- link [ref=e100] [cursor=pointer]:
|
||||
- /url: "#15"
|
||||
- text: parent
|
||||
- link "[–]" [ref=e112] [cursor=pointer]:
|
||||
- text: "|"
|
||||
- link [ref=e101] [cursor=pointer]:
|
||||
- /url: "#487171"
|
||||
- text: next
|
||||
- link "[–]" [ref=e102] [cursor=pointer]:
|
||||
- /url: javascript:void(0)
|
||||
- generic [ref=e115]:
|
||||
- generic [ref=e116]: Is there anywhere to eat on Sandhill Road?
|
||||
- generic [ref=e103]:
|
||||
- generic [ref=e104]: Is there anywhere to eat on Sandhill Road?
|
||||
- generic:
|
||||
- paragraph
|
||||
- row "upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e120]:
|
||||
- cell "upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e121]:
|
||||
- table [ref=e122]:
|
||||
- rowgroup [ref=e123]:
|
||||
- row "upvote dmon on Feb 25, 2007 | | [–] sure" [ref=e124]:
|
||||
- cell [ref=e125]:
|
||||
- img [ref=e126]
|
||||
- cell "upvote" [ref=e127]:
|
||||
- link "upvote" [ref=e129] [cursor=pointer]:
|
||||
- /url: vote?id=1079&how=up&goto=item%3Fid%3D1
|
||||
- cell "dmon on Feb 25, 2007 | | [–] sure" [ref=e131]:
|
||||
- generic [ref=e133]:
|
||||
- link "dmon" [ref=e134] [cursor=pointer]:
|
||||
- row "upvote dmon on Feb 25, 2007 | | | [–] sure" [ref=e105]:
|
||||
- cell "upvote dmon on Feb 25, 2007 | | | [–] sure" [ref=e106]:
|
||||
- table [ref=e107]:
|
||||
- rowgroup [ref=e108]:
|
||||
- row "upvote dmon on Feb 25, 2007 | | | [–] sure" [ref=e109]:
|
||||
- cell [ref=e110]:
|
||||
- img [ref=e111]
|
||||
- cell "upvote" [ref=e112]:
|
||||
- link "upvote" [ref=e114] [cursor=pointer]:
|
||||
- /url: vote?id=1079&how=up&auth=851f0b1e178c46300e9d2218764827726415a50d&goto=item%3Fid%3D1#1079
|
||||
- generic "upvote" [ref=e115]
|
||||
- cell "dmon on Feb 25, 2007 | | | [–] sure" [ref=e116]:
|
||||
- generic [ref=e118]:
|
||||
- link "dmon" [ref=e119] [cursor=pointer]:
|
||||
- /url: user?id=dmon
|
||||
- link "on Feb 25, 2007" [ref=e136] [cursor=pointer]:
|
||||
- /url: item?id=1079
|
||||
- generic [ref=e138]:
|
||||
- generic "2007-02-25T22:18:23 1172441903" [ref=e120]:
|
||||
- link "on Feb 25, 2007" [ref=e121] [cursor=pointer]:
|
||||
- /url: item?id=1079
|
||||
- generic [ref=e122]:
|
||||
- text: "|"
|
||||
- link [ref=e139] [cursor=pointer]:
|
||||
- link [ref=e123] [cursor=pointer]:
|
||||
- /url: "#15"
|
||||
- text: root
|
||||
- text: "|"
|
||||
- link [ref=e140] [cursor=pointer]:
|
||||
- link [ref=e124] [cursor=pointer]:
|
||||
- /url: "#17"
|
||||
- text: parent
|
||||
- link "[–]" [ref=e141] [cursor=pointer]:
|
||||
- text: "|"
|
||||
- link [ref=e125] [cursor=pointer]:
|
||||
- /url: "#487171"
|
||||
- text: next
|
||||
- link "[–]" [ref=e126] [cursor=pointer]:
|
||||
- /url: javascript:void(0)
|
||||
- generic [ref=e144]:
|
||||
- generic [ref=e145]: sure
|
||||
- generic [ref=e127]:
|
||||
- generic [ref=e128]: sure
|
||||
- generic:
|
||||
- paragraph
|
||||
- row "Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4 Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact Search:" [ref=e151]:
|
||||
- cell "Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4 Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact Search:" [ref=e152]:
|
||||
- row "jacquesm on Feb 19, 2009 [dead] | | [–] So, just to see how hard it is to make the longest span between article and comment :) Congratulations on your second birthday YC, and thanks to Paul Graham for writing this forum. I had a really good look at the good a few days ago and I was quite impressed with how elegant the whole thing is put together. Lisp would not be my language of choice for a website like this, and yet, after seeing how concise it was I'm tempted to play around with lisp in a web environment." [ref=e129]:
|
||||
- cell "jacquesm on Feb 19, 2009 [dead] | | [–] So, just to see how hard it is to make the longest span between article and comment :) Congratulations on your second birthday YC, and thanks to Paul Graham for writing this forum. I had a really good look at the good a few days ago and I was quite impressed with how elegant the whole thing is put together. Lisp would not be my language of choice for a website like this, and yet, after seeing how concise it was I'm tempted to play around with lisp in a web environment." [ref=e130]:
|
||||
- table [ref=e131]:
|
||||
- rowgroup [ref=e132]:
|
||||
- row "jacquesm on Feb 19, 2009 [dead] | | [–] So, just to see how hard it is to make the longest span between article and comment :) Congratulations on your second birthday YC, and thanks to Paul Graham for writing this forum. I had a really good look at the good a few days ago and I was quite impressed with how elegant the whole thing is put together. Lisp would not be my language of choice for a website like this, and yet, after seeing how concise it was I'm tempted to play around with lisp in a web environment." [ref=e133]:
|
||||
- cell [ref=e134]:
|
||||
- img
|
||||
- cell [ref=e135]:
|
||||
- img [ref=e137]
|
||||
- cell "jacquesm on Feb 19, 2009 [dead] | | [–] So, just to see how hard it is to make the longest span between article and comment :) Congratulations on your second birthday YC, and thanks to Paul Graham for writing this forum. I had a really good look at the good a few days ago and I was quite impressed with how elegant the whole thing is put together. Lisp would not be my language of choice for a website like this, and yet, after seeing how concise it was I'm tempted to play around with lisp in a web environment." [ref=e138]:
|
||||
- generic [ref=e140]:
|
||||
- link "jacquesm" [ref=e141] [cursor=pointer]:
|
||||
- /url: user?id=jacquesm
|
||||
- generic "2009-02-19T12:21:23 1235046083" [ref=e142]:
|
||||
- link "on Feb 19, 2009" [ref=e143] [cursor=pointer]:
|
||||
- /url: item?id=487171
|
||||
- text: "[dead]"
|
||||
- generic [ref=e144]:
|
||||
- text: "|"
|
||||
- link [ref=e145] [cursor=pointer]:
|
||||
- /url: "#15"
|
||||
- text: prev
|
||||
- text: "|"
|
||||
- link [ref=e146] [cursor=pointer]:
|
||||
- /url: "#234509"
|
||||
- text: next
|
||||
- link "[–]" [ref=e147] [cursor=pointer]:
|
||||
- /url: javascript:void(0)
|
||||
- generic [ref=e148]:
|
||||
- generic [ref=e149]:
|
||||
- text: So, just to see how hard it is to make the longest span between article and comment :)
|
||||
- paragraph [ref=e150]: Congratulations on your second birthday YC, and thanks to Paul Graham for writing this forum. I had a really good look at the good a few days ago and I was quite impressed with how elegant the whole thing is put together.
|
||||
- paragraph [ref=e151]: Lisp would not be my language of choice for a website like this, and yet, after seeing how concise it was I'm tempted to play around with lisp in a web environment.
|
||||
- generic:
|
||||
- paragraph
|
||||
- row "kleevr on July 2, 2008 [dead] | | [11 more]" [ref=e152]:
|
||||
- cell "kleevr on July 2, 2008 [dead] | | [11 more]" [ref=e153]:
|
||||
- table [ref=e154]:
|
||||
- rowgroup [ref=e155]:
|
||||
- row "kleevr on July 2, 2008 [dead] | | [11 more]" [ref=e156]:
|
||||
- cell [ref=e157]:
|
||||
- img
|
||||
- cell "kleevr on July 2, 2008 [dead] | | [11 more]" [ref=e158]:
|
||||
- generic [ref=e160]:
|
||||
- link "kleevr" [ref=e161] [cursor=pointer]:
|
||||
- /url: user?id=kleevr
|
||||
- generic "2008-07-02T20:29:48 1215030588" [ref=e162]:
|
||||
- link "on July 2, 2008" [ref=e163] [cursor=pointer]:
|
||||
- /url: item?id=234509
|
||||
- text: "[dead]"
|
||||
- generic [ref=e164]:
|
||||
- text: "|"
|
||||
- link [ref=e165] [cursor=pointer]:
|
||||
- /url: "#487171"
|
||||
- text: prev
|
||||
- text: "|"
|
||||
- link [ref=e166] [cursor=pointer]:
|
||||
- /url: "#82729"
|
||||
- text: next
|
||||
- link "[11 more]" [ref=e167] [cursor=pointer]:
|
||||
- /url: javascript:void(0)
|
||||
- row "vice on Nov 22, 2007 [dead] | [–] I'm nX 1 too ;)" [ref=e168]:
|
||||
- cell "vice on Nov 22, 2007 [dead] | [–] I'm nX 1 too ;)" [ref=e169]:
|
||||
- table [ref=e170]:
|
||||
- rowgroup [ref=e171]:
|
||||
- row "vice on Nov 22, 2007 [dead] | [–] I'm nX 1 too ;)" [ref=e172]:
|
||||
- cell [ref=e173]:
|
||||
- img
|
||||
- cell [ref=e174]:
|
||||
- img [ref=e176]
|
||||
- cell "vice on Nov 22, 2007 [dead] | [–] I'm nX 1 too ;)" [ref=e177]:
|
||||
- generic [ref=e179]:
|
||||
- link "vice" [ref=e180] [cursor=pointer]:
|
||||
- /url: user?id=vice
|
||||
- generic "2007-11-22T12:50:54 1195735854" [ref=e181]:
|
||||
- link "on Nov 22, 2007" [ref=e182] [cursor=pointer]:
|
||||
- /url: item?id=82729
|
||||
- text: "[dead]"
|
||||
- generic [ref=e183]:
|
||||
- text: "|"
|
||||
- link [ref=e184] [cursor=pointer]:
|
||||
- /url: "#234509"
|
||||
- text: prev
|
||||
- link "[–]" [ref=e185] [cursor=pointer]:
|
||||
- /url: javascript:void(0)
|
||||
- generic [ref=e186]:
|
||||
- generic [ref=e187]:
|
||||
- text: I'm nX 1 too
|
||||
- paragraph [ref=e188]: ;)
|
||||
- generic:
|
||||
- paragraph
|
||||
- row "Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact Search:" [ref=e189]:
|
||||
- cell "Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact Search:" [ref=e190]:
|
||||
- img
|
||||
- table [ref=e154]:
|
||||
- rowgroup [ref=e155]:
|
||||
- row [ref=e156]:
|
||||
- cell [ref=e157]
|
||||
- link "Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4" [ref=e160] [cursor=pointer]:
|
||||
- /url: https://www.ycombinator.com/apply/
|
||||
- generic [ref=e162]:
|
||||
- generic [ref=e163]:
|
||||
- link "Guidelines" [ref=e164] [cursor=pointer]:
|
||||
- table [ref=e191]:
|
||||
- rowgroup [ref=e192]:
|
||||
- row [ref=e193]:
|
||||
- cell [ref=e194]
|
||||
- generic [ref=e195]:
|
||||
- generic [ref=e196]:
|
||||
- link "Guidelines" [ref=e197] [cursor=pointer]:
|
||||
- /url: newsguidelines.html
|
||||
- text: "|"
|
||||
- link "FAQ" [ref=e165] [cursor=pointer]:
|
||||
- link "FAQ" [ref=e198] [cursor=pointer]:
|
||||
- /url: newsfaq.html
|
||||
- text: "|"
|
||||
- link "Lists" [ref=e166] [cursor=pointer]:
|
||||
- link "Lists" [ref=e199] [cursor=pointer]:
|
||||
- /url: lists
|
||||
- text: "|"
|
||||
- link "API" [ref=e167] [cursor=pointer]:
|
||||
- link "API" [ref=e200] [cursor=pointer]:
|
||||
- /url: https://github.com/HackerNews/API
|
||||
- text: "|"
|
||||
- link "Security" [ref=e168] [cursor=pointer]:
|
||||
- link "Security" [ref=e201] [cursor=pointer]:
|
||||
- /url: security.html
|
||||
- text: "|"
|
||||
- link "Legal" [ref=e169] [cursor=pointer]:
|
||||
- link "Legal" [ref=e202] [cursor=pointer]:
|
||||
- /url: https://www.ycombinator.com/legal/
|
||||
- text: "|"
|
||||
- link "Apply to YC" [ref=e170] [cursor=pointer]:
|
||||
- link "Apply to YC" [ref=e203] [cursor=pointer]:
|
||||
- /url: https://www.ycombinator.com/apply/
|
||||
- text: "|"
|
||||
- link "Contact" [ref=e171] [cursor=pointer]:
|
||||
- link "Contact" [ref=e204] [cursor=pointer]:
|
||||
- /url: mailto:hn@ycombinator.com
|
||||
- generic [ref=e174]:
|
||||
- generic [ref=e205]:
|
||||
- text: "Search:"
|
||||
- textbox [ref=e175]
|
||||
- textbox [ref=e206]
|
||||
@@ -1,3 +1,4 @@
|
||||
Return value:
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e2]:
|
||||
- banner [ref=e3]:
|
||||
@@ -5,549 +6,338 @@
|
||||
- link "shadcn/ui" [ref=e6] [cursor=pointer]:
|
||||
- /url: /
|
||||
- img
|
||||
- generic [ref=e11] [cursor=pointer]: shadcn/ui
|
||||
- navigation [ref=e12]:
|
||||
- link "Docs" [ref=e13] [cursor=pointer]:
|
||||
- generic [ref=e7]: shadcn/ui
|
||||
- navigation [ref=e8]:
|
||||
- link "Docs" [ref=e9] [cursor=pointer]:
|
||||
- /url: /docs/installation
|
||||
- link "Components" [ref=e14] [cursor=pointer]:
|
||||
- link "Components" [ref=e10] [cursor=pointer]:
|
||||
- /url: /docs/components
|
||||
- link "Blocks" [ref=e15] [cursor=pointer]:
|
||||
- link "Blocks" [ref=e11] [cursor=pointer]:
|
||||
- /url: /blocks
|
||||
- link "Charts" [ref=e16] [cursor=pointer]:
|
||||
- link "Charts" [ref=e12] [cursor=pointer]:
|
||||
- /url: /charts/area
|
||||
- link "Themes" [ref=e17] [cursor=pointer]:
|
||||
- link "Directory" [ref=e13] [cursor=pointer]:
|
||||
- /url: /docs/directory
|
||||
- link "Themes" [ref=e14] [cursor=pointer]:
|
||||
- /url: /themes
|
||||
- link "Colors" [ref=e18] [cursor=pointer]:
|
||||
- link "Colors" [ref=e15] [cursor=pointer]:
|
||||
- /url: /colors
|
||||
- generic [ref=e19]:
|
||||
- button "Search documentation... ⌘ K" [ref=e21]:
|
||||
- generic [ref=e22]: Search documentation...
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e16]:
|
||||
- button "Search documentation... ⌘ K" [ref=e18]:
|
||||
- generic [ref=e19]: Search documentation...
|
||||
- generic [ref=e21]:
|
||||
- generic: ⌘
|
||||
- generic: K
|
||||
- link "91.7k" [ref=e26] [cursor=pointer]:
|
||||
- link "98.7k" [ref=e22] [cursor=pointer]:
|
||||
- /url: https://github.com/shadcn-ui/ui
|
||||
- img
|
||||
- generic [ref=e29] [cursor=pointer]: 91.7k
|
||||
- button "Toggle layout" [ref=e30]:
|
||||
- generic [ref=e31]: Toggle layout
|
||||
- generic [ref=e23]: 98.7k
|
||||
- button "Toggle layout" [ref=e24]:
|
||||
- generic [ref=e25]: Toggle layout
|
||||
- img
|
||||
- button "Toggle theme" [ref=e36]:
|
||||
- button "Toggle theme" [ref=e26]:
|
||||
- img
|
||||
- generic [ref=e44]: Toggle theme
|
||||
- main [ref=e45]:
|
||||
- generic [ref=e46]:
|
||||
- generic [ref=e49]:
|
||||
- link "New Calendar Component" [ref=e50] [cursor=pointer]:
|
||||
- /url: /docs/components/calendar
|
||||
- text: New Calendar Component
|
||||
- generic [ref=e27]: Toggle theme
|
||||
- main [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- generic [ref=e32]:
|
||||
- 'link "New New Components: Field, Input Group, Item and more" [ref=e33] [cursor=pointer]':
|
||||
- /url: /docs/changelog
|
||||
- generic "New" [ref=e34]
|
||||
- text: "New Components: Field, Input Group, Item and more"
|
||||
- img
|
||||
- heading "The Foundation for your Design System" [level=1] [ref=e54]
|
||||
- paragraph [ref=e55]: A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code.
|
||||
- generic [ref=e56]:
|
||||
- link "Get Started" [ref=e57] [cursor=pointer]:
|
||||
- heading "The Foundation for your Design System" [level=1] [ref=e35]
|
||||
- paragraph [ref=e36]: A set of beautifully designed components that you can customize, extend, and build on. Start here then make it your own. Open Source. Open Code.
|
||||
- generic [ref=e37]:
|
||||
- link "Get Started" [ref=e38] [cursor=pointer]:
|
||||
- /url: /docs/installation
|
||||
- link "View Components" [ref=e58] [cursor=pointer]:
|
||||
- link "View Components" [ref=e39] [cursor=pointer]:
|
||||
- /url: /docs/components
|
||||
- generic [ref=e60]:
|
||||
- generic [ref=e65]:
|
||||
- link "Examples" [ref=e66] [cursor=pointer]:
|
||||
- generic [ref=e41]:
|
||||
- generic [ref=e46]:
|
||||
- link "Examples" [ref=e47] [cursor=pointer]:
|
||||
- /url: /
|
||||
- link "Dashboard" [ref=e67] [cursor=pointer]:
|
||||
- link "Dashboard" [ref=e48] [cursor=pointer]:
|
||||
- /url: /examples/dashboard
|
||||
- link "Tasks" [ref=e68] [cursor=pointer]:
|
||||
- link "Tasks" [ref=e49] [cursor=pointer]:
|
||||
- /url: /examples/tasks
|
||||
- link "Playground" [ref=e69] [cursor=pointer]:
|
||||
- link "Playground" [ref=e50] [cursor=pointer]:
|
||||
- /url: /examples/playground
|
||||
- link "Authentication" [ref=e70] [cursor=pointer]:
|
||||
- link "Authentication" [ref=e51] [cursor=pointer]:
|
||||
- /url: /examples/authentication
|
||||
- generic [ref=e71]:
|
||||
- generic [ref=e72]: Theme
|
||||
- combobox "Theme" [ref=e73]:
|
||||
- generic [ref=e74]: "Theme:"
|
||||
- generic: Default
|
||||
- generic [ref=e52]:
|
||||
- generic [ref=e53]: Theme
|
||||
- combobox "Theme" [ref=e54]:
|
||||
- generic [ref=e55]: "Theme:"
|
||||
- generic: Neutral
|
||||
- img
|
||||
- generic [ref=e81]:
|
||||
- generic [ref=e82]:
|
||||
- generic [ref=e83]:
|
||||
- generic [ref=e84]:
|
||||
- generic [ref=e85]:
|
||||
- generic [ref=e86]: Total Revenue
|
||||
- generic [ref=e87]: $15,231.89
|
||||
- generic [ref=e88]: +20.1% from last month
|
||||
- img [ref=e93]
|
||||
- generic [ref=e99]:
|
||||
- generic [ref=e100]:
|
||||
- generic [ref=e101]: Subscriptions
|
||||
- generic [ref=e102]: +2,350
|
||||
- generic [ref=e103]: +180.1% from last month
|
||||
- button "View More" [ref=e105]
|
||||
- img [ref=e110]
|
||||
- generic [ref=e123]:
|
||||
- generic [ref=e124]:
|
||||
- generic [ref=e125]:
|
||||
- generic [ref=e126]:
|
||||
- generic [ref=e127]: Upgrade your subscription
|
||||
- generic [ref=e128]: You are currently on the free plan. Upgrade to the pro plan to get access to all features.
|
||||
- generic [ref=e130]:
|
||||
- generic [ref=e131]:
|
||||
- generic [ref=e132]:
|
||||
- generic [ref=e133]: Name
|
||||
- textbox "Name" [ref=e134]
|
||||
- generic [ref=e135]:
|
||||
- generic [ref=e136]: Email
|
||||
- textbox "Email" [ref=e137]
|
||||
- generic [ref=e138]:
|
||||
- generic [ref=e139]: Card Number
|
||||
- generic [ref=e140]:
|
||||
- textbox "Card Number" [ref=e141]
|
||||
- textbox "MM/YY" [ref=e142]
|
||||
- textbox "CVC" [ref=e143]
|
||||
- group "Plan" [ref=e144]:
|
||||
- generic [ref=e145]: Plan
|
||||
- paragraph [ref=e146]: Select the plan that best fits your needs.
|
||||
- radiogroup [ref=e147]:
|
||||
- generic [ref=e148]:
|
||||
- radio "Starter Plan Perfect for small businesses." [checked] [ref=e149]:
|
||||
- img [ref=e151]
|
||||
- generic [ref=e153]:
|
||||
- generic [ref=e154]: Starter Plan
|
||||
- generic [ref=e155]: Perfect for small businesses.
|
||||
- generic [ref=e156]:
|
||||
- radio "Pro Plan More features and storage." [ref=e157]
|
||||
- generic [ref=e158]:
|
||||
- generic [ref=e159]: Pro Plan
|
||||
- generic [ref=e160]: More features and storage.
|
||||
- generic [ref=e161]:
|
||||
- generic [ref=e162]: Notes
|
||||
- textbox "Notes" [ref=e163]
|
||||
- generic [ref=e164]:
|
||||
- generic [ref=e165]:
|
||||
- checkbox "I agree to the terms and conditions" [ref=e166]
|
||||
- generic [ref=e167]: I agree to the terms and conditions
|
||||
- generic [ref=e168]:
|
||||
- checkbox "Allow us to send you emails" [checked] [ref=e169]:
|
||||
- generic:
|
||||
- img
|
||||
- generic [ref=e173]: Allow us to send you emails
|
||||
- generic [ref=e174]:
|
||||
- button "Cancel" [ref=e175]
|
||||
- button "Upgrade Plan" [ref=e176]
|
||||
- generic [ref=e177]:
|
||||
- generic [ref=e178]:
|
||||
- generic [ref=e179]: Team Members
|
||||
- generic [ref=e180]: Invite your team members to collaborate.
|
||||
- generic [ref=e181]:
|
||||
- generic [ref=e182]:
|
||||
- generic [ref=e183]:
|
||||
- img "Image" [ref=e185]
|
||||
- generic [ref=e186]:
|
||||
- paragraph [ref=e187]: Sofia Davis
|
||||
- paragraph [ref=e188]: m@example.com
|
||||
- button "Owner" [ref=e189]:
|
||||
- text: Owner
|
||||
- img
|
||||
- generic [ref=e192]:
|
||||
- generic [ref=e193]:
|
||||
- img "Image" [ref=e195]
|
||||
- generic [ref=e196]:
|
||||
- paragraph [ref=e197]: Jackson Lee
|
||||
- paragraph [ref=e198]: p@example.com
|
||||
- button "Developer" [ref=e199]:
|
||||
- text: Developer
|
||||
- img
|
||||
- generic [ref=e202]:
|
||||
- generic [ref=e203]:
|
||||
- img "Image" [ref=e205]
|
||||
- generic [ref=e206]:
|
||||
- paragraph [ref=e207]: Isabella Nguyen
|
||||
- paragraph [ref=e208]: i@example.com
|
||||
- button "Billing" [ref=e209]:
|
||||
- text: Billing
|
||||
- img
|
||||
- generic [ref=e212]:
|
||||
- generic [ref=e213]:
|
||||
- generic [ref=e214]: Cookie Settings
|
||||
- generic [ref=e215]: Manage your cookie settings here.
|
||||
- generic [ref=e216]:
|
||||
- generic [ref=e217]:
|
||||
- generic [ref=e218]:
|
||||
- generic [ref=e219]: Strictly Necessary
|
||||
- generic [ref=e220]: These cookies are essential in order to use the website and use its features.
|
||||
- switch "Necessary" [checked] [ref=e221]
|
||||
- generic [ref=e223]:
|
||||
- generic [ref=e224]:
|
||||
- generic [ref=e225]: Functional Cookies
|
||||
- generic [ref=e226]: These cookies allow the website to provide personalized functionality.
|
||||
- switch "Functional" [ref=e227]
|
||||
- button "Save preferences" [ref=e230]
|
||||
- generic [ref=e231]:
|
||||
- generic [ref=e232]:
|
||||
- generic [ref=e233]:
|
||||
- generic [ref=e234]: Create an account
|
||||
- generic [ref=e235]: Enter your email below to create your account
|
||||
- generic [ref=e236]:
|
||||
- generic [ref=e237]:
|
||||
- button "GitHub" [ref=e238]:
|
||||
- img
|
||||
- text: GitHub
|
||||
- button "Google" [ref=e241]:
|
||||
- img
|
||||
- text: Google
|
||||
- generic [ref=e248]: Or continue with
|
||||
- generic [ref=e249]:
|
||||
- generic [ref=e250]: Email
|
||||
- textbox "Email" [ref=e251]
|
||||
- generic [ref=e252]:
|
||||
- generic [ref=e253]: Password
|
||||
- textbox "Password" [ref=e254]
|
||||
- button "Create account" [ref=e256]
|
||||
- generic [ref=e257]:
|
||||
- generic [ref=e258]:
|
||||
- generic [ref=e259]:
|
||||
- img "Image" [ref=e261]
|
||||
- generic [ref=e262]:
|
||||
- paragraph [ref=e263]: Sofia Davis
|
||||
- paragraph [ref=e264]: m@example.com
|
||||
- button "New message" [ref=e265]:
|
||||
- button "Copy Code" [ref=e56]:
|
||||
- img
|
||||
- generic [ref=e57]: Copy Code
|
||||
- generic [ref=e61]:
|
||||
- generic [ref=e65]:
|
||||
- group "Payment Method" [ref=e66]:
|
||||
- generic [ref=e67]: Payment Method
|
||||
- paragraph [ref=e68]: All transactions are secure and encrypted
|
||||
- generic [ref=e69]:
|
||||
- group [ref=e70]:
|
||||
- generic [ref=e71]: Name on Card
|
||||
- textbox "Name on Card" [ref=e72]:
|
||||
- /placeholder: John Doe
|
||||
- generic [ref=e73]:
|
||||
- group [ref=e74]:
|
||||
- generic [ref=e75]: Card Number
|
||||
- textbox "Card Number" [ref=e76]:
|
||||
- /placeholder: 1234 5678 9012 3456
|
||||
- paragraph [ref=e77]: Enter your 16-digit number.
|
||||
- group [ref=e78]:
|
||||
- generic [ref=e79]: CVV
|
||||
- textbox "CVV" [ref=e80]:
|
||||
- /placeholder: "123"
|
||||
- generic [ref=e81]:
|
||||
- group [ref=e82]:
|
||||
- generic [ref=e83]: Month
|
||||
- combobox "Month" [ref=e84]:
|
||||
- generic: MM
|
||||
- img
|
||||
- generic [ref=e269]: New message
|
||||
- generic [ref=e271]:
|
||||
- generic [ref=e272]: Hi, how can I help you today?
|
||||
- generic [ref=e273]: Hey, I'm having trouble with my account.
|
||||
- generic [ref=e274]: What seems to be the problem?
|
||||
- generic [ref=e275]: I can't log in.
|
||||
- generic [ref=e277]:
|
||||
- textbox "Type your message..." [ref=e278]
|
||||
- button "Send" [disabled]:
|
||||
- combobox [ref=e85]
|
||||
- group [ref=e86]:
|
||||
- generic [ref=e87]: Year
|
||||
- combobox "Year" [ref=e88]:
|
||||
- generic: YYYY
|
||||
- img
|
||||
- generic: Send
|
||||
- generic [ref=e285]:
|
||||
- generic [ref=e286]:
|
||||
- generic [ref=e287]: Report an issue
|
||||
- generic [ref=e288]: What area are you having problems with?
|
||||
- generic [ref=e289]:
|
||||
- generic [ref=e290]:
|
||||
- generic [ref=e291]:
|
||||
- generic [ref=e292]: Area
|
||||
- combobox "Area" [ref=e293]:
|
||||
- generic: Billing
|
||||
- img
|
||||
- generic [ref=e297]:
|
||||
- generic [ref=e298]: Security Level
|
||||
- combobox "Security Level" [ref=e299]:
|
||||
- generic: Severity 2
|
||||
- img
|
||||
- generic [ref=e303]:
|
||||
- generic [ref=e304]: Subject
|
||||
- textbox "Subject" [ref=e305]
|
||||
- generic [ref=e306]:
|
||||
- generic [ref=e307]: Description
|
||||
- textbox "Description" [ref=e308]
|
||||
- generic [ref=e309]:
|
||||
- button "Cancel" [ref=e310]
|
||||
- button "Submit" [ref=e311]
|
||||
- generic [ref=e312]:
|
||||
- generic [ref=e313]:
|
||||
- generic [ref=e317]:
|
||||
- navigation [ref=e318]:
|
||||
- button "Go to the Previous Month" [ref=e319]:
|
||||
- img
|
||||
- button "Go to the Next Month" [ref=e322]:
|
||||
- img
|
||||
- generic [ref=e325]:
|
||||
- status [ref=e327]: June 2025
|
||||
- grid "June 2025" [ref=e328]:
|
||||
- rowgroup [ref=e329]:
|
||||
- row [ref=e330]:
|
||||
- columnheader [ref=e331]: Su
|
||||
- columnheader [ref=e332]: Mo
|
||||
- columnheader [ref=e333]: Tu
|
||||
- columnheader [ref=e334]: We
|
||||
- columnheader [ref=e335]: Th
|
||||
- columnheader [ref=e336]: Fr
|
||||
- columnheader [ref=e337]: Sa
|
||||
- rowgroup [ref=e338]:
|
||||
- row "Sunday, June 1st, 2025 Monday, June 2nd, 2025 Tuesday, June 3rd, 2025 Wednesday, June 4th, 2025 Thursday, June 5th, 2025, selected Friday, June 6th, 2025, selected Saturday, June 7th, 2025, selected" [ref=e339]:
|
||||
- gridcell "Sunday, June 1st, 2025" [ref=e340]:
|
||||
- button "Sunday, June 1st, 2025" [ref=e341]: "1"
|
||||
- gridcell "Monday, June 2nd, 2025" [ref=e342]:
|
||||
- button "Monday, June 2nd, 2025" [ref=e343]: "2"
|
||||
- gridcell "Tuesday, June 3rd, 2025" [ref=e344]:
|
||||
- button "Tuesday, June 3rd, 2025" [ref=e345]: "3"
|
||||
- gridcell "Wednesday, June 4th, 2025" [ref=e346]:
|
||||
- button "Wednesday, June 4th, 2025" [ref=e347]: "4"
|
||||
- gridcell "Thursday, June 5th, 2025, selected" [selected] [ref=e348]:
|
||||
- button "Thursday, June 5th, 2025, selected" [ref=e349]: "5"
|
||||
- gridcell "Friday, June 6th, 2025, selected" [selected] [ref=e350]:
|
||||
- button "Friday, June 6th, 2025, selected" [ref=e351]: "6"
|
||||
- gridcell "Saturday, June 7th, 2025, selected" [selected] [ref=e352]:
|
||||
- button "Saturday, June 7th, 2025, selected" [ref=e353]: "7"
|
||||
- row "Sunday, June 8th, 2025, selected Monday, June 9th, 2025, selected Tuesday, June 10th, 2025, selected Wednesday, June 11th, 2025, selected Thursday, June 12th, 2025, selected Friday, June 13th, 2025, selected Saturday, June 14th, 2025" [ref=e354]:
|
||||
- gridcell "Sunday, June 8th, 2025, selected" [selected] [ref=e355]:
|
||||
- button "Sunday, June 8th, 2025, selected" [ref=e356]: "8"
|
||||
- gridcell "Monday, June 9th, 2025, selected" [selected] [ref=e357]:
|
||||
- button "Monday, June 9th, 2025, selected" [ref=e358]: "9"
|
||||
- gridcell "Tuesday, June 10th, 2025, selected" [selected] [ref=e359]:
|
||||
- button "Tuesday, June 10th, 2025, selected" [ref=e360]: "10"
|
||||
- gridcell "Wednesday, June 11th, 2025, selected" [selected] [ref=e361]:
|
||||
- button "Wednesday, June 11th, 2025, selected" [ref=e362]: "11"
|
||||
- gridcell "Thursday, June 12th, 2025, selected" [selected] [ref=e363]:
|
||||
- button "Thursday, June 12th, 2025, selected" [ref=e364]: "12"
|
||||
- gridcell "Friday, June 13th, 2025, selected" [selected] [ref=e365]:
|
||||
- button "Friday, June 13th, 2025, selected" [ref=e366]: "13"
|
||||
- gridcell "Saturday, June 14th, 2025" [ref=e367]:
|
||||
- button "Saturday, June 14th, 2025" [ref=e368]: "14"
|
||||
- row "Sunday, June 15th, 2025 Monday, June 16th, 2025 Tuesday, June 17th, 2025 Wednesday, June 18th, 2025 Thursday, June 19th, 2025 Friday, June 20th, 2025 Saturday, June 21st, 2025" [ref=e369]:
|
||||
- gridcell "Sunday, June 15th, 2025" [ref=e370]:
|
||||
- button "Sunday, June 15th, 2025" [ref=e371]: "15"
|
||||
- gridcell "Monday, June 16th, 2025" [ref=e372]:
|
||||
- button "Monday, June 16th, 2025" [ref=e373]: "16"
|
||||
- gridcell "Tuesday, June 17th, 2025" [ref=e374]:
|
||||
- button "Tuesday, June 17th, 2025" [ref=e375]: "17"
|
||||
- gridcell "Wednesday, June 18th, 2025" [ref=e376]:
|
||||
- button "Wednesday, June 18th, 2025" [ref=e377]: "18"
|
||||
- gridcell "Thursday, June 19th, 2025" [ref=e378]:
|
||||
- button "Thursday, June 19th, 2025" [ref=e379]: "19"
|
||||
- gridcell "Friday, June 20th, 2025" [ref=e380]:
|
||||
- button "Friday, June 20th, 2025" [ref=e381]: "20"
|
||||
- gridcell "Saturday, June 21st, 2025" [ref=e382]:
|
||||
- button "Saturday, June 21st, 2025" [ref=e383]: "21"
|
||||
- row "Sunday, June 22nd, 2025 Monday, June 23rd, 2025 Tuesday, June 24th, 2025 Wednesday, June 25th, 2025 Thursday, June 26th, 2025 Friday, June 27th, 2025 Saturday, June 28th, 2025" [ref=e384]:
|
||||
- gridcell "Sunday, June 22nd, 2025" [ref=e385]:
|
||||
- button "Sunday, June 22nd, 2025" [ref=e386]: "22"
|
||||
- gridcell "Monday, June 23rd, 2025" [ref=e387]:
|
||||
- button "Monday, June 23rd, 2025" [ref=e388]: "23"
|
||||
- gridcell "Tuesday, June 24th, 2025" [ref=e389]:
|
||||
- button "Tuesday, June 24th, 2025" [ref=e390]: "24"
|
||||
- gridcell "Wednesday, June 25th, 2025" [ref=e391]:
|
||||
- button "Wednesday, June 25th, 2025" [ref=e392]: "25"
|
||||
- gridcell "Thursday, June 26th, 2025" [ref=e393]:
|
||||
- button "Thursday, June 26th, 2025" [ref=e394]: "26"
|
||||
- gridcell "Friday, June 27th, 2025" [ref=e395]:
|
||||
- button "Friday, June 27th, 2025" [ref=e396]: "27"
|
||||
- gridcell "Saturday, June 28th, 2025" [ref=e397]:
|
||||
- button "Saturday, June 28th, 2025" [ref=e398]: "28"
|
||||
- row "Sunday, June 29th, 2025 Monday, June 30th, 2025 Tuesday, July 1st, 2025 Wednesday, July 2nd, 2025 Thursday, July 3rd, 2025 Friday, July 4th, 2025 Saturday, July 5th, 2025" [ref=e399]:
|
||||
- gridcell "Sunday, June 29th, 2025" [ref=e400]:
|
||||
- button "Sunday, June 29th, 2025" [ref=e401]: "29"
|
||||
- gridcell "Monday, June 30th, 2025" [ref=e402]:
|
||||
- button "Monday, June 30th, 2025" [ref=e403]: "30"
|
||||
- gridcell "Tuesday, July 1st, 2025" [ref=e404]:
|
||||
- button "Tuesday, July 1st, 2025" [ref=e405]: "1"
|
||||
- gridcell "Wednesday, July 2nd, 2025" [ref=e406]:
|
||||
- button "Wednesday, July 2nd, 2025" [ref=e407]: "2"
|
||||
- gridcell "Thursday, July 3rd, 2025" [ref=e408]:
|
||||
- button "Thursday, July 3rd, 2025" [ref=e409]: "3"
|
||||
- gridcell "Friday, July 4th, 2025" [ref=e410]:
|
||||
- button "Friday, July 4th, 2025" [ref=e411]: "4"
|
||||
- gridcell "Saturday, July 5th, 2025" [ref=e412]:
|
||||
- button "Saturday, July 5th, 2025" [ref=e413]: "5"
|
||||
- generic [ref=e415]:
|
||||
- generic [ref=e416]:
|
||||
- generic [ref=e417]: Move Goal
|
||||
- generic [ref=e418]: Set your daily activity goal.
|
||||
- generic [ref=e419]:
|
||||
- generic [ref=e420]:
|
||||
- button "Decrease" [ref=e421]:
|
||||
- img
|
||||
- generic [ref=e424]: Decrease
|
||||
- generic [ref=e425]:
|
||||
- generic [ref=e426]: "350"
|
||||
- generic [ref=e427]: Calories/day
|
||||
- button "Increase" [ref=e428]:
|
||||
- img
|
||||
- generic [ref=e432]: Increase
|
||||
- img [ref=e437]
|
||||
- button "Set Goal" [ref=e472]
|
||||
- generic [ref=e474]:
|
||||
- generic [ref=e475]:
|
||||
- generic [ref=e476]: Exercise Minutes
|
||||
- generic [ref=e477]: Your exercise minutes are ahead of where you normally are.
|
||||
- generic [ref=e481]:
|
||||
- application [ref=e482]:
|
||||
- generic [ref=e494]:
|
||||
- generic [ref=e497]: Mon
|
||||
- generic [ref=e500]: Tue
|
||||
- generic [ref=e503]: Wed
|
||||
- generic [ref=e506]: Thu
|
||||
- generic [ref=e509]: Fri
|
||||
- generic [ref=e512]: Sat
|
||||
- generic [ref=e515]: Sun
|
||||
- combobox [ref=e89]
|
||||
- group "Billing Address" [ref=e91]:
|
||||
- generic [ref=e92]: Billing Address
|
||||
- paragraph [ref=e93]: The billing address associated with your payment method
|
||||
- group [ref=e95]:
|
||||
- checkbox "Same as shipping address" [checked] [ref=e96]:
|
||||
- generic:
|
||||
- img
|
||||
- checkbox [checked]
|
||||
- generic [ref=e97]: Same as shipping address
|
||||
- group [ref=e99]:
|
||||
- group [ref=e101]:
|
||||
- generic [ref=e102]: Comments
|
||||
- textbox "Comments" [ref=e103]:
|
||||
- /placeholder: Add any additional comments
|
||||
- group [ref=e104]:
|
||||
- button "Submit" [ref=e105]
|
||||
- button "Cancel" [ref=e106]
|
||||
- generic [ref=e107]:
|
||||
- generic [ref=e108]:
|
||||
- generic [ref=e109]:
|
||||
- generic [ref=e111]:
|
||||
- img "@shadcn" [ref=e113]
|
||||
- img "@maxleiter" [ref=e115]
|
||||
- img "@evilrabbit" [ref=e117]
|
||||
- generic [ref=e118]: No Team Members
|
||||
- generic [ref=e119]: Invite your team to collaborate on this project.
|
||||
- button "Invite Members" [ref=e121]:
|
||||
- img
|
||||
- text: Invite Members
|
||||
- generic [ref=e122]:
|
||||
- generic [ref=e123]:
|
||||
- status "Loading"
|
||||
- text: Syncing
|
||||
- generic [ref=e124]:
|
||||
- status "Loading"
|
||||
- text: Updating
|
||||
- generic [ref=e125]:
|
||||
- status "Loading"
|
||||
- text: Loading
|
||||
- group [ref=e126]:
|
||||
- group [ref=e127]:
|
||||
- button "Add" [ref=e128]:
|
||||
- img
|
||||
- group [ref=e129]:
|
||||
- group [ref=e130]:
|
||||
- textbox "Send a message..." [ref=e131]
|
||||
- group [ref=e132]:
|
||||
- button "Voice Mode" [ref=e133]:
|
||||
- img
|
||||
- group [ref=e135]:
|
||||
- generic [ref=e136]: Price Range
|
||||
- paragraph [ref=e137]: Set your budget range ($200 - 800).
|
||||
- generic "Price Range" [ref=e138]:
|
||||
- slider "Minimum" [ref=e142]
|
||||
- slider "Maximum" [ref=e144]
|
||||
- generic [ref=e145]:
|
||||
- group [ref=e146]:
|
||||
- textbox "Search..." [ref=e147]
|
||||
- group [ref=e148]:
|
||||
- img [ref=e149]
|
||||
- group [ref=e152]: 12 results
|
||||
- group [ref=e153]:
|
||||
- textbox "example.com" [ref=e154]
|
||||
- group [ref=e155]:
|
||||
- generic [ref=e156]: https://
|
||||
- group [ref=e157]:
|
||||
- button "Info" [ref=e158]:
|
||||
- img
|
||||
- group [ref=e159]:
|
||||
- textbox "Ask, Search or Chat..." [ref=e160]
|
||||
- group [ref=e161]:
|
||||
- button "Add" [ref=e162]:
|
||||
- img
|
||||
- button "Auto" [ref=e163]
|
||||
- generic [ref=e164]: 52% used
|
||||
- button "Send" [ref=e165]:
|
||||
- img
|
||||
- generic [ref=e166]: Send
|
||||
- group [ref=e167]:
|
||||
- textbox "@shadcn" [ref=e168]
|
||||
- group [ref=e169]:
|
||||
- img [ref=e171]
|
||||
- generic [ref=e173]:
|
||||
- generic [ref=e174]:
|
||||
- generic [ref=e175]: Input Secure
|
||||
- group [ref=e176]:
|
||||
- textbox "Input Secure" [ref=e177]
|
||||
- group [ref=e178]:
|
||||
- button "Info" [ref=e179]:
|
||||
- img
|
||||
- group [ref=e180]: https://
|
||||
- group [ref=e181]:
|
||||
- button "Favorite" [ref=e182]:
|
||||
- img
|
||||
- generic [ref=e183]:
|
||||
- generic [ref=e184]:
|
||||
- generic [ref=e185]:
|
||||
- generic [ref=e186]: Two-factor authentication
|
||||
- paragraph [ref=e187]: Verify via email or phone number.
|
||||
- button "Enable" [ref=e189]
|
||||
- link "Your profile has been verified." [ref=e190] [cursor=pointer]:
|
||||
- /url: "#"
|
||||
- generic [ref=e191]:
|
||||
- img
|
||||
- generic [ref=e193]: Your profile has been verified.
|
||||
- img [ref=e195]
|
||||
- generic [ref=e198]: Appearance Settings
|
||||
- group [ref=e199]:
|
||||
- generic [ref=e200]:
|
||||
- group "Compute Environment" [ref=e201]:
|
||||
- generic [ref=e202]: Compute Environment
|
||||
- paragraph [ref=e203]: Select the compute environment for your cluster.
|
||||
- radiogroup [ref=e204]:
|
||||
- group [ref=e206]:
|
||||
- generic [ref=e207]:
|
||||
- generic [ref=e208]: Kubernetes
|
||||
- paragraph [ref=e209]: Run GPU workloads on a K8s configured cluster. This is the default.
|
||||
- radio "Kubernetes" [checked] [ref=e210]:
|
||||
- img [ref=e211]
|
||||
- group [ref=e214]:
|
||||
- generic [ref=e215]:
|
||||
- generic [ref=e216]: Virtual Machine
|
||||
- paragraph [ref=e217]: Access a VM configured cluster to run workloads. (Coming soon)
|
||||
- radio "Virtual Machine" [ref=e218]
|
||||
- group [ref=e220]:
|
||||
- generic [ref=e221]:
|
||||
- generic [ref=e222]: Number of GPUs
|
||||
- paragraph [ref=e223]: You can add more later.
|
||||
- group [ref=e224]:
|
||||
- textbox "Number of GPUs" [ref=e225]: "8"
|
||||
- button "Decrement" [ref=e226]:
|
||||
- img
|
||||
- button "Increment" [ref=e227]:
|
||||
- img
|
||||
- group [ref=e229]:
|
||||
- generic [ref=e230]:
|
||||
- generic [ref=e231]: Wallpaper Tinting
|
||||
- paragraph [ref=e232]: Allow the wallpaper to be tinted.
|
||||
- switch "Wallpaper Tinting" [checked] [ref=e233]
|
||||
- generic [ref=e234]:
|
||||
- group [ref=e236]:
|
||||
- generic [ref=e237]: Prompt
|
||||
- group [ref=e238]:
|
||||
- textbox "Prompt" [ref=e239]:
|
||||
- /placeholder: Ask, search, or make anything...
|
||||
- group [ref=e240]:
|
||||
- button "Add context" [ref=e241]:
|
||||
- img
|
||||
- text: Add context
|
||||
- group [ref=e243]:
|
||||
- button "Attach file" [ref=e244]:
|
||||
- img
|
||||
- button "Auto" [ref=e245]
|
||||
- button "All Sources" [ref=e246]:
|
||||
- img
|
||||
- text: All Sources
|
||||
- button "Send" [ref=e247]:
|
||||
- img
|
||||
- group [ref=e248]:
|
||||
- group [ref=e249]:
|
||||
- button "Go Back" [ref=e250]:
|
||||
- img
|
||||
- group [ref=e251]:
|
||||
- button "Archive" [ref=e252]
|
||||
- button "Report" [ref=e253]
|
||||
- group [ref=e254]:
|
||||
- button "Snooze" [ref=e255]
|
||||
- button "More Options" [ref=e256]:
|
||||
- img
|
||||
- group [ref=e258]:
|
||||
- checkbox "I agree to the terms and conditions" [checked] [ref=e259]:
|
||||
- generic:
|
||||
- img
|
||||
- generic [ref=e260]: I agree to the terms and conditions
|
||||
- generic [ref=e261]:
|
||||
- group [ref=e262]:
|
||||
- group [ref=e263]:
|
||||
- button "1" [ref=e264]
|
||||
- button "2" [ref=e265]
|
||||
- button "3" [ref=e266]
|
||||
- group [ref=e267]:
|
||||
- button "Previous" [ref=e268]:
|
||||
- img
|
||||
- button "Next" [ref=e269]:
|
||||
- img
|
||||
- group [ref=e270]:
|
||||
- button "Copilot" [ref=e271]:
|
||||
- img
|
||||
- text: Copilot
|
||||
- button "Open Popover" [ref=e272]:
|
||||
- img
|
||||
- group "How did you hear about us?" [ref=e277]:
|
||||
- generic [ref=e278]: How did you hear about us?
|
||||
- paragraph [ref=e279]: Select the option that best describes how you heard about us.
|
||||
- generic [ref=e280]:
|
||||
- group [ref=e282]:
|
||||
- checkbox "Social Media" [checked] [ref=e283]:
|
||||
- generic:
|
||||
- generic: Thursday
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- generic: Today
|
||||
- generic: "390"
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- generic: Average
|
||||
- generic: "278"
|
||||
- generic [ref=e542]:
|
||||
- generic [ref=e543]:
|
||||
- generic [ref=e544]: Payments
|
||||
- generic [ref=e545]: Manage your payments.
|
||||
- button "Add Payment" [ref=e547]
|
||||
- generic [ref=e548]:
|
||||
- table [ref=e551]:
|
||||
- rowgroup [ref=e552]:
|
||||
- row "Select all Status Email Amount" [ref=e553]:
|
||||
- cell "Select all" [ref=e554]:
|
||||
- checkbox "Select all" [ref=e555]
|
||||
- cell "Status" [ref=e556]
|
||||
- cell "Email" [ref=e557]
|
||||
- cell "Amount" [ref=e558]:
|
||||
- generic [ref=e559]: Amount
|
||||
- cell [ref=e560]
|
||||
- rowgroup [ref=e561]:
|
||||
- row "Select row success ken99@example.com $316.00 Open menu" [ref=e562]:
|
||||
- cell "Select row" [ref=e563]:
|
||||
- checkbox "Select row" [ref=e564]
|
||||
- cell "success" [ref=e565]:
|
||||
- generic [ref=e566]: success
|
||||
- cell "ken99@example.com" [ref=e567]:
|
||||
- generic [ref=e568]: ken99@example.com
|
||||
- cell "$316.00" [ref=e569]:
|
||||
- generic [ref=e570]: $316.00
|
||||
- cell "Open menu" [ref=e571]:
|
||||
- button "Open menu" [ref=e572]:
|
||||
- generic [ref=e573]: Open menu
|
||||
- img
|
||||
- row "Select row success Abe45@example.com $242.00 Open menu" [ref=e578]:
|
||||
- cell "Select row" [ref=e579]:
|
||||
- checkbox "Select row" [ref=e580]
|
||||
- cell "success" [ref=e581]:
|
||||
- generic [ref=e582]: success
|
||||
- cell "Abe45@example.com" [ref=e583]:
|
||||
- generic [ref=e584]: Abe45@example.com
|
||||
- cell "$242.00" [ref=e585]:
|
||||
- generic [ref=e586]: $242.00
|
||||
- cell "Open menu" [ref=e587]:
|
||||
- button "Open menu" [ref=e588]:
|
||||
- generic [ref=e589]: Open menu
|
||||
- img
|
||||
- row "Select row processing Monserrat44@example.com $837.00 Open menu" [ref=e594]:
|
||||
- cell "Select row" [ref=e595]:
|
||||
- checkbox "Select row" [ref=e596]
|
||||
- cell "processing" [ref=e597]:
|
||||
- generic [ref=e598]: processing
|
||||
- cell "Monserrat44@example.com" [ref=e599]:
|
||||
- generic [ref=e600]: Monserrat44@example.com
|
||||
- cell "$837.00" [ref=e601]:
|
||||
- generic [ref=e602]: $837.00
|
||||
- cell "Open menu" [ref=e603]:
|
||||
- button "Open menu" [ref=e604]:
|
||||
- generic [ref=e605]: Open menu
|
||||
- img
|
||||
- row "Select row failed carmella@example.com $721.00 Open menu" [ref=e610]:
|
||||
- cell "Select row" [ref=e611]:
|
||||
- checkbox "Select row" [ref=e612]
|
||||
- cell "failed" [ref=e613]:
|
||||
- generic [ref=e614]: failed
|
||||
- cell "carmella@example.com" [ref=e615]:
|
||||
- generic [ref=e616]: carmella@example.com
|
||||
- cell "$721.00" [ref=e617]:
|
||||
- generic [ref=e618]: $721.00
|
||||
- cell "Open menu" [ref=e619]:
|
||||
- button "Open menu" [ref=e620]:
|
||||
- generic [ref=e621]: Open menu
|
||||
- img
|
||||
- row "Select row pending jason78@example.com $450.00 Open menu" [ref=e626]:
|
||||
- cell "Select row" [ref=e627]:
|
||||
- checkbox "Select row" [ref=e628]
|
||||
- cell "pending" [ref=e629]:
|
||||
- generic [ref=e630]: pending
|
||||
- cell "jason78@example.com" [ref=e631]:
|
||||
- generic [ref=e632]: jason78@example.com
|
||||
- cell "$450.00" [ref=e633]:
|
||||
- generic [ref=e634]: $450.00
|
||||
- cell "Open menu" [ref=e635]:
|
||||
- button "Open menu" [ref=e636]:
|
||||
- generic [ref=e637]: Open menu
|
||||
- img
|
||||
- row "Select row success sarah23@example.com $1,280.00 Open menu" [ref=e642]:
|
||||
- cell "Select row" [ref=e643]:
|
||||
- checkbox "Select row" [ref=e644]
|
||||
- cell "success" [ref=e645]:
|
||||
- generic [ref=e646]: success
|
||||
- cell "sarah23@example.com" [ref=e647]:
|
||||
- generic [ref=e648]: sarah23@example.com
|
||||
- cell "$1,280.00" [ref=e649]:
|
||||
- generic [ref=e650]: $1,280.00
|
||||
- cell "Open menu" [ref=e651]:
|
||||
- button "Open menu" [ref=e652]:
|
||||
- generic [ref=e653]: Open menu
|
||||
- img
|
||||
- generic [ref=e658]:
|
||||
- generic [ref=e659]: 0 of 6 row(s) selected.
|
||||
- generic [ref=e660]:
|
||||
- button "Previous" [disabled]
|
||||
- button "Next" [disabled]
|
||||
- generic [ref=e663]:
|
||||
- generic [ref=e664]:
|
||||
- generic [ref=e665]: Share this document
|
||||
- generic [ref=e666]: Anyone with the link can view this document.
|
||||
- generic [ref=e667]:
|
||||
- generic [ref=e668]:
|
||||
- generic [ref=e669]: Link
|
||||
- textbox "Link" [ref=e670]: http://example.com/link/to/document
|
||||
- button "Copy Link" [ref=e671]
|
||||
- generic [ref=e672]:
|
||||
- generic [ref=e673]: People with access
|
||||
- generic [ref=e674]:
|
||||
- generic [ref=e675]:
|
||||
- generic [ref=e676]:
|
||||
- img "Image" [ref=e678]
|
||||
- generic [ref=e679]:
|
||||
- paragraph [ref=e680]: Olivia Martin
|
||||
- paragraph [ref=e681]: m@example.com
|
||||
- combobox "Edit" [ref=e682]:
|
||||
- generic: Can edit
|
||||
- img
|
||||
- generic [ref=e686]:
|
||||
- generic [ref=e687]:
|
||||
- img "Image" [ref=e689]
|
||||
- generic [ref=e690]:
|
||||
- paragraph [ref=e691]: Isabella Nguyen
|
||||
- paragraph [ref=e692]: b@example.com
|
||||
- combobox "Edit" [ref=e693]:
|
||||
- generic: Can edit
|
||||
- img
|
||||
- generic [ref=e697]:
|
||||
- generic [ref=e698]:
|
||||
- img "Image" [ref=e700]
|
||||
- generic [ref=e701]:
|
||||
- paragraph [ref=e702]: Sofia Davis
|
||||
- paragraph [ref=e703]: p@example.com
|
||||
- combobox "Edit" [ref=e704]:
|
||||
- generic: Can edit
|
||||
- img
|
||||
- generic [ref=e708]:
|
||||
- generic [ref=e709]:
|
||||
- img "Image" [ref=e711]
|
||||
- generic [ref=e712]:
|
||||
- paragraph [ref=e713]: Ethan Thompson
|
||||
- paragraph [ref=e714]: e@example.com
|
||||
- combobox "Edit" [ref=e715]:
|
||||
- generic: Can edit
|
||||
- img
|
||||
- contentinfo [ref=e719]:
|
||||
- generic [ref=e722]:
|
||||
- img
|
||||
- checkbox [checked]
|
||||
- generic [ref=e284]: Social Media
|
||||
- group [ref=e286]:
|
||||
- checkbox "Search Engine" [ref=e287]
|
||||
- checkbox
|
||||
- generic [ref=e288]: Search Engine
|
||||
- group [ref=e290]:
|
||||
- checkbox "Referral" [ref=e291]
|
||||
- checkbox
|
||||
- generic [ref=e292]: Referral
|
||||
- group [ref=e294]:
|
||||
- checkbox "Other" [ref=e295]
|
||||
- checkbox
|
||||
- generic [ref=e296]: Other
|
||||
- generic [ref=e297]:
|
||||
- generic [ref=e298]:
|
||||
- generic [ref=e299]:
|
||||
- status "Loading"
|
||||
- generic [ref=e300]: Processing your request
|
||||
- generic [ref=e301]: Please wait while we process your request. Do not refresh the page.
|
||||
- button "Cancel" [ref=e303]
|
||||
- contentinfo [ref=e304]:
|
||||
- generic [ref=e307]:
|
||||
- text: Built by
|
||||
- link "shadcn" [ref=e723] [cursor=pointer]:
|
||||
- link "shadcn" [ref=e308] [cursor=pointer]:
|
||||
- /url: https://twitter.com/shadcn
|
||||
- text: at
|
||||
- link "Vercel" [ref=e724] [cursor=pointer]:
|
||||
- link "Vercel" [ref=e309] [cursor=pointer]:
|
||||
- /url: https://vercel.com/new?utm_source=shadcn_site&utm_medium=web&utm_campaign=docs_cta_deploy_now_callout
|
||||
- text: . The source code is available on
|
||||
- link "GitHub" [ref=e725] [cursor=pointer]:
|
||||
- link "GitHub" [ref=e310] [cursor=pointer]:
|
||||
- /url: https://github.com/shadcn-ui/ui
|
||||
- text: .
|
||||
- region "Notifications alt+T"
|
||||
- alert [ref=e728]
|
||||
- generic [ref=e729]: Mon
|
||||
- alert [ref=e311]
|
||||
Reference in New Issue
Block a user