Solo design review
One reviewer iterating fast. Drop the script tag with your data-workspace onto any page, leave comments while iterating, copy the prompt into your LLM. Comments follow you across machines and deploys.
A drop-in script that turns any web page into a commentable surface. Comments anchor to the element itself, so they survive a redeploy. Press the shortcut and sign in to try it on this very page — this site runs on its own demo workspace.
he default workflow for design feedback is to take a screenshot, mark it up in chat, paste a link in a ticket. The screenshot is frozen the second it's taken. The page keeps changing. Three days later the comment points at “the third card from the left.” The third card from the left no longer exists.
Vizu collapses three places (chat, design, code) into one. The comment lives on the same DOM element it was about, identified by a fingerprint that survives edits. The reviewer's intent and the reviewer's address travel together.
See the same page, two timestamps, in the hero above.
Every comment records six signals about the element it's attached to. When the page re-renders, Vizu walks each rung in order, falling through to fuzzier matches only when the cleaner ones miss.
data-vizu-key/exact, host-controlled. Always wins.#id/exact, per HTML uniqueness.Class signature/classList Jaccard match within tag, when unambiguous.CSS selector path/full hierarchy from root.Ancestor chain/3 levels of {tag, nth-of-type}, tolerant to wrapper inserts.Text snippet/fuzzy Levenshtein match. Last resort.A comment on the third “Buy” button is still on the third “Buy” button after a redesign. Across our drift test corpus, 96% of comments re-anchor through wrapper inserts, class renames, text edits, and sibling shuffles. When an element is truly deleted, the comment orphans cleanly instead of jumping to a similar element nearby. The drifted ones wear a visible badge so you know to glance before acting.
Add data-vizu-key=“…” to elements you anticipate reviewing repeatedly. Comments anchored to tagged elements resolve at 100% accuracy — every other rung is a fallback for elements you didn't tag.
type ElementFingerprint = {
selector: string
parentSelector: string
tagName: string
textSnippet: string // first 80 chars
siblingIndex: number
attributes: {
id?: string
classList?: string[]
role?: string
ariaLabel?: string
dataKey?: string // data-vizu-key
}
}Vizu ships no backend, no prompt builder, no persistence by default. It emits 15 typed events; you wire whatever side effects make sense for your application.
POST comments to your API on comment:added. Tie identity to your session on user:changed. Register a Copy-as-prompt action on the React side. The library knows nothing about how you store or use the data it collects.
comment:addedVisitor saved a new commentcomment:removedVisitor deleted onecomments:clearedBulk wipe via clearAll()comments:setHydrated programmaticallycomments:loadedStorage adapter resolvedelement:selectedVisitor clicked an elementelement:deselectedPopover closedsidebar:opened / :closedDrawer toggleaction:invokedPill action clickeduser:changedsetUser() calledenabled / disabledLifecyclemounted / unmountedUI mount lifecycleEvery instance connects to a cloud workspace. Members sign in once; comments sync across pages, machines, and deploys. Storage is invoked in the background; the UI never blocks on it.
new Vizu({ workspace: 'my-team' }). Comments are private to it.list_comments.One reviewer iterating fast. Drop the script tag with your data-workspace onto any page, leave comments while iterating, copy the prompt into your LLM. Comments follow you across machines and deploys.
Multiple reviewers on the same page. Invite your team to the workspace; everyone signs in once and comments land in one shared thread — with @-mentions, replies, and attachments.
Reviewer copies the prompt. LLM rewrites the page. The redeploy keeps the same DOM structure where possible; fingerprints re-anchor unresolved comments. Next round starts where the last one ended.
useVizuAction({
id: 'copy-prompt',
label: 'Copy as prompt',
onClick: (ctx) =>
ctx.copyToClipboard(buildPrompt(ctx)),
});The iteration loop above keeps a human in the middle. You copy the prompt, paste into Claude. MCP lifts that step out. Your agent reads comments directly through three read-only tools, same workspace, same data.
list_workspacesget_workspacelist_commentsOwner-scoped, OAuth-gated through Clerk. Writes stay behind the host-token popup. Set it up per workspace in your dashboard.
// claude_desktop_config.json
{ "mcpServers": { "vizu": { "url": "https://vizu.unhingged.com/api/mcp" } } }Same shape works in Cursor. Continue.dev uses the streamable-http entry in ~/.continue/config.yaml.
new Vizu(options)workspace is required. See VizuOptions below.VizuOptions = { workspace, apiUrl, autoSignIn,
pageVersion, shortcut, accent,
startEnabled, ignoreSelectors, actions }workspace is your workspace slug from the dashboard — comments persist there and members sign in to see them. Shortcut defaults to 'mod+shift+e'.enable()disable()toggle()isEnabled(): booleandestroy()getComments(): VizuComment[]setComments(comments, opts?)opts.persist = false skips the storage adapter, for hydrating from your backend on load.clearAll(): Promise<void>setUser(user | null)getUser(): VizuUser | null{ id?, name, avatarUrl?, email? }.addAction({ id, label, onClick, variant?, title? })onClick receives an ActionContext with comments, pageHtml, user, copyToClipboard, toast.removeAction(id)invokeAction(id)getActions(): VizuAction[]on(event, handler): () => voidoff(event, handler)snapshotHtml(): stringjumpToFingerprint(fp)@unhingged/vizu-react<VizuProvider options={...}>useVizu(): VizuuseComments(): VizuComment[]useVizuUser(): [user, setUser]useVizuEvent(event, handler)useVizuAction({...})any HTML page · zero build step
Available · v0.0.1<!-- exposes window.__vizu -->
<script
src="https://unpkg.com/@unhingged/vizu-core/dist/vizu.min.js"
data-workspace="your-workspace"
data-shortcut="mod+shift+e"
data-start-enabled="true">
</script>@unhingged/vizu-react · hooks + provider
Available · v0.0.1$ bun add @unhingged/vizu-core @unhingged/vizu-react# or $ npm install @unhingged/vizu-core @unhingged/vizu-react# or $ pnpm add @unhingged/vizu-core @unhingged/vizu-react// app/layout.tsx
'use client'
import { VizuProvider } from '@unhingged/vizu-react'
<VizuProvider options={{ workspace: 'your-workspace' }}>
{children}
</VizuProvider>@unhingged/vizu-vue · useVizu composable
Coming · v0.1$ bun add @unhingged/vizu-core @unhingged/vizu-vue@unhingged/vizu-angular · VizuService
Coming · v0.1$ bun add @unhingged/vizu-core @unhingged/vizu-angularNeed a different framework? Vizu's core is framework-agnostic. Wrap it for Svelte, Solid, Qwik, or anything in ~30 lines.