| name | dryui-feedback |
| description | Apply a single DryUI feedback submission. Use whenever you have a submission id from the feedback widget, a `feedback_get_submissions` payload, a screenshot annotated with arrows/text/components/region boxes, or a request like "act on submission X" / "resolve this feedback" / "the user drew a box on the page and labelled it Y". The skill covers reading the submission, decoding the four intent kinds (drawings / components / removed / moved), the lint trip-wires that block the dev server, and the resolve handshake. |
DryUI Feedback
Single job: take one feedback submission and apply the smallest change that satisfies it. Read the screenshot, decode the structured intents, edit the source, run check, mark resolved. Stop.
The submission shape
A submission is a presentation JSON object (fetch with MCP feedback_get_submissions or curl http://127.0.0.1:4748/submissions/<id>):
id, url, viewport, scroll
preferredScreenshotPath ← read this first
screenshotPath: { png, webp } ← raw screenshot paths; png may be empty for legacy rows
textNotes[] ← direct text instructions extracted from drawings
drawingHints[] ← paired { drawing, hint } entries
summary ← counts by intent kind, hint corner, and structured change kind
drawings[] ← raw annotations the user drew
hints[] ← raw parallel array: each drawing's nearest element + position
components[] ← components the user added through the inspector
removed[] ← elements the user removed
moved[] ← elements the user dragged to a new position
The screenshot is the most direct signal — open preferredScreenshotPath before reading any structured data. Everything else exists to disambiguate what you're seeing. The raw arrays are preserved as escape hatches when the presentation fields are not enough.
Decoding the four intent kinds
Each submission can carry zero or more of these. Treat them as instructions, not suggestions.
drawings[] — annotations
Each entry has a kind (freehand / arrow / text / eraser) and coordinates. Treat textNotes[] as direct user instructions — they're literal sentences the user typed onto the page (e.g. "more padding here", "make this blue", "remove this"). Use drawingHints[] to pair each drawing with its nearest DOM hint; the raw drawings[] and hints[] arrays remain available if you need index-level detail.
Arrows usually point from a label to the thing being modified. Freehand sketches mark a region without naming it — combine with the screenshot to figure out what's circled.
components[] — additions
Each entry: { kind, label, props, rect }. The user picked a DryUI component (e.g. "Card", "Button") and dropped it at rect (viewport coordinates). Apply by inserting that component into the source at the closest matching DOM ancestor, with the user's label and props.
removed[] — deletions
Each entry: { tag, selector?, rect }. The user wants this element gone. Remove the corresponding source node. If the surrounding area becomes empty, leave it empty — don't backfill. The user can ask for a replacement in a follow-up submission.
moved[] — repositions
Each entry: { tag, selector?, originalRect, currentRect }. The user dragged an element from originalRect to currentRect (viewport coordinates). The screenshot shows a solid blue outline at the new position and a dashed blue ghost at the original position so you can see the displacement at a glance. Treat this as a layout intent — the user wants the element to live where the solid outline sits. Edit the data-layout block in src/layout.css (different grid area, reordered template, new track) to match. Apply directly when the move fits an existing sibling slot.
Layout discipline
DryUI does not ship a layout component. All page-level display: grid and display: flex declarations live in src/layout.css (or @container blocks within it). Nowhere else for page layout.
Structural changes (new named region, new responsive shift, different grid template) and region-internal changes (padding inside a wrapper, gap between sibling elements, borders or backgrounds the user requested) are both yours to apply. For structural edits, work in src/layout.css under the existing [data-layout='<name>'] block — extend the template, add a new data-layout-area, or add an @container page (min-width: ...) rule. Pick the smallest edit that satisfies the feedback. Use the --dry-space-* token scale (--dry-space-2 = 8px, --dry-space-3 = 12px, --dry-space-4 = 16px, --dry-space-6 = 24px, --dry-space-8 = 32px) for spacing values; match nearby usage in the file rather than picking magnitudes by feel.
Lint trip-wires that will block the dev server
The DryUI lint runs as a Vite preprocessor — violations crash the dev server with a stack trace, so the user sees a red overlay instead of their page. Avoid every one of these when editing:
dryui/no-width — no width: / inline-size: (or min-/max- variants). Grid children are sized by their track in src/layout.css. The only allowed unit family is the typographic measure (ch, em, ex).
dryui/no-global — no :global(...) selectors. Use scoped CSS, data-attributes, or CSS variables.
dryui/no-inline-style — no style="..." attributes. Use scoped classes or CSS variables.
dryui/no-style-directive — no style:foo={bar} directives.
dryui/no-raw-native-element — use DryUI components, not raw HTML, for anything DryUI covers.
dryui/no-flex and dryui/no-raw-grid — flex and grid declarations only in src/layout.css.
Component preferences (avoid lint and a11y regressions)
| Reach for | Not |
|---|
<Heading level> | <h1>–<h6> |
<Text> | <p> / <span> for body |
<Button> | <button> |
<Input> | <input type="text"> |
<Slider> | <input type="range"> |
<Select> | <select> |
<Checkbox> | <input type="checkbox"> |
<Separator> | <hr> |
<Field.Root> + <Label> | unlabelled form inputs |
Raw HTML is permitted only for semantic landmarks (<header>, <nav>, <aside>, <main>, <footer>, <section>, <article>) and for content the DryUI surface doesn't cover (<ol>, <ul>, <li>, <a>).
Restraint
You're applying one submission. Do only what the feedback asks for:
- Don't refactor adjacent code that wasn't called out.
- Don't reformat the whole file.
- Don't "improve" things the user didn't mention.
- Don't add tests, comments, or docs unless the feedback explicitly asks.
- Match the file's existing patterns — if it uses
Heading level={3} for section headers, your additions should too.
If the feedback is ambiguous, prefer the smaller change. The user can always send another submission.
Hand-off table
| Feedback shape | Hand off to |
|---|
| Pure design tokens / theme decisions | (theme agent — coming) |
| Forms wiring / a11y / focus / keyboard | (forms agent — coming) |
| Microcopy or content-only edits | apply directly |
| Padding / spacing / colour / size on existing element | apply directly |
| Structural layout change (new region, new responsive breakpoint, different template) | apply directly in src/layout.css |
Component swap (e.g., raw <button> → <Button>) | apply directly |
Adding a component the user explicitly placed via components[] | apply directly |
Removing an element via removed[] | apply directly |
Moving an element via moved[] | apply directly |
Default to "apply directly". Layout-shape edits live in src/layout.css; component edits live in the route file.
Workflow
- Get the submission. Use MCP
feedback_get_submissions (preferred) or curl http://127.0.0.1:4748/submissions/<id>.
- Claim it. Call MCP
feedback_claim_submission (or curl -X POST http://127.0.0.1:4748/submissions/<id>/claim -H "Content-Type: application/json" -d '{"agent":"claude","name":"Claude Code","model":"claude-opus-4-7","version":"<cli-version>"}'). This flips the dashboard status to Processing so the human knows you've picked it up, stamps the start time, and records which AI / model / version is working — the completed-tasks view uses that history. Pass the most specific name, model, and version you know. Skip this step only when reproducing the workflow in tests.
- Read the screenshot. Use the
Read tool on preferredScreenshotPath. This is the ground truth.
- Locate the page in source. The submission's
url maps to a route. For http://localhost:5174/foo that's src/routes/foo/+page.svelte. For the index, src/routes/+page.svelte.
- Pair drawings with hints. Prefer
drawingHints[]; each pair has { drawing, hint }. If you need the raw arrays, for each drawings[i], look at hints[i].element to find the DOM target, and hints[i].corner + hints[i].percentX/Y for sub-element placement.
- Apply intents. In order:
drawings (textNotes[] are instructions), components (additions), removed (deletions), moved (repositions — usually a src/layout.css edit). Make the smallest source edit that satisfies each.
- Run checks. Run the project’s focused check/build/test command for the changed file or package. Fix any violations the edit introduced — re-read this skill's lint section if confused.
- Resolve. Call MCP
feedback_resolve_submission with the submission id, or curl -X PATCH http://127.0.0.1:4748/submissions/<id> -H "Content-Type: application/json" -d '{"status":"resolved"}'. The server stamps resolvedAt and computes durationMs from your claim so the dashboard can show how long the task took. Required to clear the submission from the queue.
- If you bail out, release. If you claimed a submission but decided not to apply it (out of scope, blocked, etc.), call MCP
feedback_release_submission so the dashboard moves it back to Pending instead of leaving it stuck under your name.
Tone
Quiet. State the submission id and what intents it carries. Claim, make the edit, run the relevant project check, resolve. The user already wrote the feedback — don't re-explain it back to them.