Visual feedback
Skill okuegow/agent-skills/plugins/visual-feedback/skills/visual-feedback
Use when the user wants to give feedback on a document, concept, design, plan, or report section by section - e.g. asks for a "Review-Seite", "Feedback-Seite", a kommentierbare HTML-Version, "Feedback zu jedem Abschnitt/jeder Box/jedem Element", comment pins in SVG diagrams, or wants to annotate a deliverable and paste the collected feedback back into the chat. Also when retrofitting an existing HTML page so every element accepts comments.From its SKILL.md
npx -y skills add okuegow/agent-skills --skill visual-feedbackAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 3 stars3 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.
- runs commandsInstructs the agent to run 5 commands, including `python html.parser parse of the HTML` and 4 more.
SKILL.md
12.8 KB, ~3.1k tokens by cl100k_base, as published. Nobody here has run it
Visual Feedback
Overview
Turn any deliverable (concept, report, design, plan) into ONE self-contained HTML
review page where every meaningful element is an inline comment point. Each
comment can carry a category (Question/Error/Change/Praise) and priority
(Must/Optional). Comments persist in localStorage, and the user exports them as
structured markdown WITH a JSON attachment (clipboard or .md file) to paste back
into the chat. The chat paste-back closes the loop: the agent maps each comment
to its target id and works the feedback in.
Built for multiple rounds: when you produce a follow-up version you embed a round manifest that records, per prior comment, what you did with it (addressed / declined / deferred / noted). Resolved points move into a collapsed history and stop reappearing as open fields; the page builds a per-version changelog and marks changed sections. See "Iterating across versions".
The UI defaults to English and carries a small EN/DE toggle in the toolbar; the
toggle switches every visible string AND the exported markdown, and persists in
localStorage under fb:lang. Category/priority are stored as stable, language-
independent ids (frage/fehler/aenderung/lob, muss/optional); only their
display labels translate, so the JSON block reads the same in either language.
Template: copy and adapt template.html in this skill directory. It contains
the complete widget CSS/JS and one example of every target type. Do not rebuild
the mechanics from scratch - that is how past pages drifted into incompatible
variants. The script builds its own toolbar, toast and preview overlay, so
retrofitting an existing HTML page = paste the template's widget CSS block and
complete <script> into it, then only add the markers from the recipe plus
data-fbkey/data-fbver on <body>. Nothing else is required.
Recipe (the page IS this)
-
Comment points.
<body>carriesdata-fbkey="<unique-key>"(never reuse across pages) anddata-fbver="<n>"(bump on every content rewrite, or old comments stick to changed spots). Each<section>carriesdata-fbsec="<n> · <Titel>". Targets:data-fbautoon a section/container auto-pins everyp,li,blockquote,h3,h4and every table row inside (data-fbskipexcludes subtrees) - the default for prose/tablesdata-fb="id"+data-fblabel="Label"for manual targets (cards, callouts, diagram units)data-fbon SVG elements gives overlay pins with a comment panel below the diagram<textarea data-fb="id">for always-visible fields: one general-feedback field per section plus a "Gesamturteil" at the end
-
Widget. A ✎ pin per target opens a textarea in place, plus type/priority chips. Input autosaves (debounced). The sticky toolbar (built by the script) shows a storage-status pill, an "X/Y commented" counter, an EN/DE language toggle, a filter (All/Open/Commented), jump arrows, Preview, Copy feedback, Save as .md and Reset. All labels shown here are the English default; the DE toggle swaps them (and the export) to German.
-
Versioning + comment lifecycle (feedback rounds). Comments live under
fb:<key>:v<ver>. Bumpingdata-fbvercarries prior comments with a still- matching target into the new version - matching now also requires the target's fingerprint to still match (auto-targets by content, manual targets by theirdata-fblabel), so a drifted auto-target or a relabeled manual target surfaces loudly under "Nicht mehr zuordenbar" instead of silently binding to the wrong element. Auto ids are content-based (autoIdhashes the element text; identical text gets a stable per-occurrence suffix), so inserting or reordering other elements no longer shifts ids. Give every<section>a stabledata-fbsecidso changelog jumps survive title edits. On top of carry-forward sits an optional round manifest (<script type="application/json" id="fb-rounds">, embedded in the page) recording, per round, what you did with each prior comment - see "Iterating across versions" below. Without a manifest the page behaves exactly as before (carry everything, no history UI). Multi-tab: saves are read-modify-write merged and astoragelistener syncs other tabs live (targets with a pending edit or open editor are left alone), so the common case (one reviewer, maybe a second tab) never loses data. localStorage has no real transactions, so genuinely concurrent saves from two tabs in the same instant can still drop one - the tool is built for single-reviewer use, not concurrent co-editing. -
Export - both ways, structured, dual-format. "Copy feedback" (navigator.clipboard with execCommand fallback), "Save as .md" (Blob download) and "Preview" all produce the same document: human-readable markdown grouped by section, PLUS a machine-readable JSON block for the agent. The markdown headings follow the UI language (English shown below; German when the DE toggle is on). The JSON block is language-independent.
# Feedback: <page title> Captured on <date> Page: `<data-fbkey>` · Version v<data-fbver> · <n> comments Summary: 2× Error, 1× Question (of which 1× Must) ## <data-fbsec> **<data-fblabel>** <sub>Text · `<id>` · Error · Must</sub> _Ref: "<start of the commented element's text>"_ > comment line(s) ... ```json { "page": ..., "version": ..., "comments": [ {id,type,section,label,quote,category,priority,comment,ts}, ... ], "orphans": [...] }When working the feedback in, prefer the JSON block: `id` is the stable anchor, `quote` the context, `category`/`priority` the intent (values stay `frage/fehler/aenderung/lob` and `muss/optional` regardless of UI language). -
Reset without dialogs. Reset is a two-click confirm inside the button itself ("Sure? Click again" / "Wirklich? Erneut klicken", auto-disarm after 3 s). The page contains zero
alert()/confirm()/prompt()calls - they block the browser. -
Self-contained file. No CDNs, webfonts, or external requests; system font stacks (e.g. Charter/Avenir on macOS); light + dark via
prefers-color-scheme;prefers-reduced-motionrespected; works fromfile://(in-memory fallback with a visible warning when localStorage is blocked). English UI by default with an EN/DE toggle; German strings use real Umlauten, and no text uses em-dashes. -
Verify, then open. Parse the HTML (python
html.parser: no unclosed tags, count ofdata-fbtargets matches plan),node --checkthe extracted<script>, then open with macOSopen(never browser-automation tools for local files). The widget mechanics themselves are covered by the jsdom smoke testtemplate-test.jsnext to the template (needsnpm install jsdom, run only after changing the template, not per page).
Iterating across versions (feedback rounds)
The procedure for every follow-up version. This is what keeps answered points from reappearing as open fields and produces the change history.
- Read the paste-back JSON. It carries
comments(open + reopened points) ANDrounds(the history so far). Keeprounds- you extend it, never rebuild. - Work each point in and assign exactly one status:
addressed(done),declined(rejected -notewith the reason is mandatory),deferred(not this round - stays a live open field),noted(acknowledged, e.g. praise - no action). If you do not act on a point, mark itdeferred; never drop it silently - the page counts un-responded carried comments as "unanswered" and warns in the toolbar. - Bump
data-fbver(N to N+1). A manifest change without a version bump does nothing; the bump is what re-initialises the page. - Author the manifest
<script type="application/json" id="fb-rounds">. Copy every prior round object from the paste-back'sroundsverbatim and APPEND exactly one new round:{ version:N+1, basedOn:N, date, resolutions:[{id,status,comment,note,fp}], changes:[{text,section,fromComments}], changedSections:[id | {id,kind:"new"}] }.idis the prior comment's target id; copy itsfpfrom the paste-back too (auto-targets carry a content fingerprint, manual targets a label fingerprint) so "Doch offen" can verify the target has not drifted.section/changedSectionsreference the stabledata-fbsecid, not the visibledata-fbsectext. - Never leave an addressed/declined point as a live open field, and never
re-emit the reviewer's old answers as pre-filled comments. The manifest, not
old comment text, is how you respond.
addressed/declined/notedarchive the point into the history; onlydeferredstays live. - Content-level "open questions" you authored (e.g. an "Offene
Entscheidungen" box): once decided, remove them from the open section and record
the decision as a
changesline, so they stop showing as open. - Verify (jsdom
template-test.jsafter template edits; parse + open per recipe step 7) and open.
The reviewer gets a "Verlauf"/History button (version-grouped changes + resolutions with "Doch offen" to re-open any point you resolved), "Neu/Geändert in vN" badges on changed sections, and a check mark on resolved targets.
Identity model and accepted residuals. A comment binds to its target by a
fingerprint: auto-targets by content, manual targets by their data-fblabel (so a
body edit keeps the comment while a relabel orphans it loudly). What the fingerprint
guarantees: inserting or reordering other elements never shifts an auto-target's
binding - the occurrence-drift class of silent misassignment is closed. Two residuals
remain, each narrow and of a different kind:
(1) a genuine short-hash collision - two different contents hashing to the same
fingerprint - could in principle misbind an auto-target; it is astronomically
unlikely with the double FNV hash and not further mitigated;
(2) reusing the same id and the same label for unrelated content carries onto
the new element, because it is indistinguishable from a legitimate edit-in-place -
a deliberate manual id+label reuse (author error). Keep manual id+label pairs
meaningful and give manual targets an explicit data-fblabel, and (2) does not occur.
Common mistakes
| Mistake | Fix |
|---|---|
| Rebuilding widget JS/CSS instead of copying the template | Copy template.html; only content and markers change |
| Hand-marking every paragraph with data-fb | data-fbauto on the section; hand-mark only boxes and SVG elements |
| Working feedback in from the prose alone | Use the JSON block: id is the stable anchor, quote the context |
Reusing a manual data-fb id for different content | Manual identity = id + data-fblabel (or, when unlabeled, the leading content). A changed label/content surfaces the old comment as an orphan (loud). An identical id+label pair carries by design (that is how body edits keep their comment) - so never reuse the same id+label for an unrelated element. Always give manual targets an explicit data-fblabel so body edits stay stable |
| SVG pins at 0/0 or drifting after resize | Positioning runs via getBoundingClientRect on load AND resize - keep the template's positionSvgPins wiring intact |
| Table rows not commentable | Use the ✎-cell + colspan-row mechanism from the template |
confirm() for reset | Two-click confirm in the button (template has it) |
| Export as free-form plaintext | Grouped markdown with data-fbsec/data-fblabel so the agent can map comments back |
| Only clipboard export | Also offer the .md Blob download (clipboard can fail on file://) |
| Same localStorage key on two pages | Unique key per file, or comments bleed between pages |
| Feedback UI drowns the content | Widgets stay collapsed until toggled; content layout comes first |
| Re-emitting the reviewer's old answers as open comments next version | Respond via the round manifest (status + note); addressed/declined archive, only deferred stays live |
| Bumping content but forgetting the manifest | Every follow-up version appends one round to fb-rounds; unanswered carried comments are counted and warned |
changes[].section pointing at the visible title | Reference the stable data-fbsecid, not the data-fbsec display text, or jumps break after title edits |
Rebuilding the rounds array | Copy all prior rounds from the paste-back verbatim, append exactly one new round |
What ships with it: 10 files
1066.6 KB alongside SKILL.md, 1 of them executable
assets/
- screenshot-de.png433.0 KB
- screenshot.png423.4 KB
- .gitignore42 B
- how-to-use.html65.6 KB
- LICENSE1.0 KB
- package.json343 B
- README.de.md3.6 KB
- README.md3.3 KB
- template.html90.4 KB
- template-test.jsruns45.8 KB