Visual UI feedback tool — annotate elements in a running web app, then fix them from annotation files. Two modes: `/redline setup` guides installation of the Chrome extension or Tauri plugin. `/redline <path>` reads an annotation JSON file and fixes all annotated issues in the codebase. Use this skill whenever the user mentions: redline, UI annotations, visual feedback, annotate elements, fix annotations, paint feedback on UI, mark up the UI, visual code review of a running app, or wants to annotate and fix visual issues in their web app.
Annotate elements in a running web app, then process those annotations to fix the code.
/redline setupGuide the user to install the Redline annotation overlay for their environment.
Check the project type:
src-tauri/ directory → Tauri app → recommend the Tauri pluginAsk the user to confirm if unclear.
Tell the user:
https://github.com/twiced-technology-gmbh/redline-plugin-chrome and load unpackedCmd+Option+Shift+A (Mac) / Ctrl+Alt+Shift+A (Win/Linux) to toggleGuide the user through these changes:
Add dependency to src-tauri/Cargo.toml:
tauri-plugin-redline = "0.1"
Or for a local path (e.g., monorepo):
tauri-plugin-redline = { path = "../path/to/tauri-plugin-redline" }
Register the plugin in src-tauri/src/lib.rs — must be on the Builder (before .setup()), not inside setup(), because the plugin uses js_init_script:
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_shell::init());
if cfg!(debug_assertions) {
builder = builder.plugin(tauri_plugin_redline::init());
}
builder.setup(|app| { /* ... */ })
Add permission to src-tauri/capabilities/default.json:
"redline:default"
Run cargo build to verify compilation.
mkdir -p .claude/redline
Add this entry to .gitignore if not already present:
.claude/redline/
Tell the user:
Cmd+Option+Shift+A (Mac) or Ctrl+Alt+Shift+A (Win/Linux) while the app is running/redline <filename>/redline <filename>Process an annotation file and fix the issues in the codebase.
The argument is a filename (e.g. home-2026-03-10-1430.json). Search for it in this order:
~/Downloads/<filename>find ~ -maxdepth 2 -name "<filename>" -type f 2>/dev/null | head -1Reading strategy: The file contains large base64 screenshot strings at the end. Use Bash to extract the annotation data without screenshots first:
python3 -c "
import json,sys
d=json.load(sys.stdin)
ss={}
if 'screenshots' in d:
ss['has_page']=bool(d['screenshots'].get('page'))
ss['has_annotations']=bool(d['screenshots'].get('annotations'))
d.pop('screenshots')
d.pop('screenshot',None)
d['_screenshot_availability']=ss
print(json.dumps(d, indent=2))
" < FILE_PATH
This gives you all annotation data with their computedCss included (needed for fixes) while keeping context manageable.
Every annotation has a type field. The type determines how reliably the captured element data identifies the user's actual target:
HIGH confidence — select type:
The user clicked directly on a DOM element. The selector, html, computedCss, and childHints are exact. Proceed directly to source location (Step 3).
MEDIUM confidence — arrow, circle, box, text types:
The user drew a shape near an element. Element data comes from document.elementFromPoint() at the shape's center or endpoint. Usually correct, but may capture a parent wrapper instead of the intended child.
Check the html field:
data-testid, id, meaningful classes, text content) → treat as reliablecomment refers to something specific (e.g., "change icon color") but the html is a generic wrapper (e.g., <a class="card-link">) → the target is likely a child element. Check childHints for the actual target (e.g., <svg> for an icon comment).For arrows: The to point (arrow tip) determines the captured element. The user is pointing AT this element — focus on it, not the from point.
LOW confidence — freehand type:
The bounding box center of freehand strokes can land anywhere — often a parent container. If the html is a generic container (<div>, <main>, <section>, <article>) with 4+ childHints, the captured element is almost certainly NOT the actual target.
→ For LOW confidence annotations, you MUST use the screenshot for disambiguation (Step 2b). → For MEDIUM confidence annotations where the comment doesn't match the captured element, also use the screenshot.
The JSON includes a screenshots object with two base64 images:
screenshots.page — the app content with the overlay hidden (JPEG)screenshots.annotations — the drawn shapes on transparent background (PNG)Extract and read both screenshots using the Read tool on the original JSON file, or extract them:
python3 -c "
import json,sys,base64
d=json.load(sys.stdin)
ss=d.get('screenshots',{})
if ss.get('page'):
with open('/tmp/redline-page.jpg','wb') as f: f.write(base64.b64decode(ss['page'].split(',')[1]))
print('Page screenshot: /tmp/redline-page.jpg')
if ss.get('annotations'):
with open('/tmp/redline-annotations.png','wb') as f: f.write(base64.b64decode(ss['annotations'].split(',')[1]))
print('Annotations screenshot: /tmp/redline-annotations.png')
" < FILE_PATH
Then Read both image files. You are a multimodal model — you can see images. Use them to:
nearSelector/html/childHints tell you WHERE in the DOM that target is (or its parent is).Example: An arrow with comment "Change icon color to yellow" has nearSelector pointing to <a href="/skills"> (a card wrapper). The screenshot shows the arrow tip pointing at the SVG icon inside that card. The childHints confirm <svg> is the first child. → Target is the SVG icon component inside the Skills card, not the card wrapper itself.
Example: A freehand with nearSelector pointing to #main-content (the entire page body). This is useless alone. The screenshot shows freehand strokes circling a specific table row. → Target is that table row, identifiable by cross-referencing the position with the page layout.
For each annotation, find the source file. Strategy depends on confidence:
For HIGH confidence (select) and confirmed MEDIUM/LOW annotations:
Extract identifiers from html (fastest path):
data-testid, id, aria-label, or other unique attributesTrace the selector path (when html has no unique identifiers):
>):nth-child(N) — count children in the parent's JSXcreatePortal to the rendering componentFor draw annotations where the target is a CHILD of the captured element:
<svg> icon inside the card component)childHints array shows direct children — match the one relevant to the commentVerify with secondary signals:
childHints: confirm children matchtext: confirm rendered texttagName: confirm HTML tagposition/from/to: consistent with layout positionNEVER do this:
html or selectorGroup confirmed annotations by source file for parallel fixes.
For annotations that map to different files, dispatch parallel agents — one per file. Each agent receives the full annotation data and:
comment: The user's feedback — this is the PRIMARY instructioncomputedCss: Current styling — use to understand what needs to change (e.g., for "too much padding", read padding-* values)html: Rendered outerHTML — shows attributes and structurechildHints: Direct children — helps navigate inner structurecomputedCss reference — curated subset of ~40 properties:
display, position, width, height, min-*, max-*, padding-*, margin-*, gap, flex-*, align-*, justify-content, grid-*, top/right/bottom/left, z-index, overflowcolor, background-color, background, border, border-radius, box-shadow, opacityfont-size, font-weight, font-family, line-height, text-align, text-decoration, letter-spacing, white-spacetransform, transitionDefault/empty values (none, normal, auto, 0px, transparent) are omitted.
For ambiguous comments (e.g., "fix this", "wrong", "ugly"): flag in the summary rather than guessing. Include the selector, current styles, and what the screenshot shows so the user can clarify.
Redline: processed <N> annotations from <view>
Fixed:
- div.card > h2.title: reduced padding from 24px to 12px (src/components/Card.tsx:15)
- button.submit: changed color from #333 to var(--primary) (src/styles/buttons.css:42)
Skipped (ambiguous):
- nav > a.active: comment was "fix this" — please clarify what needs to change
Skipped (low confidence, no screenshot):
- #main-content: freehand annotation "adjust spacing" — nearSelector hit the page container, could not determine target without screenshot
Files modified:
- src/components/Card.tsx
- src/styles/buttons.css