Skip to content
Back to skills

Run Elm

ASecurity

Run, build, screenshot, or regenerate protos for the local Elm SPA frontend (frontends/elm-spa). Use when the user wants to start the Elm dev server, check an Elm change standalone, or drive/screenshot a page in a headless browser.

  • 65 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 10, 2026
developmentrustgonodeexpresstestinggitfrontendbackendperformance

Works with

  • cli

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 6 files and shows the line behind each finding

Scanned September 10, 2026

npx -y skills add JonLatane/jonline --skill run-elm --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Run Elm?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Run Elm
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jonlatane-run-elm/badge)](https://www.skillsdirectory.com/skills/jonlatane-run-elm)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: run-elm
description: Run, build, screenshot, or regenerate protos for the local Elm SPA frontend (frontends/elm-spa). Use when the user wants to start the Elm dev server, check an Elm change standalone, or drive/screenshot a page in a headless browser.
---

Rellm's Elm frontend lives in `frontends/elm-spa` and is driven through its own `Makefile`. Paths below are relative to the repo root unless noted.

## Running the dev server

```
cd frontends/elm-spa && make run
```

- This runs `npx elm-spa server`.
- Serves on **port 1234** locally.
- The user often has this running themselves already — it's fine to kill and restart it.

## Other targets

- `make build` — `npx elm-spa build`, a production build. Also the fastest way to get real compiler errors for a change — `Gen.*`/`Request`/`Route` (elm-spa's generated routing glue) only exist after this or `make run` has generated them at least once, so a bare `elm make` beforehand will show spurious "module not found" errors for those.
- `make protos` — regenerates Elm proto bindings into `./src` from `../../protos/*.proto` via `protoc-gen-elm`. Run `make setup_proto_build` once beforehand if `protoc-gen-elm` isn't installed (`yarn global add protoc-gen-elm`).

## When to use this vs. the backend

Use this skill for fast standalone iteration on Elm code/UI. If you need to confirm a change works *as served by the Rust backend* (e.g. routing, embedded assets), use the `run-backend` skill's `rebuild_elm_and_run` target instead.

## Screenshotting / driving a page (agent path)

`chromium-cli` isn't available in this container, so this skill carries its own tiny driver at `.claude/skills/run-elm/driver.mjs` (Playwright, chromium-cli-style line commands). It needs the dev server (above) already running on port 1234.

**One-time setup:**

```
cd .claude/skills/run-elm && npm install && npx playwright install chromium
```

**Usage:** pipe a line-per-command script to it (paths in `screenshot` are relative to your `cwd` when you invoke `node`, so `cd` back to the repo root first, or use absolute paths):

```
cd /path/to/rellm
node .claude/skills/run-elm/driver.mjs <<'EOF'
nav http://localhost:1234/server/http:localhost
wait-for text=About
screenshot /tmp/about.png
click button:has-text("Theme")
wait-for text=Primary Color
screenshot /tmp/theme.png
console-errors
EOF
```

Commands: `nav <url>`, `wait-for text=<text>` (or a CSS/Playwright selector), `click <selector>`, `click-at <selector> <x> <y>` (click at a specific offset inside `selector`, for hitting a large backdrop/overlay element at a point not covered by a smaller child on top of it), `fill <selector> <value>`, `type <anything> <value>` (types `value` via real keydown/keyup events at whatever element currently has DOM focus — the selector arg is ignored, no click-to-focus first; see the password-field gotcha below), `upload <selector> <path>` (clicks `selector`, feeds `path` to the native file picker it opens — e.g. `File.Select.file`), `press <key>`, `sleep <ms>`, `screenshot <path>`, `console-errors` (prints any `console.error`s seen so far), `eval <js>` (runs `<js>` as a single-line expression via `page.evaluate`, prints the JSON-serialized result — wrap multi-statement code in an IIFE, e.g. `eval (() => { ...; return x; })()`; can `await` an async IIFE too, useful for polling). See the comment header in `driver.mjs` for the full list. Read the resulting PNG with the `Read` tool to actually look at it — a screenshot you didn't view proves nothing.

### Gotchas

- Navigating to a `/server/:id` (or similar server-probing) route logs `net::ERR_CONNECTION_REFUSED`/`ERR_SSL_PROTOCOL_ERROR` to the console while it tries candidate ports — that's `Shared.AccountsPanel`'s own connection negotiation working as designed, not a real error. Only worry about `console-errors` output that isn't explained by a page you know is probing a host.
- `npx playwright install chromium` can print a large "you're running this without installing your project's dependencies" warning banner and appear to do nothing — that's normal here (this driver's `package.json` lives in the skill dir, not the repo root); it still downloads the browser. If browsers were already cached from a previous run (`~/.cache/ms-playwright`), the command produces no output at all and returns immediately — that's success, not a hang.
- `node_modules`/`package-lock.json` under `.claude/skills/run-elm/` are for this driver only, gitignored via the skill's own `.gitignore` — don't confuse them with the Elm app's own dependencies (there are none; Elm has no `node_modules`).
- **`fill` on the Accounts Panel's password field (`#account-form-password`) triggers a premature form submission with an empty/partial password**, popping the create-account confirmation modal early and (if you let it through) failing with a `password_too_short_min_8` error even though the field visually shows the right value. Root cause unconfirmed (suspected: this field is freshly mounted by `ChooseCreateAccountClicked`'s own `Dom.focus`, and `fill`'s synthetic value-set + input event races that). Use `type` instead — it sends real per-character key events to whatever's focused (no click first, since the app already focuses the field itself) and doesn't trigger the early submit. See the "log in as a test account" recipe below.
- `click button:has-text("+ Add")` (or any selector with a bare `+`/`>`/`~` in the text) fails to parse as CSS — Playwright reads `+` as a sibling combinator inside the `:has-text()` argument. Use a class selector instead (e.g. `.my-media-panel-add`) for button text containing those characters.

### Recipe: diagnosing a CSS animation/transition bug

When a FLIP-style animation (`UI.Flip.elm`, used by Accounts/Servers/StarredPosts/Users/MyMediaPanel) looks like it's not playing, don't guess from reading the Elm — instrument the real browser, since the failure mode is usually a CSS/DOM-timing interaction invisible in the code:

```
node .claude/skills/run-elm/driver.mjs <<'EOF'
... (get to the state right before the animation, e.g. an upload/delete) ...
eval window.__log = []; document.querySelector('<container selector>').addEventListener('transitionrun', e => window.__log.push({type:'run', prop:e.propertyName, t:Math.round(performance.now())})); document.querySelector('<container selector>').addEventListener('transitionend', e => window.__log.push({type:'end', prop:e.propertyName, t:Math.round(performance.now())})); 'listeners attached'
... (trigger the animation, e.g. click a delete/confirm button) ...
sleep 1000
eval ({log: window.__log})
EOF
```

An empty `log` (no `transitionrun` at all) means the browser never started the transition — almost always because the element's class changed in the *same* DOM patch that also moved/recreated it (a keyed-list reorder, or the whole list unmounting/remounting around a `Fetching` state), which cancels a CSS transition outright with no error. Confirm the theory by reproducing it in isolation, outside Elm entirely, before touching any code — e.g. `eval (() => { wrap.appendChild(el); el.classList.add('collapsed'); })()` vs. toggling the class alone without moving the node; if only the move+toggle combo kills `transitionrun`, that's the bug, not a browser/CSS-support issue. `getBoundingClientRect()` polling in a loop (`await new Promise(r => setTimeout(r, 25))`) is useful for watching a value change gradually vs. snap instantly, as a cheaper first signal before wiring up the event listeners.

### Recipe: log in as a fresh test account

Needed any time a change touches something behind a signed-in account (e.g. an Accounts Panel chip action). Against the local dev backend (`localhost`, candidate port 27707 tried automatically — see `AccountsPanel.candidatePorts`), account creation has no email/approval step, so this is fully scriptable:

```
node .claude/skills/run-elm/driver.mjs <<'EOF'
nav http://localhost:1234
wait-for .accounts-menu-toggle
click .accounts-menu-toggle
wait-for #account-form-server
fill #account-form-username sometestuser
click button:has-text("Create Account")
wait-for #account-form-password
type x SomeP4ssword123
wait-for text=Media Policy
click button:has-text("Cancel")
sleep 300
click button[type="submit"]
wait-for text=Media Policy
sleep 500
click .create-account-modal button:has-text("Create Account")
sleep 2500
screenshot /tmp/logged_in.png
EOF
```

Why the extra Cancel/resubmit round-trip: the first `type` immediately after the password field appears pops the confirmation modal prematurely (see the gotcha above) — its `Cancel` button discards nothing (`CancelCreateAccountClicked` only clears the confirmation, not the form fields), so canceling and clicking the real submit button (`button[type="submit"]`) a second time confirms with the actual typed password intact. Skipping the cancel/resubmit and confirming straight through the premature modal is what produces the `password_too_short_min_8` failure.

The Server field defaults to a known/connected host already, so `#account-form-server` doesn't need to be touched unless testing against a different server. Username must be unique per test run — the backend has no reset between runs on a real dev instance, so reusing a name will hit a "username taken" error instead of creating a fresh account.

Files in this skill

  • .gitignore14 B
  • SKILL.md9.1 KB
  • driver.mjs5 KB
  • narrow_test.mjs1.6 KB
  • package-lock.json1.6 KB
  • package.json205 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.