Live ui generation
Enables live UI generation by click-inspecting DOM elements or capturing screenshots, then applying code fixes with a generating animation. Use for "inspect element", "live UI generation", "live gen", "fix this element", "update this component", "change this UI", "edit this part of the page", "make this look different", or when visually selecting DOM elements for modification. Also handles teardown — use for "stop frontloop", "remove frontloop", "uninstall live-ui-generation", or "stop the servers". Does not apply to general code generation without element inspection.From its SKILL.md
npx -y skills add tamal-thetaonelab/frontloop --skill live-ui-generationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
21.8 KB, as published. Nobody here has run it
Live UI Generation
Overview
Closes the loop between browser and Claude via three modes:
- Element inspect: Hover ⋞ FAB → transforms to ✛ Inspect → click element → describe fix. Orange shimmer appears, and the TASK payload arrives in the shell.
- Screenshot capture: Hover ⋞ FAB → click [] Screenshot → drag to select region → describe fix. TASK payload includes
screenshotPath. - Undo: Hover ⋞ FAB → click ↩ Undo (red when history exists). Sends UNDO task; Claude reverts the last change and reloads.
Browser ──WS──▶ ws-task-server (stdout) ──▶ Claude reads TASK, applies change
▲ │
└──────────── HTTP POST /complete/<id> ◀────────────────┘
Browser ──WS──▶ ws-task-server (stdout) ──▶ Claude reads UNDO, reverts change
(fire-and-forget — no COMPLETE response)
FAB: Fixed bottom-right. Hover transforms the FAB from ⋞ to ✛ (Inspect), reveals two dial items: ↩ Undo (grey/red) and [] Screenshot (indigo), plus an "Inspect" label next to the FAB. Hides entirely while any operation is active; Escape restores. Only active in dev mode — guarded by window.__FRONTLOOP_ENABLED__ set from the framework's env variable. Clicking the FAB itself starts inspect mode — the click is never wasted.
Console error capture: Both the inspect and screenshot prompt panels include a "Send console errors" checkbox. When checked, captured console.error calls are included in the TASK payload. A live [N errors] counter appears next to the checkbox.
Undo stack: Task IDs stored in sessionStorage (__frontloop_undo_stack__). Survives page reloads. Button turns red when non-empty, grey when empty.
Architecture
| Component | Location | Role |
|---|---|---|
| WS + HTTP server | <repo-root>/ws-task-server.<server-ext> | WS 7332, HTTP completion + reload + screenshot on 7333 |
| Client bundle | <frontend>/public/frontloop-bundle.js (copied from reference/live-ui-bundle.js) | Self-contained ESM bundle: FAB, WebSocket, console error capture. Guards on window.__FRONTLOOP_ENABLED__ — no-op in production. |
<repo-root>—git rev-parse --show-toplevel<frontend>— dir withsrc/+package.jsonwith a dev server script<server-ext>—.jsifpackage.jsonhas no"type": "module", otherwise.cjs(the server uses CommonJSrequire())
Monitoring
The Monitor tool spawns a persistent background process. ws-task-server.<server-ext> emits TASK: {...} lines to stdout — each line is a real-time notification. Use Monitor, not Bash; Bash is ephemeral and cannot host a persistent WebSocket server reliably.
Sandbox note: If Claude Code is running inside its network sandbox,
Monitorinherits the same restrictions and the server will fail to bind withEPERM: operation not permitted. See Step 3 for the sandbox-safe fallback.
Setup
Claude handles steps 1–5. Step 6 (dev server) must be started by the user.
Step 1 — Resolve repo paths
Identify <repo-root> and <frontend> using the rules above.
Step 2 — ws-task-server
Copy reference/ws-task-server.js verbatim to <repo-root>/ws-task-server.<server-ext>. Use bash to copy — do not reconstruct from memory. Check package.json for "type": "module" first to resolve <server-ext> (.cjs if present, .js otherwise).
Check whether ws is available before starting the server:
ls <frontend>/node_modules/ws 2>/dev/null && echo "ws found" || echo "ws missing"
If missing, install it as a dev dependency in the frontend:
cd <frontend> && npm install --save-dev ws
Step 3 — Start server with Monitor
If the server is already running (lsof -i :7332 | grep LISTEN shows a process), skip launching a new server and go straight to starting the Monitor — do not wait for the user to ask. Always start the Monitor before handing back to the user.
Normal path (no sandbox):
# Via Monitor — server stdout becomes real-time TASK notifications
NODE_PATH=<frontend>/node_modules node <repo-root>/ws-task-server.<server-ext>
Sandbox path — if the Monitor attempt fails with EPERM: operation not permitted on port 7332, the Claude Code sandbox is blocking listen(). Use Bash with dangerouslyDisableSandbox: true instead:
# 1. Start server detached, log to file
NODE_PATH=<frontend>/node_modules node <repo-root>/ws-task-server.<server-ext> \
> /tmp/ws-task-server.log 2>&1 &
# 2. Then Monitor the log file for TASK events
tail -f /tmp/ws-task-server.log | grep --line-buffered -E "TASK:|UNDO:|ERROR|Error|WS on|HTTP on"
The Bash call needs dangerouslyDisableSandbox: true (Claude Code will prompt for permission). The Monitor on the log file does not need sandbox disabled — it is just reading a file.
Verify either path: lsof -i :7332 | grep LISTEN && lsof -i :7333 | grep LISTEN. Expect ws-task-server: WS on 7332, HTTP on 7333.
Step 4 — Copy bundle to public/
Locate the bundle relative to this SKILL.md:
find ~/.claude -path '*/live-ui-generation/reference/live-ui-bundle.js' 2>/dev/null | head -1
Copy it to the project's public folder:
cp <skill-bundle-path> <frontend>/public/frontloop-bundle.js
For CRA the public folder is <frontend>/public/. For Vite/Vue/Svelte/Angular/vanilla it is also public/ at the frontend root. Verify the file landed at a path that will be served as /frontloop-bundle.js by the dev server.
Step 5 — Inject into HTML entry point
Use the exact inject command from reference/wiring.md for the detected framework. Do not add only the bundle <script> tag — the guard script (window.__FRONTLOOP_ENABLED__) is mandatory and must be injected in the same operation. The bundle is a no-op without it.
Both tags are plain <script> — no type="module" on either. Vite extracts inline type="module" scripts into proxy modules that fail to load; IIFE bundles don't need module semantics. The guard uses a runtime hostname check that works identically across all frameworks:
<script>window.__FRONTLOOP_ENABLED__ = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';</script>
<script src="/frontloop-bundle.js"></script>
Framework → HTML file:
| Framework | HTML entry point |
|---|---|
| Vite (React / Vue / Svelte) | <frontend>/index.html |
| CRA | <frontend>/public/index.html |
| Angular | <frontend>/src/index.html |
| Next.js App Router | src/app/layout.tsx (source file — see wiring.md) |
| Next.js Pages Router | src/pages/_document.tsx (source file — see wiring.md) |
| Vanilla | <frontend>/index.html |
After injecting, verify both lines landed:
grep '__FRONTLOOP_ENABLED__' <html-entry-point> && grep 'frontloop-bundle' <html-entry-point> && echo "both present" || echo "MISSING — check injection"
Step 5.5 — Update .gitignore
Append public/frontloop-bundle.js to <frontend>/.gitignore (or <repo-root>/.gitignore if no frontend-level one):
grep -q 'frontloop-bundle' <frontend>/.gitignore 2>/dev/null || \
echo 'public/frontloop-bundle.js' >> <frontend>/.gitignore
Also append .rewind/:
grep -q '\.rewind/' <frontend>/.gitignore 2>/dev/null || \
echo '.rewind/' >> <frontend>/.gitignore
Step 6 — User starts dev server
Claude must NOT start the dev server. Instruct the user to start their dev server.
Usage Flow
Phase A — Element inspect
- User hovers ⋞ FAB → transforms to ✛ Inspect, two dial items (Undo, Screenshot) slide out to the left
- User clicks ✛ FAB (or Inspect label) → FAB hides, crosshair cursor, elements highlight orange on hover
- User clicks element → highlight pinned, prompt panel appears (bottom-right)
- User describes fix, optionally checks "Send console errors". Two radios appear: Minimal (default, messages only) and Detailed (full stack traces). A live
[N errors]counter shows how many errors are captured. - User presses Enter / clicks "Send Fix Task" — or Escape to cancel
- Orange shimmer over element, browser sends
TASKvia WebSocket withconsoleErrorspayload if enabled
Phase A2 — Screenshot capture
- User hovers ⋞ FAB → clicks [] Screenshot → FAB hides, crosshair cursor
- User drags → dashed indigo selection rectangle
- User releases → best enclosing container found, screenshot captured as PNG
- Panel opens with thumbnail preview, selector, textarea, and same "Send console errors" checkbox + radios
- User sends → PNG saved to
/tmp/liveui-screenshot-<id>.png. TASK dispatched withscreenshotPathand optionalconsoleErrors - Indigo shimmer over region
Phase A3 — Undo
- User hovers ⋞ FAB → clicks ↩ Undo (only active when stack is non-empty)
- Browser pops last task ID, sends
UNDO: {"id":"<new>","type":"undo","targetTaskId":"<last>"}via WebSocket - Monitor notifies Claude with
type: "undo"task - Claude reverts the code change → sends
POST /reload
Phase B — TASK arrives
Check type field:
"dom-fix"→ Phase C"undo"→ Phase D (targetTaskIdidentifies the fix to revert)
If screenshotPath present, Read the image file before planning.
If consoleErrors present (full payload in stderr, slim summary in stdout), review the errors before applying a fix — they may explain the problem the user is reporting (e.g. React deprecation warnings causing blank UI areas). The consoleErrors field has:
mode:"minimal"(messages only) or"detailed"(full stack traces)errors: array of{ message, stack, timestamp }objects (full errors only in stderr)- Slim stdout summary:
count(number of errors)
Phase C — Respond and complete
Always send POST /complete/<taskId> at the end of every dom-fix task — even if the prompt was a question, a clarification, or required no code change. The shimmer stays on screen until completion is received; leaving it unsent strands the UI.
- Identify the source file from payload: use
element.sourceLocation(exact file + line) when present — it's the most reliable signal. Fall back toelement.componentHierarchyfor React components,page+pageTitlefor route components, orcontainer.titlefor card identity. - If a code change is needed: find and edit the source file.
- If a code change was made — run:
npx tsc --noEmit 2>&1 | head -40— zero new errors required. Important:tscdoes not catch JSX syntax errors (missing>etc.) — those surface as bundler errors. So run build to identify those. curl -s -X POST http://localhost:7333/complete/<taskId>— always, regardless of whether code changed.- If a code change was made and HMR seems not working:
curl -s -X POST http://localhost:7333/reload
Phase D — Undo
Undo is session-aware: only revert changes made in the current session.
- If the change was made this session: revert that edit, run type check,
POST /reload - If unknown (prior session): inspect
git diff, tell user which file was changed, ask which to revert
No POST /complete for undo — fire-and-forget.
Timing
- Target: TASK → change → completion in under 60s for simple changes
- Shimmer auto-clears after 180s if no completion received
Payload Schema
dom-fix
| Field | Type | Req | Description |
|---|---|---|---|
id | string | yes | 8-char alphanumeric task ID |
type | string | yes | "dom-fix" |
prompt | string | yes | Free-text fix description |
element.selector | string | yes | CSS selector |
element.tag | string | yes | Lowercase tag name |
element.html | string | yes | outerHTML (truncated to 2000 chars) |
element.text | string | yes | textContent (trimmed to 500 chars) |
element.styles | object | yes | Computed style properties |
element.componentHierarchy | string | no | React fiber chain e.g. "th (CurrentTabPane > Table)". null on non-React. |
element.sourceLocation | string | no | Source file + line of the clicked element, e.g. "components/layout/ActivitySidebar.tsx:30". Path relative to src/. null when not available (see edge cases). Present in slim stdout when non-null. |
element.columnIndex | number | no | 0-based column index when <th> clicked |
container.* | varied | no | Card context: selector, tag, html, text, title, value, description, dataKeys |
pageTitle | string | yes | document.title |
page | string | yes | window.location.href |
screenshotPath | string | no | Absolute path to saved PNG. Use Read to view. |
consoleErrors | object | no | Present when user checked "Send console errors". Contains mode: "minimal" — messages only, or "detailed" — full stack traces. errors: array of {message, stack, timestamp}. Slimmed in stdout to {mode, count}; full errors in stderr. |
sourceLocation source by React version:
- React 19: always populated — React captures
fiber._debugStack(anError) natively at fiber creation; Vite dev URLs in the stack already contain the original.tsxpath. - React 16–18 +
@vitejs/plugin-reactor CRA: populated viafiber._debugSource(set by Babel's@babel/plugin-transform-react-jsx-development). - React 16–18 + esbuild/SWC only:
null— no source injection in the JSX transform. - Non-React (Angular, Vue, Svelte): always
null.
undo
| Field | Type | Req | Description |
|---|---|---|---|
id | string | yes | 8-char alphanumeric ID |
type | string | yes | "undo" |
targetTaskId | string | yes | id of the dom-fix task being undone |
Teardown
Run this when the user asks to stop, uninstall, or remove Frontloop from their project.
Detect install version first
ls <frontend>/src/mocks/dom-inspector.ts 2>/dev/null && echo "legacy" || echo "current"
legacy(v0.1.0–0.3.0): TS source files insrc/mocks/+ provider wiring. Use reference/teardown-legacy.md instead of the steps below.current(v0.4.0+): bundle inpublic/+ HTML script tag injection. Continue with steps below.
Step T1 — Kill the running servers
lsof -ti :7332,:7333 | xargs kill 2>/dev/null; echo "servers stopped"
Step T2 — Remove the server file and log
rm -f <repo-root>/ws-task-server.js <repo-root>/ws-task-server.cjs
rm -f /tmp/ws-task-server.log
Step T3 — Remove the bundle
rm -f <frontend>/public/frontloop-bundle.js
Step T4 — Remove the script tag injection
Three things were injected — the comment, the guard script, and the bundle script. All three must go.
For Vite (<frontend>/index.html), CRA (<frontend>/public/index.html), Angular (<frontend>/src/index.html), and Vanilla (<frontend>/index.html), run the appropriate command:
# Vite
sed -i '' '/frontloop dev guard/d; /frontloop-bundle/d; /__FRONTLOOP_ENABLED__/d' <frontend>/index.html
# CRA
sed -i '' '/frontloop dev guard/d; /frontloop-bundle/d; /__FRONTLOOP_ENABLED__/d' <frontend>/public/index.html
# Angular
sed -i '' '/frontloop dev guard/d; /frontloop-bundle/d; /__FRONTLOOP_ENABLED__/d' <frontend>/src/index.html
For Angular, also remove the { "glob": "**/*", "input": "public", "output": "/" } asset entry from angular.json (build and test targets) before running the sed command above.
For Next.js, remove the entire conditional <Script> block from layout.tsx / _document.tsx — see reference/wiring.md for the exact block that was added.
Verify nothing remains:
grep -r '__FRONTLOOP_ENABLED__\|frontloop-bundle' <frontend>/index.html <frontend>/public/index.html <frontend>/src/index.html 2>/dev/null && echo "still present" || echo "clean"
Step T5 — Clean up .gitignore
Remove the lines added during setup:
public/frontloop-bundle.js
.rewind/
Step T6 — Confirm
lsof -i :7332 | grep LISTEN || echo "7332 clear"
lsof -i :7333 | grep LISTEN || echo "7333 clear"
ls <repo-root>/ws-task-server.js <repo-root>/ws-task-server.cjs 2>/dev/null && echo "server file still present — recheck T2" || echo "server file clean"
ls <frontend>/public/frontloop-bundle.js 2>/dev/null && echo "bundle still present — recheck T3" || echo "bundle clean"
Tell the user: Frontloop has been removed. Their source files are untouched — all edits made by Frontloop remain in place and in git history.
Edge Cases
| Situation | Action |
|---|---|
| TypeScript errors in modified files | Fix before completion. If fix requires infra changes, tell user + still send completion |
| Concurrent tasks | Complete task 1 fully before starting task 2 |
Server not running (lsof -i :7332 empty) | Restart via Monitor (Step 3). Shimmer auto-removes after 180s |
EPERM: operation not permitted on port 7332/7333 | Claude Code sandbox is blocking listen(). Start via Bash with dangerouslyDisableSandbox: true + redirect stdout to /tmp/ws-task-server.log, then Monitor the log file (see Step 3 sandbox path) |
| Completion returns 404 | WS connection already closed (tab nav/timeout). Shimmer already gone — no action needed |
Screenshot upload fails (no screenshotPath) | CORS issue — server must set Access-Control-Allow-Origin: * and handle OPTIONS. Reference file handles this. |
element.sourceLocation is null | Expected when: React 18 with esbuild-only JSX (no Babel), production builds, or non-React frameworks (Angular/Vue). Gracefully omitted from slim stdout — fall back to componentHierarchy. |
| FAB doesn't appear | Check window.__FRONTLOOP_ENABLED__ in browser console — must be true. Verify the guard script is injected before the bundle script. |
| FAB appears in production | Guard expression evaluated to true in prod. Verify the framework env var (e.g. import.meta.env.DEV should be false in Vite prod builds). |
Failed to load module script: … MIME type of "text/html" | Either the bundle file is missing from public/ (dev server returned its SPA fallback), or a type="module" guard script was used and Vite extracted it into a proxy module that failed to load. Fix: (1) verify <frontend>/public/frontloop-bundle.js exists; (2) ensure neither the guard nor the bundle script tag has type="module". |
Guard or bundle script tag has type="module" | Remove it from both tags. Vite extracts inline type="module" scripts into proxy modules (index.html?html-proxy&index=N.js) — if that proxy fails, window.__FRONTLOOP_ENABLED__ is never set and the bundle is a no-op. IIFE bundles don't need module semantics. |
window.__FRONTLOOP_ENABLED__ is false in dev | Hostname check failed — dev server is not on localhost or 127.0.0.1 (e.g. remote dev box, custom domain). Open browser console and check window.location.hostname. If needed, manually set window.__FRONTLOOP_ENABLED__ = true in the guard script for that hostname. |
| Bundle loads but FAB doesn't appear | Check window.__FRONTLOOP_ENABLED__ in the browser console — must be true. Verify the guard script is present and comes before the bundle script. |
value.replace is not a function | cssEscape receives a non-string value. Guard with typeof value !== "string" |
| HMR doesn't reflect change | POST /reload forces full page refresh |
| Duplicate FAB in DOM | Bundle loaded twice — check for duplicate script tags in HTML |
CSS :hover dial too wide | Use JS mouseenter/mouseleave (in reference file). CSS :hover on flex container includes invisible children in hit area |
for...of over NodeList fails | Use .forEach() — --downlevelIteration may not be enabled |
| Undo button stays grey after reload | saveUndoStack() missing after a push(), or updateUndoBtnState() not called |
UNDO task for unknown targetTaskId | Prior session — use git diff to identify recent edits and offer to revert |
Common Mistakes
| Mistake | Fix |
|---|---|
Wrong taskId in curl | Copy id verbatim from TASK JSON — 8 alphanumeric chars |
| Skipping type check | Always run npx tsc --noEmit before POST /complete |
| Starting dev server yourself | Never — instruct user to run it |
Server MODULE_NOT_FOUND | NODE_PATH must point to <frontend>/node_modules, not repo root |
EPERM when starting via Monitor | Sandbox blocks port binding. Use Bash + dangerouslyDisableSandbox: true + log file + Monitor log (Step 3 sandbox path) |
Sending POST /complete for undo | Undo is fire-and-forget — only send POST /reload |
Not sending POST /complete after a question/clarification | Completion is always required for dom-fix tasks — even when no code changed. Omitting it leaves the shimmer on screen indefinitely. |
type="module" on guard or bundle script | Remove it — Vite extracts inline module scripts into proxy modules that fail to load; IIFE bundles don't need module semantics |
| Guard script after bundle script | window.__FRONTLOOP_ENABLED__ must be set before the bundle loads — guard script must come first |
Injecting bundle into src/ files | Only patch the HTML entry point — no TypeScript/JSX source files (except Next.js layout) |