# Frontend Dev

> Manages local web server lifecycle and enables web page development with Playwright MCP. Use when developing HTML/CSS/JS, building web pages, viewing web pages, testing UI interactions, verifying renders, debugging frontend issues, or iterating on web design. Covers build, startup, shutdown, and browser-based testing workflows.

- Skill: `tomevault-io/frontend-dev-4` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/frontend-dev-4`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/frontend-dev-4/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/frontend-dev-4

---


# Frontend Development

Toolkit for frontend development in the SpeedReader codebase. Handles server lifecycle and delegates browser-based testing to Playwright MCP.

## Setup

Run setup first:

```bash
uv run .claude/skills/frontend-dev/scripts/setup.py
```

Outputs:
- `READY` - Proceed with usage
- `RESTART_REQUIRED` - Ask user to restart Claude Code, then stop and wait

## Development Loop

**All scripts must be run with `uv run`, not `python3`.**

### 1. Edit code

Make changes to frontend code in `src/Frontend/`.

### 2. Reload

```bash
uv run .claude/skills/frontend-dev/scripts/reload.py
```

This builds SpeedReader, starts the server, and waits for health. The server runs in the background with a 15-minute auto-shutdown.

`reload.py` is **fast**. Claude should *never* attempt to short-circuit it.

`reload.py` does a complete, fresh build of the frontend. It is *not necessary* to run **any** build steps manually.

**SpeedReader does not hot reload. Run reload.py after every code change.**

### 3. Interact with Playwright MCP

Use Playwright MCP tools to interact with the page at http://localhost:5050/demo.

For visual regression testing, use `?hideProcessingTime=true` to hide dynamic content:
```
http://localhost:5050/demo?hideProcessingTime=true
```

**Snapshot vs Screenshot:**
- Use `browser_snapshot` for interaction (getting element refs) and existence checks
- Use `browser_take_screenshot` for visual verification - things that look fine in a snapshot are often revealed to be broken when viewed as a screenshot

As a general rule, use snapshots for interaction and screenshots for verification and validation.

*Important*: when looking at screenshots, *be careful*. It's very easy to say "this is fine" when the screenshot is close to the expected result, but different in a visually similar but semantically different way. Example: a rectangle with pointed corners vs rounded corners, small adjustments to the position of an item, the levels of indentation in a list. For cases like this, identify the area of interest, look very carefully at it, and try to identify physically small yet semantically distinctive features.

### 4. Repeat

Edit code -> reload -> verify with Playwright -> repeat until done.

## Actions

Actions are reusable JS scripts. Call them via `browser_run_code`:

```
browser_run_code: code="@demo_upload"
browser_run_code: code="@demo_upload?file=test.png"
browser_run_code: code="@toggle_layers?a_rect=true;r_rect=false;polygon=false;text=true"
```

Syntax: `@action` or `@action?key=value;key2=value2`

Available actions:
- `demo_upload` - Upload image and wait for OCR. Args: `file`
- `toggle_layers` - Set visualization layer visibility. Args: `a_rect`, `r_rect`, `polygon`, `text` (all optional, true/false)

### Writing Actions

Actions are JavaScript files in `scripts/actions/`. Each is an async function receiving `page` and `args`:

```javascript
async (page, args) => {
  const file = args.file || 'hello.png';
  await page.locator('#file-input').setInputFiles(file);
  await page.waitForSelector('.stats-bar', { timeout: 60000 });
}
```

Create or edit actions as needed.

## Fixes

### Screenshots timing out

If `browser_take_screenshot` hangs or times out repeatedly, close and reopen the browser:

```
browser_close
browser_navigate: url="http://localhost:5050/demo"
```

This resets the browser state and typically resolves the issue.

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/j-r-beckett) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-11 -->

