craft-analyze
Post-cycle analysis — QA, UX, Creative, and Style audits using MCP browser tools.
How a dial physically lives on the user's page: the handles it owns, the four operations that drive it, and the rules that keep measurements honest and teardown total. `dial-inline.md` calls these operations by name; this file is the only place their payloads are defined.
> /plugin marketplace add drobins25/craft > /plugin install craft@craft
How it fires
How this command gets triggered: by you, by Claude, or both.
/dial-injectContext preview
What this command does when you run it.
How a dial physically lives on the user's page: the handles it owns, the four operations that drive it, and the rules that keep measurements honest and teardown total. `dial-inline.md` calls these operations by name; this file is the only place their payloads are defined.
How a dial physically lives on the user's page: the handles it owns, the four operations that drive it, and the rules that keep measurements honest and teardown total. `dial-inline.md` calls these operations by name; this file is the only place their payloads are defined.
Everything dial injects hangs off exactly three namespaced handles:
1. **`<style id="craft-dial-style">`** - appended to `document.head`. Holds ALL candidate CSS for the session. 2. **`<div id="craft-dial-panel">`** - appended to `document.body` as its **last child**. The fixed control panel. 3. **`document.documentElement.dataset.craftDial`** - the active position, as a lowercase letter (`"a"`, `"b"`, ...). Set on `<html>`, never on any element inside the app.
Any OTHER real node a candidate injects (an approach position's clickable button, badge, or bar - pseudo-elements can't be interactive) MUST carry the attribute **`data-craft-dial-injected`**. The marker is what makes CLEAR total by construction, regardless of injection technique. An unmarked append is a contract violation - it survives teardown and haunts the page.
**React-safe placement rule:** head, body-last-child, and the `<html>` attribute are all outside any framework root, so a re-render cannot strip them. An **in-tree append is forbidden** - React (and every virtual-DOM framework) reconciles only its own root's children, and anything foreign inside that root is subject to removal on the next render. This placement is why the panel survives.
Every candidate rule is keyed off the `<html>` attribute, inside the single style element:
html[data-craft-dial="a"] .filter-row { gap: 32px; }
html[data-craft-dial="b"] .filter-row { gap: 24px; }
html[data-craft-dial="c"] .filter-row { gap: 22px; }Toggling a position sets ONE attribute and mutates nothing else. No inline styles on app elements, no class additions, no per-toggle style rewrites - the whole session's candidates coexist in the one stylesheet and the attribute picks the live one.
For **approach** positions that add an element: prefer `::before`/`::after` content under the same `html[data-craft-dial="x"]` key - pseudo-elements are removed with the stylesheet for free. When the candidate needs a real, interactive node (a clickable button), create it with `data-craft-dial-injected` set, so CLEAR's bulk selector removes it. Never an unmarked append.
Each is one `evaluate_script` payload. `dial-inline.md` refers to them by name.
Read the surface before and during the session. Returns a small JSON-ish object - never a full accessibility tree:
(() => {
const els = [...document.querySelectorAll('<selector>')]
.filter(el => el.getClientRects().length > 0 &&
getComputedStyle(el).visibility !== 'hidden');
if (!els.length) return { found: 0 };
const el = els[0];
const cs = getComputedStyle(el);
const r = el.getBoundingClientRect();
return {
found: els.length,
selector: '<selector>',
value: cs.getPropertyValue('<property>'),
rect: { x: r.x, y: r.y, w: r.width, h: r.height }
};
})()**Visibility rule (non-negotiable):** every measured or targeted element must pass `el.getClientRects().length > 0 && getComputedStyle(el).visibility !== 'hidden'`. Responsive pages carry BOTH their desktop and mobile faces in the DOM; the hidden face has zero rects and lies about the layout. A bare `querySelector` with no visibility filter is the default mistake - it reports numbers from an element nobody can see.
One payload creates all three handles. Candidate CSS and panel markup are built by the flow; the placement is fixed:
(() => {
const style = document.createElement('style');
style.id = 'craft-dial-style';
style.textContent = `<candidate CSS rules>`;
document.head.appendChild(style);
const panel = document.createElement('div');
panel.id = 'craft-dial-panel';
panel.innerHTML = `<panel markup>`;
document.body.appendChild(panel);
document.documentElement.dataset.craftDial = 'a';
})()Re-injection (a new position mid-session, a refresh recovery) REPLACES the style element's `textContent` and the panel's `innerHTML` through the same ids - it never appends a second style or panel. Positions are never removed once injected; the letter set only grows.
document.documentElement.dataset.craftDial = '<letter>';
That is the entire operation. The panel's click handlers do the same thing from inside the page; a chat-driven toggle does it via `evaluate_script`.
**Re-measure rule (non-negotiable):** after every toggle, any number reported to the user comes from a fresh MEASURE - a live `getBoundingClientRect()` / `getComputedStyle` read - never from the CSS value that was injected. An injected value that lost a specificity fight, hit a `!important`, or missed its selector reports as "applied" unless it is read back. The readout's whole job is being the receipt that the CSS actually landed.
The canonical clear, verbatim. This exact snippet - same ids, same order - is what every flow runs; a paraphrase breaks the coverage grep that keeps the call sites aligned:
document.getElementById('craft-dial-style')?.remove();
document.getElementById('craft-dial-panel')?.remove();
document.querySelectorAll('[data-craft-dial-injected]').forEach(el => el.remove());
delete document.documentElement.dataset.craftDial;CLEAR is **idempotent on a page that was never dialed**: optional-chained removal no-ops on absent nodes, the bulk selector matches nothing, and `delete` on an absent dataset key is a silent no-op. Run it unconditionally - before a dial starts (a stale session may have left residue) and on every exit path including abandonment.
**Visual register: flat, grey, unmistakably scaffolding.** Dev chrome, never design - anything that looks like design risks bein
Stop Vibing. Start Crafting. A Claude Code plugin that acts as an intelligent harness for your development workflow: your codebase is read-only by default, every change passes through a Write Gate as planned and approved work, and craft tracks your project's
Repo: drobins25/craft
Post-cycle analysis — QA, UX, Creative, and Style audits using MCP browser tools.
Consult a craft agent. Routes your question to the best mind in the workshop - not a menu, a recommendation.
Agent crystallization command. Studies a tool, role, or person and produces a portable 9-section agent that inhabits the domain - with beliefs, scar tissue,…
Complete a cycle. Triggers reflection if pending learnings, then archives.
Design a cycle — create new cycles with planned stories, detail existing planning cycles, or quick-sketch a roadmap. Detects planning docs in .craft/planning/…