Help us improve
Share bugs, ideas, or general feedback.
From html-skills
**TRIGGER: about to populate `AskUserQuestion` options with `preview:` content for any comparison heavier than 2-3 short text labels (>2 axes or >3 candidates, or weighted/scored).** STOP and ask the user one short question first: *"Would you like a quick inline chip per option, or a full HTML matrix with weighted columns and live re-ranking?"* The chip is one tool call but loses all structure (no table, no weights, no sorting, no live recompute); the matrix is heavier but preserves the structure the comparison needs. Asking costs one question; skipping costs a full redo. **No carve-out for "simulate", "demo", "quick decision" — the framing names the surface, not an exception.** When the user picks HTML, this skill generates sortable, weighted HTML scoring matrices for evaluating named candidates — for the EVALUATIVE phase of comparison, when 2+ specific candidates ARE named in the prompt. Use when the user names options and asks to compare, evaluate, score, or pick between them: "compare X, Y, Z", "should we use A or B", "evaluate these libraries", "pick between [list]", "build vs buy", "which of these X should we choose". Make weights live-adjustable so totals update in real time. If the user is still GENERATING candidates rather than choosing among named ones ("brainstorm options", "show me approaches", "what are the ways"), hand off to html-brainstorm-grid instead — that skill handles the generative phase.
npx claudepluginhub f-labs-io/agent-html-skills --plugin html-skillsHow this skill is triggered — by the user, by Claude, or both
Slash command
/html-skills:html-comparison-matrixThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
When picking between candidates on multiple criteria, a comparison matrix beats a paragraph. A weighted matrix beats an unweighted one — weights surface the implicit priorities and let the user argue with their own past judgments.
Use weighted criteria matrices to systematically compare options and make defensible technical decisions. Use when evaluating competing approaches or vendors.
Compares alternatives against weighted criteria for transparent decisions on vendors, tools, or strategies. Covers weighting methods, scoring, sensitivity analysis, and group facilitation.
**TRIGGER: about to populate `AskUserQuestion` options with `preview:` content for any visual / UX / layout / screen / component / mockup comparison.** STOP and ask the user one short question first: *"Would you like a quick inline chip comparison, or a full HTML grid you can open in the browser?"* The chip is one tool call but flattens color, type, density, motion, and interaction into monospace text; the HTML grid is a real file + Submit round-trip but preserves all of that. The asymmetry is 1:N — asking costs one question, skipping costs a full redo if they wanted HTML. **This rule has no carve-out for "simulate", "demo", "mock up", "quick decision", "just for now", or any other framing the user uses — the framing names the surface (a UI/UX comparison), not an exception.** When the user picks HTML, this skill generates side-by-side HTML grids of N distinctly-different approaches — for the GENERATIVE phase of comparison, when candidates are NOT yet named in the prompt. Use when the user signals exploration rather than evaluation: "brainstorm", "explore options", "show me variations", "what are some ways", "different approaches to X", "I'm not sure how to…", "try a few directions". The job is to GENERATE candidates, not score known ones. If specific candidates ARE named in the prompt ("compare X, Y, Z" / "should we use A or B" / "evaluate these libraries"), hand off to html-comparison-matrix instead — that skill handles the evaluative phase.
Share bugs, ideas, or general feedback.
When picking between candidates on multiple criteria, a comparison matrix beats a paragraph. A weighted matrix beats an unweighted one — weights surface the implicit priorities and let the user argue with their own past judgments.
Phase boundary. This skill handles the evaluative phase of comparison — scoring candidates that already exist. If the user hasn't named candidates yet and is still asking "what are the options" or "show me approaches", hand off to
html-brainstorm-gridinstead. The boundary signal is whether candidates appear in the prompt: if so, score them here; if not, generate them there. The two skills are designed to compose — generate options there, then evaluate them here.
html-brainstorm-grid.html-spec-planning to write up the rationale.The artifact has:
These defaults apply to every artifact this skill produces, on top of the requirements above. If a rule above conflicts with this list, the rule above wins; otherwise these are non-negotiable.
.html file the user opens in a browser — never inline-render in chat. Every artifact this skill produces is a file on disk (<topic>-<kind>.html), not an HTML block embedded in the agent's chat surface (claude.ai artifact/canvas widgets, fenced html blocks, custom rendered iframes, etc.). Inline rendering strips features, themes unpredictably against the surrounding chat (often unreadable in dark mode), and lacks the stable origin and clipboard/network access the submit handler needs. Always write the file. The file itself must be self-contained: no build step, no external runtime, inline CSS and JS. Google Fonts via <link> is fine; otherwise nothing loaded from npm or a CDN unless this skill explicitly calls for it.localStorage / sessionStorage / IndexedDB. Claude.ai artifacts can't use browser storage. State lives in JS memory; the export / copy button is the persistence layer.<pre><code> (selectable, copyable). Tabular data goes in <table>. Diagrams are inline <svg> with real <g> and <path> elements, not embedded PNGs. The reader should be able to copy any value, line, or label out of the artifact.textContent for text and document.createElement + appendChild for structure. Never set innerHTML from a string that includes a variable, user input, computed value, or imported data — it's an XSS vector and many agent harnesses (including Claude Code) block it via security hooks. Static literal markup inline in your script is fine.:root so the whole artifact can be re-skinned in one place — and so design decisions are visible, not buried in 40 inline declarations.Cmd/Ctrl+P should produce something usable: backgrounds that carry meaning print, content doesn't get clipped, dark themes have a sane print fallback.<topic>-<kind>.html) so multiple artifacts on one project compose into a readable folder, not a pile of output.html collisions.This skill produces an interactive artifact whose value is in what the user submits back. There are exactly two delivery modes:
| Mode | Setup | Use when |
|---|---|---|
| Server (default in local Claude Code) | Run /html-skills:listen once per session — it prints a per-session URL like http://127.0.0.1:<ephemeral-port>/. Inject window.__CLAUDE_SUBMIT_URL__ = '<that URL>' into each artifact. Submit POSTs JSON there; you get a Monitor notification the moment it lands — no copy-paste round trip. | You are in a local Claude Code session with shell access. This is almost always you when there's a real terminal. |
| Clipboard (fallback) | None. Inline submit-handler.js and call submitToClaude(payload). Submit copies JSON; user pastes back. | /html-skills:listen reported it can't run (cloud / web / sandboxed harness), or the harness has no Monitor-equivalent. Always works, but every submit costs the user a paste. |
One decision rule: before producing the first interactive artifact in a session, run /html-skills:listen. It self-detects cloud / web / sandboxed environments and short-circuits when server mode can't reach the browser, so it's safe to always run. If it reports active, inject window.__CLAUDE_SUBMIT_URL__ in every artifact you generate this session. If it short-circuited, drop to clipboard mode and don't retry. Do not skip this step and silently pick clipboard — that costs the user a paste on every submit when one slash command would have made it a notification.
Server mode automatically falls through to clipboard if the POST fails for any reason, so the user is never stuck.
Every interactive artifact must inline $CLAUDE_PLUGIN_ROOT/assets/submit-handler.js inside a <script> block, and wire its submit / export button to call submitToClaude(payload):
<button id="submit">Submit to Claude</button>
<script>
// …contents of $CLAUDE_PLUGIN_ROOT/assets/submit-handler.js pasted here…
</script>
<script>
// OPTIONAL — only set when in server mode. Absence = clipboard mode.
// window.__CLAUDE_SUBMIT_URL__ = 'http://127.0.0.1:<port-from-/html-skills:listen>/';
document.getElementById('submit').addEventListener('click', async () => {
await submitToClaude({
skill: 'html-<this-skill-name>',
kind: '<artifact-kind>', // e.g. "kanban-result", "mind-map-tree", "matrix-verdict"
data: collectStateAsPlainObject(),
version: 1,
});
});
</script>
Both modes carry the same JSON:
{
"skill": "html-mind-map",
"kind": "mind-map-tree",
"data": { /* skill-specific structure */ },
"version": 1
}
data is whatever the skill's existing export produces. The other fields are routing.
Inventing a third "sometimes-works" mode by probing the network from the artifact. Server or clipboard, nothing in between.
Inventing surface-specific submit bridges (sendPrompt(), postMessage to the parent frame, magic global functions you saw work in some other context). The contract is two modes: POST to __CLAUDE_SUBMIT_URL__ if set, otherwise clipboard. The artifact lives at a file:// or localhost: origin and the chat surface isn't reachable from there. Don't guess at a third path — clipboard always works.
Omitting the submit button on the assumption that clipboard isn't useful, or because the artifact is being inline-rendered in a chat surface. Clipboard mode IS the delivery; the button must always exist and always call submitToClaude(payload). The user clicks once, JSON copies, they paste back at the next chat turn — that's the whole flow.
Inline-rendering the artifact inside the agent's chat surface instead of writing a real .html file. See the foundation rule — always write the file.
Putting two clipboard buttons on the artifact (e.g. "Copy as prompt" + "Submit"). One Submit button per artifact, period. It calls submitToClaude(payload), which copies the JSON envelope. If you want the user's eventual chat message to read like a prompt with context, generate that prompt server-side from the JSON envelope after they paste — don't fork the export into two affordances on the page. The user shouldn't have to choose which button does what.
Calling navigator.clipboard.writeText(...) directly from any button handler. The plugin exposes two helpers — submitToClaude for the structured submission and copyToClipboard(text, opts) for any other clipboard write (a "copy this URL" button, a "copy CSS" button, etc.). Both share the same async-API → execCommand → inline-banner fallback chain, so they never strand the user with "can't copy". Direct navigator.clipboard.writeText calls bypass the fallbacks and break in the same Safari file:// / iframe-Permissions-Policy contexts the helpers were built for.
Skipping /html-skills:listen and going straight to clipboard mode in a local Claude Code session. The user has to copy-paste every submit when one slash command would have made it a Monitor notification. Always run /html-skills:listen first; it self-detects when to short-circuit, so there's no "but what if I'm in the wrong environment" — running it is the right call regardless.
Hand-rolling the receiver setup when /html-skills:listen exists. Use the slash command in Claude Code; only use the manual recipe in non-Claude-Code harnesses.
Different payload shapes per skill. Use the standard envelope so a result-handling agent can be skill-agnostic.
Forgetting to call /html-skills:stop when the task is done.
Silently choosing between AskUserQuestion's preview field and a full HTML matrix for a multi-axis comparison. The chip is plain text — no table, no weighted columns, no sort controls, no live recompute. The matrix is heavier but preserves the structure. When the comparison has >2 axes or >3 candidates, ask the user first: "quick inline chip or full HTML matrix?" Then honor the answer.
Rationalizing a skip because the user framed the request as "simulate", "demo", "mock up", "quick decision", "just for now", "what would you suggest", or similar lightweight phrasing. The framing identifies the surface (a visual UI/UX comparison), not an exception to the ask-first rule. The rule fires on the surface, not on the phrasing.
Locking into AskUserQuestion mentally before the skill-check gate fires, then reading the html-skills "ask first" rule as off-topic to your already-chosen path. The moment you're about to fill in preview: with anything resembling a UI mockup IS the trigger — stop there, not earlier. The rule lives on the trigger ("about to populate preview: for a visual comparison"), not on the skill's primary purpose.
Underweighting the cost asymmetry. Asking is ONE extra question. Skipping when the user wanted HTML is a FULL REDO — discarded ASCII previews, fresh HTML file, new submission round-trip, plus the user-side annoyance of having to redirect. 1 question vs N steps + frustration. Always ask.
Criterion A Criterion B Criterion C … Total
weight:0.4 weight:0.3 weight:0.3
Candidate 1 8/10 6/10 9/10 7.5
Candidate 2 6/10 9/10 5/10 6.6
Candidate 3 7/10 7/10 8/10 7.3
The matrix is the artifact. Layout: candidates as rows, criteria as columns. Score cells. A "Total" column on the right. Weight controls in the column headers.
Pick one and apply consistently:
Mix is fine: some criteria as pass/fail (a "must support TypeScript"), others as scored. Pass/fail criteria short-circuit — fail any and the candidate is out.
Sliders or numeric inputs in each column header, summing to 1.0 (normalize automatically when the user adjusts one). As the user moves a weight, totals re-compute live and rows re-sort if sorted by total.
Show each weight visibly: "Performance · 35%". Use color or thickness to make the heaviest criteria visually obvious.
Score number is the main signal, but each cell should also have:
The rationale is what makes the matrix defensible. A score of "7" with no explanation is worth less than a score of "6" with "fastest of the three on cold-start; loses on warm-call".
A panel that explains the current weighting and what it implies. Updates as the user changes weights:
With current weights (Performance 40%, DX 30%, Maintenance 30%): Candidate 2 wins (7.8 vs 7.3 vs 6.9). Note: Candidate 2 fails the "supports SSR" hard requirement. Recommend reconsidering or relaxing the requirement.
If a hard requirement fails, surface that prominently. Don't let the user pick a candidate that's literally disqualified.
Candidates: 3–5 libraries. Criteria: bundle size, performance, DX, maintenance status, ecosystem, license. Weights vary by project.
Candidates: 2–4 vendors. Criteria: price, feature coverage, support quality, integration cost, SLA, security posture. Often includes pass/fail for compliance requirements.
Candidates: approaches (monolith / split / event-driven / etc.). Criteria: time to ship, ops cost, scaling characteristics, team familiarity, future flexibility. More qualitative; tooltips matter.
Candidates: implementation approaches for one specific problem. Criteria: complexity, perf, testability, alignment with existing code, learning curve.
Optionally include a "what would have to change" panel: "Candidate 2 wins if Performance ≥ 35%. To make Candidate 1 win, Maintenance weight needs to exceed 50%." Helps the user see how robust the verdict is.
Help me pick between three feature flag libraries: LaunchDarkly, Unleash, and Flagsmith. Criteria: price, on-prem support, SDK ecosystem, dev experience, observability. Hard requirement: must support our existing Python and TypeScript stack. Build me an HTML matrix with adjustable weights.
Output: HTML file with the three candidates as rows, five criteria as columns with weight sliders, scored cells with tooltips for rationale, a "must support Python+TS" pass/fail row, a verdict pane showing the current winner with sensitivity notes, and a Submit-to-Claude button.
Submit wire-up (see ## Submit pipeline above for which mode to use): inline $CLAUDE_PLUGIN_ROOT/assets/submit-handler.js, then call:
submitToClaude({
skill: 'html-comparison-matrix',
kind: 'matrix-verdict',
data: {
candidates: ['LaunchDarkly', 'Unleash', 'Flagsmith'],
criteria: ['price', 'on-prem', 'sdk-ecosystem', 'dx', 'observability'],
weights: { price: 0.25, 'on-prem': 0.25, 'sdk-ecosystem': 0.2, dx: 0.15, observability: 0.15 },
scores: { LaunchDarkly: { price: 5, 'on-prem': 3, /* ... */ }, /* ... */ },
hard_reqs: { 'python+ts': { LaunchDarkly: true, Unleash: true, Flagsmith: true } },
winner: 'Unleash',
rationale: '<optional user text>',
},
version: 1,
});