Automated summary
Foundation for building and running ui-engine UIs with Lua apps connected to widgets.
Strengths
- Structured command set via .ui/mcp
- Event loop support for interactive UI handling
- Playwright integration for automated testing
- Theme and checkpoint management
Limitations
- Requires familiarity with Lua and app structure
- Event loop must be running for UI to respond
- Absolute paths are disallowed, limiting some automation scripts
When developing or testing UIs built with ui-engine and Lua apps.
For non-UI tasks or when not using the specific ui-engine framework.
Security analysis
CautionThe skill instructs using powerful tools (bash, Playwright) for legitimate UI development purposes. It does not contain malicious commands, exfiltration, or obfuscation, but the execution of arbitrary Lua code via .ui/mcp run could be risky if misused.
- •Uses .ui/mcp scripts that execute Lua code on a server
- •Instructions involve Playwright browser automation
- •Background bash processes for event loop
Examples
/uishow contactseventsname: ui description: use when running Frictionless UIs or needing to understand UI structure (before modifying one)
UI MCP
Foundation for building and running ui-engine UIs with Lua apps connected to widgets.
Simple Requests
When the user says /ui, show app-console as in Quick Start. Prefer Playwright if the user has been using it.
When the user says show APP, show APP as in Quick Start. Prefer Playwright if the user has been using it.
When the user says events it means to start the event loop, but not use .ui/mcp display or .ui/mcp browser as in Quick Start.
Helper Script Reference
The .ui/mcp script provides commands for interacting with the UI server. Always use relative paths (never absolute — absolute paths break the user's permission rules).
{url} means the UI server URL — read the port from .ui/ui-port and construct http://localhost:{port}. This is NOT the MCP connection port — it's the UI's own HTTP server. Use .ui/mcp status when you also need session count or base_dir.
CRITICAL: URLs must NEVER include session IDs. Always use {url}/?conserve=true (root URL). Session IDs in URLs will cause problems.
| Command | Description |
|---------|-------------|
| .ui/mcp status | Get server status (url, sessions, base_dir) |
| .ui/mcp browser | Open browser to {url}/?conserve=true |
| .ui/mcp display APP | Display APP in the browser |
| .ui/mcp run 'lua code' | Execute Lua code in session |
| .ui/mcp event | Wait for next UI event (120s timeout) |
| .ui/mcp state | Get current session state |
| .ui/mcp variables | Get current variable values |
| .ui/mcp audit APP | Run code quality audit on APP |
| .ui/mcp progress APP PERCENT STAGE | Report build progress |
| .ui/mcp linkapp add\|remove APP | Manage app symlinks |
| .ui/mcp checkpoint CMD APP [MSG] | Manage checkpoints (save/list/rollback/diff/clear) |
| .ui/mcp theme list | List available themes |
| .ui/mcp theme classes [THEME] | List semantic classes for theme |
| .ui/mcp theme audit APP [THEME] | Audit app's theme class usage |
Quick Start: Show an Existing App
A. Read the design
Read {base_dir}/apps/APP/design.md to learn the global variable name (e.g., claudePanel) and event types.
B. Display the app
- Use
.ui/mcp runto check ifmcp.value.type == AppName(PascalCase version of app-name)- If not, use
.ui/mcp display app-name
- If not, use
- If Playwright is connected:
browser_evaluatewithfunction: "() => window.location.href"to get current URL- If URL does NOT start with
{url}/, thenbrowser_navigateto{url}/mark-playwright.html?url={url}/?conserve=true - ONLY use mark-playwright.html when navigating via Playwright. Never use it for
.ui/mcp browser. - Do not wait for page to display or take a snapshot
- Otherwise, run
.ui/mcp browser
C. Start the event loop
Bash(.ui/mcp event, run_in_background=true)
The UI will NOT respond to clicks until this is running.
D. Handle events
- Parse JSON:
[{"app":"app-console","event":"select","name":"contacts"}] - Read design.md's "Events" section for how to handle each event type
- For UI changes: Check event's
handlerfield and invoke that skill - Simple state changes: use
.ui/mcp rundirectly - Kill old task, then restart:
Bash(.ui/mcp event, run_in_background=true)
Details
Step A: The app field
The app field is the ONLY field that determines which design.md you read. Do NOT be misled by context, note, name, or other fields that mention other app names — these are event data, not the source app.
Event: {"app":"app-console", "event":"app_created", "name":"contacts", "context":"contacts"}
^^^^^^^^^^^^^^^^
Read: .ui/apps/app-console/design.md <-- ONLY use the "app" field
Do NOT skip reading design.md — even for events that seem obvious like app_created.
Step C: Event loop lifecycle
Only ONE listener may exist at a time — the script enforces this via .ui/.eventpid and will error if a listener is already running.
Task lifecycle:
- Run
.ui/mcp eventwithBash(run_in_background=true), save the task_id. If it errors with "already running", do nothing — the script manages the PID lock itself. Do NOT manually check PIDs or the.eventpidfile; just wait for the existing listener to complete. - When the background task completes, ALWAYS read the output file immediately — do NOT assume timeout. Failing to read means silently dropping events.
- Handle any events received
- Start a fresh listener (go to step 1)
Exit codes:
- 0 + JSON output = events received (may be empty array
[]for timeout) - 52 = server restarted (restart both server and event loop)
After ui_configure: Restart the event loop — reconfiguring changes the MCP port that .ui/mcp event uses.
Step D: Handler dispatch and build settings
NEVER modify or create UI code directly. When an event requires UI changes, check the handler field and invoke that skill. This applies to ALL UI modifications — requirements, design, code, and viewdefs.
Every event has two fields injected automatically based on the status bar toggles:
| Toggle | Field | Values |
|--------|-------|--------|
| Build mode (rocket/diamond) | handler | "/ui-fast" or "/ui-thorough" |
| Execution (hourglass/arrows) | background | false or true |
These reflect the user's explicit choices. For background execution:
Task(subagent_type="ui-builder", run_in_background=true, prompt="invoke {handler} skill...")
App Variable Convention
Each app defines a global variable for interacting with it via .ui/mcp run:
| App Name | Global Variable | Example Call |
|----------------|-----------------|-------------------------------------|
| claude-panel | claudePanel | claudePanel:addAgentMessage("Hi") |
| contacts | contacts | contacts:addContact(name, email) |
| ma-luba | maLuba | maLuba:someMethod() |
Convention: kebab-case app name → camelCase variable. The global variable is exactly the camelCase conversion of the app directory name (no "App" suffix).
Find the variable by looking at the bottom of app.lua for the instance creation:
if not session.reloading then
claudePanel = ClaudePanel:new() -- <-- this is the global variable
end
Chat Messages
All chat messages render markdown. Use it via .ui/mcp run:
.ui/mcp run 'mcp:addAgentMessage("Found **salary data**: $150k-$180k")'
Pointing at UI elements
When the user asks "where is X?" or "show me the Y", both highlight the element AND include a clickable link in your response so the user can re-trigger the highlight later:
- Find the element's DOM id (from variables, viewdefs, or Playwright)
- Highlight it immediately:
mcp.code = [=[window.uiApp.highlight("ui-42")// ]=] .. os.time() - Reply with a rich message containing a highlight link:
mcp:addRichMessage(
"The search box is "
.. mcp:highlightLink("ui-42", "right here")
.. ". Type to filter the list."
)
The link stays in the chat history — the user can click it anytime to re-highlight the element.
Message methods
| Method | Description |
|--------|-------------|
| mcp:addAgentMessage(text) | Agent message with markdown rendering |
| mcp:addRichMessage(html) | Agent message with raw HTML (for highlight links, custom content) |
| mcp:highlightLink(elementId, label) | Returns anchor HTML that highlights an element on click |
| mcp:addAgentThinking(text) | Thinking/progress message (italic, updates status bar) |
| mcp:renderMarkdown(text) | Convert markdown to HTML fragment (for custom use) |
Building or Modifying UIs
When the user asks for changes outside an event loop, ask which mode they prefer or default to /ui-fast for small changes.
Before building a new app:
- Create the app directory:
mkdir -p {base_dir}/apps/<app> - Write requirements to
{base_dir}/apps/<app>/requirements.md
Requirements Format
# Descriptive Title
A short paragraph describing what the app does.
## Section 1
...
The first line is a descriptive title (e.g., "# Contact Manager"), followed by prose describing the app. See .claude/skills/ui-builder/examples/requirements.md for a reference.
Directory Structure
{base_dir}/
├── apps/<app>/ # App source files
│ ├── requirements.md # What to build (you write this)
│ ├── design.md # How it works (generated)
│ ├── app.lua # Lua code (generated)
│ └── viewdefs/ # HTML templates (generated)
├── lua/ # Symlinks to app lua files
├── viewdefs/ # Symlinks to app viewdefs
├── html/<app> # Symlink to app dir (serves static files at /<app>/)
├── html/<app>-storage # Symlink to storage dir (serves at /<app>-storage/)
├── storage/<app>/ # Optional local storage (isolated from app updates)
├── patterns/ # Reusable pattern documentation
├── themes/ # Theme definitions and CSS variables
├── event # Event wait script
└── log/ # Runtime logs
File Ownership
requirements.md— you write/update thisdesign.md,app.lua,viewdefs/— MUST load/ui-basicsfirst, then use/ui-fastor/ui-thorough. This is a non-standard system; standard web patterns will lead you astray.
Debugging
- Lua logs:
{base_dir}/log/lua.logfor Lua errors - MCP server stderr:
.ui/log/mcp.log - Variable inspector:
http://localhost:{mcp-port}/variables(read port from.ui/mcp-port) — curl for JSON, browser for interactive inspector - MCP resources:
ui://variables(full variable tree),ui://state(live state JSON) - JS diagnostics:
window.uiApp.getStore()(variable state) andwindow.uiApp.getBinding()(widget bindings) in browser console - Remote JS execution: Set
mcp.codefrom Lua — bound toui-codein the MCP shell, enabling JS execution in the browser. Critical when using a system browser instead of Playwright. Re-assigning the same value is a no-op (change detection); append a nonce to re-execute (e.g.,code .. "\n// " .. nonce) .ui/mcp runreturns error messages
From the file to a first result
The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.
View the source and supporting filesThis download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.
1. Download the file
Download SKILL.mdFrom the root of a test project, save the file as .skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.
Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.
mkdir -p '.skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/ui-mcp-frictionless-ui-framework-mm5t89tq/download' --output '.skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq/SKILL.md.part' && mv '.skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq/SKILL.md.part' '.skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq/SKILL.md'2. Check prerequisites
Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.
No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.
The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.
3. Load the skill
After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.
Claude Code
test -f '.skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq/SKILL.md' && test -s '.skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/ui' && cp -R '.skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq' '.claude/skills/ui'OpenSkills
OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.
Official OpenSkills installationopenskills install './.skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq'openskills read 'ui-mcp-frictionless-ui-framework-mm5t89tq'Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.
For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.
openskills syncManual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.
4. Try a first task
Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:
Read .skill-downloads/ui-mcp-frictionless-ui-framework-mm5t89tq/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.
React Hook Form Best Practices
Development
Master React Hook Form: avoid anti-patterns, manage subscriptions, and handle reset, dirty state, and controlled inputs correctly.
Emotion to Mantine Migration
Development
Migrate Emotion styled-components to Mantine components with style props and CSS modules. Use when converting .styled.tsx files or removing @emotion imports.
Scope Creep Detector
Development
Analyzes git diffs against a stated intent to detect scope creep, unrelated files, broad PRs, and oversized changes, then suggests keep, split, or justify actions.