Documentation

Everything DesignLoop does, surface by surface — what each control is, what it costs you, and where the data goes. Every section carries a short silent recording of the thing it describes, so you can see the surface before you install it.

  • Version 0.1.0
  • Chrome 116+
  • On-device AI Chrome 138+
  • Updated 22 August 2026

Start here#

DesignLoop is a Chrome extension and a companion Figma plugin. It closes one loop: the live page comes into Figma as real layers, the fix happens in the browser on the real thing, and the fix goes back to code as an issue a developer — or a coding agent — can implement without a meeting.

There is no server, no account and no telemetry. On default settings nothing leaves your machine: the default model is Chrome's on-device Gemini Nano and the default transcription mode is none. Everything that does leave — GitHub, OpenRouter, OpenAI — leaves on a key you supply, to a service you configured, and only when you take an action that says so.

Fig. 1 — the whole loop in one take: page → Figma, fix in the browser, back to code.

The four things it does

Export to Figma
Pick an element, a page, or crawl a whole site. It rebuilds in Figma as frames, text, vectors, images and auto-layout — editable layers, not a screenshot. Export →
Edit the live page
A Figma-style inspector docked on the page itself, plus a chat you can describe changes to. Every change is diffed, undoable, and reverts cleanly on reload. Inspector →
Extract the design system
Scan the page for the system it already has — colours, type, spacing, radii, shadows, components — then clean it up, apply it back, and export it as CSS, Tailwind, W3C tokens or a Figma board. Design System →
File the issue
Speak the problem or hand over a whole editing session. The issue lands with screenshots, selectors, a style diff and the environment table — and can be handed straight to a coding agent. Issues →

The shortest useful path: install, open a page you own, click the toolbar icon, hit Element, click something, and type “tighter padding, darker text” into the side panel. That exercise touches the picker, the session, the model and the diff ledger in about forty seconds.


Install & set up#

Two installs — the extension and the Figma plugin — then as much or as little configuration as the features you actually want.

1 · The extension

From the Chrome Web Store, or unpacked from source. Unpacked is the development path and needs bun:

bun run install:all
bun run build
# chrome://extensions → Developer mode → Load unpacked → extension/dist

Load extension/dist, not extension/. Pin the icon to the toolbar — activation is a click on it, and there is no content script running before that click.

2 · The Figma plugin

Published as DesignLoop Import in the Figma Community, or loaded locally from source, which needs the Figma desktop app: Plugins → Development → Import plugin from manifest… and pick figma-plugin/manifest.json.

You only need the plugin for full-fidelity imports. The SVG and PNG copy formats paste into Figma — or anywhere else — with no plugin at all.

3 · Configure only what you need

Nothing below is required to export a page or to edit one with the on-device model. Each row is a feature you switch on by supplying its key.

To getYou supplyWhere it goes
Filing GitHub issues A fine-grained PAT + owner/repo api.github.com, from the service worker only
The Plus / Pro models An OpenRouter key openrouter.ai, on your account
Whisper-grade transcription An OpenAI key (or the OpenRouter one) api.openai.com / openrouter.ai
Voice notes at all One microphone grant Nowhere — audio is kept locally unless you tick attach
Renaming a font family Nothing Google Fonts, only when you rename one

The GitHub token

Scope a fine-grained PAT to the single repo you want issues in, with two permissions: Issues: read & write (to create the issue) and Contents: read & write (to upload screenshots — GitHub's REST API cannot attach files to an issue, so assets are committed to a designloop-assets branch and linked). Paste it with the repo in Settings and hit Test connection.

The microphone

Granted once, on the extension's own origin, from a page the extension opens for exactly that purpose — a background worker cannot raise that prompt itself. After that, voice works on every site with no further prompts.


How it fits together#

Five runtime surfaces, one session, and a hard rule about where secrets are allowed to live. Worth five minutes: most confusing behaviour in the product is a consequence of one of these.

The surfaces

The service worker
A stateless message router. It is the only surface that holds the GitHub or OpenRouter key and the only one that talks to those APIs. Chrome evicts it after about 30 seconds idle, which is why every long job keeps its state in storage rather than in memory.
The content script
Injected on a gesture — never on page load — into the tab you activated. It draws the toolbar, the picker, the dock and the note pins inside one shadow root, and it serialises the DOM. It runs inside the page's trust boundary, so it is never given a token.
The side panel
Chrome's side panel, docked beside the page: four tabs — Chat, Issues, Exports, Design System.
The offscreen document
Where the microphone and the canvas work happen, because a service worker can do neither. Created on demand, closed when recording ends.
Options
The settings page, plus the one-time microphone grant page.

The session

Everything you change on a page belongs to an iterate session: an ordered list of steps, each with the element it touched, the declarations it wrote, a before/after screenshot and a computed-style diff. Chat turns, dock edits, voice utterances and sweeps all land in the same list, which is why ⌘Z walks back a manual padding edit and an AI restyle identically, and why Create issue can describe a change in executable detail rather than in adjectives.

Sessions are keyed by page — origin plus pathname, so a query string or a hash is still the same view — and survive navigation, the worker dying, and the panel being closed. They do not survive a reload of the page itself: the edits are live DOM changes, so a refresh restores the site exactly as its server sent it.

Where things are stored

WhatWhereLifetime
Settings, keys, drafts, sessionschrome.storage.localUntil you delete them or uninstall
Screenshots, audio, export payloadsExtension-origin IndexedDBGarbage-collected once the draft that owns them is pushed or discarded
Active model, iterate modeTheir own top-level keysAtomic — every surface watches them, none of them race
Anything at allchrome.storage.syncNever. Nothing is synced.

The token boundary is the one rule with no exceptions. A content script shares a process with the page it runs on. A hostile page that gets code into it still finds no credentials, and everything it sends the worker is schema-validated before the worker acts on it.


The page toolbar#

Clicking the extension icon injects one floating glass bar into the page. It is the only thing DesignLoop puts on a site by default, it is draggable by its mark, and every control on it is a mode you can leave with Esc.

Fig. 2 — the bar, and the dock it opens. It follows the work; it never docks itself to the browser.
The mark
The drag handle. The bar stays where you leave it, per page.
Export ▾
A split button. The face runs the last export you chose; the caret opens Pick an element… and Whole page. The result lands in the Exports tab. Export →
Live
Hands-free editing. The record dot goes red, the mic stays hot, and each pause in speech becomes an utterance that queues and applies in order. Live mode →
Element
Arms the picker: hover to see each element tagged with its selector and size, click to aim the session at it and open the dock. Engaged controls wear an ink capsule, never a coloured tint.
+ Note
Arms the note gesture — click any element to start a thread on it. Notes →
Edited ‹ ›
The review cluster, which only exists once the session has changed something. Edited toggles to Original and back — a held before/after of the whole page — and the arrows are undo and redo.
− 100% +
DesignLoop's own zoom, stacked on top of the browser's, so you can pull back to judge a layout without the browser's zoom reflowing it. Bare + / / 0 drive it.
– ✕
Minimise collapses the bar to a 38px tile that keeps the session alive. Close ends the session and takes the overlay off the page.

Two other ways in

⌥⇧E arms the export picker and ⌥⇧I arms the issue picker without touching the toolbar; ⌥ double-click on any element starts an issue on it directly. Both shortcuts are rebindable at chrome://extensions/shortcuts.


The inspector dock#

A Figma-style properties panel, drawn on the page itself. It reads the pinned element's computed styles and hands every commit to the session — so a manual edit is a first-class step: live, diffed, undoable, and mirrored into the side panel's ledger alongside the AI's.

Fig. 3 — the layers tree is a way to move, not a picture of the DOM. Arrows resize: 1px a press, 10px with Shift.

What's in it

Header
The selector of the pinned element, a Select button that re-arms the picker, and the collapse arrow. The header is also the drag handle — the dock floats wherever you put it and stays there.
Layers
The tree around the selection, each row carrying its real pixel size. Hovering a row lights that element on the page; clicking one retargets the whole dock. This is how you reach a wrapper you can't click because something sits on top of it.
Notes
The notes threaded onto this element, and + Add. They ride into the issue you file. Notes →
Layout
Display, position, size, rotation, overflow — as editable fields, not a read-out.
Spacing
Padding as four T/R/B/L cells. A bare number means px.

The field grammar

Fields are invisible at rest — no ground, no border — so the dock reads as a list of values rather than a wall of inputs. Focus takes a violet border and halo. A value commits on Enter or on blur, and that commit is the undoable session step: the field flashes violet, the ledger in the side panel grows a line, and ⌘Z takes it back.

Structural edits

The dock does more than restyle. With an element selected:

ActionHowNotes
Resize 1px a press, 10px with Shift
Duplicate⌘DThe copy lands immediately after the original
DeleteDelete / BackspaceUndoable like any other step
Reorder / reparentDrag a row in the treePlaces the node before the row you drop on
Edit copyClick the text in placeCommits as a text step, diffed like a style one
Undo / redo⌘Z / ⇧⌘ZOne history across chat, voice and manual edits
Fig. 4 — a padding edit on the real site, and the style card that names the token each value resolves to.

Every change is a live DOM change on your copy of the page. Nothing is written to the site. A reload restores it exactly as the server sent it — which is why the point of the loop is to turn the session into an issue before you leave.


Chat#

The Chat tab is where a change gets described rather than drawn. Type what you want — “tighter padding, darker text” — and the model turns it into concrete declarations that are applied to the real page, screenshotted, and diffed.

Fig. 5 — a turn, its unified diff, and the receipt it folds down to once kept.

Three ways to start

New chat
An empty thread aimed at the whole page. The only door a change that isn't about one element can walk through — “collapse every grey into three” has no element to be about.
Pick an element
Arms the picker; the session is aimed at what you click and the dock opens on it.
Live
Hands-free: point and speak. Below →

Past sessions are listed under the launcher, grouped by page, freshest first, with a search box over their names and pages. Resuming one restores its thread; it does not re-apply its changes to a page that has since reloaded.

Ask, Auto, Plan

The mode decides how a change is allowed to land, and ⇧⇥ cycles it. It is a single shared setting, so the on-page toolbar and the side panel never disagree about which one is active.

Fig. 6 — one instruction, three modes. The tail of the thread is the only thing that differs.
ModeWhat happensReach for it when
Auto default Applies and keeps every change on the spot. No card, no signature — just the transcript and undo. You're exploring and want the page to keep up with you.
Ask Applies the change, then waits: each element shows Keep / Revert until you sign off. Deliberate review, or someone is watching.
Plan Works the change out and touches nothing. The proposal parks on an Apply / Discard card describing exactly what it would do. The page is precious, or you want to read the intent first.

Ask leaps and then lets you look. Plan lets you look before it leaps. Apply on a plan keeps the change outright — the plan review was the review.

Slash commands

A / at the head of an empty draft opens the command menu: every session action that otherwise lives on a button, reachable from the keyboard. The query matches names and aliases, so /clear and /start both find /new. The moment you type a space it's prose again — /clear the deck is a sentence.

Fig. 7 — the command menu. Commands that can't run right now still show, greyed, with the reason.
CommandAliasesWhat it does
/new/clear /startStart a new session, clearing the chat
/keepNew session, keeping the changes on the page
/liveGo hands-free — point and speak
/undoUndo the last change
/issueCreate a GitHub issue from this session
/discardEnd the session and revert everything

The composer

A box that grows with the draft rather than scrolling inside two rows, plus the model picker, an attach clip and a mic. Images can be pasted straight in with ⌘V or attached — they ride along as reference (“make it look like this”), which is the reason the cloud rungs can be pointed at a screenshot of somebody else's page. A very long paste becomes a reference chip instead of flooding the box, and oversized text attachments are skipped rather than blowing the prompt budget.

Raw property: value lines skip the model entirely. That is also the fallback when Gemini Nano isn't available on this machine.

Dictation

The mic in the composer dictates into the box. Chrome's recognizer supplies the live interim ticker either way; when a transcription key is set, a real STT model gets the last word, because the live recognizer's failure mode is a confident wrong sentence rather than an error — and acting on one costs an inference and an undo.

Live mode

Live keeps the mic hot. Each pause is an utterance; utterances land faster than a model answers, so they queue and drain in order, with the queue depth shown in the bar. You point at elements on the page and talk — the session is doing the typing.

Sweeps: the change with no element

Some instructions are about the page, not a node. Those run as a sweep: a scope pass that says out loud how many elements it reaches and names the first few, then clustered batches that land in order, with a progress rail so a long pass is never a frozen panel.

Fig. 8 — “collapse every grey into three”. The scope pass is named, not just counted, so you can tell it's wrong before it runs.

A sweep needs a rung that can hold the whole page at once, which is exactly the line the model ladder draws. On Nano the same instruction is refused with the reason rather than half-applied to one div.


The model ladder#

Three rungs. One runs on your machine and is free; two run in the cloud on your own key. Choosing a rung is the single decision in this product that changes where your page goes, so the picker says so on every row.

Fig. 9 — the picker. A rung with no key behind it is still selectable, and tells you exactly what would switch it on.
RungScopeRuns onDefault engine
◇ Nano default One element at a time On-device · private Chrome's built-in Gemini Nano
◈ Plus your key Every element, one pass Cloud · OpenRouter deepseek/deepseek-chat
✦ Pro your key Agentic design passes Cloud · OpenRouter anthropic/claude-sonnet-4.5

Nano is free, private and small: its context holds exactly one element at a time. That is a hard limit, not a soft one, and the picker says so rather than letting you discover it by asking for a whole-page restyle and getting one div back. It needs Chrome 138+ and a one-time model download of a few gigabytes.

Plus and Pro hold the whole page at once, which is what makes cross-cutting work possible: collapse eleven greys into three, snap every margin to an 8px grid, audit a page against its own design system. Both slugs are overridable in Settings — the whole point of bring-your-own-key is that you choose what you're paying for.

What actually leaves. On Nano, nothing: the page never leaves the machine. On Plus or Pro, the parts of the page you are working on — the element, its computed styles, the scoped candidates of a sweep, and any image you attached — are sent to OpenRouter under your key. A rung with no key is locked: still listed, still selectable, but the composer refuses to send.


Notes & annotation#

Not every piece of feedback is a change. + Note arms a gesture: click an element and a pin threads itself onto it. The thread is a conversation about that one element — every note on it, oldest first, each stamped — and it rides into the issue you file.

Fig. 10 — one element, many notes. Hovering a pin opens its thread; clicking one keeps it open.
Session pins
Cyan, hugging the element's top-right. These belong to the session in progress and ride into the issue when you commit it.
Filed pins
The full brand gradient, top-left. These are issues already filed on this page, read back out of storage — so you can see what's been reported before you report it again.
The badge
Counts the notes on that element. There is no global 1, 2, 3 numbering to keep straight.

Pins stay on the page whatever mode it's in — a note written while annotating has to still be there when you go back to editing, or the review it was written for never happens.

The annotator

Inside a draft in the Issues tab, Annotate opens the screenshot with a box and arrow tool. Drawings are baked into the image that is uploaded, so the annotation survives the trip to GitHub — where nobody has your extension.


The Design System tab#

Most pages already have a design system. They just don't know it: eleven greys that wanted to be three, a type scale with two accidents in it, five radii where the CSS says two. This tab reads that system out of the live page, lets you correct it, and then hands it back — to the page, to Figma, or to code.

Fig. 11 — the scan, with near-duplicate greys collapsing onto one.

Scanning

A scan walks the rendered page — computed styles, not the stylesheet — so it sees what actually shipped, including whatever a component library injected at runtime. It groups near-identical values into single tokens and counts how many elements use each one, because usage count is what separates a token from an accident. Every scan saves itself into a library keyed by page, so you can come back to a system without re-running it.

The board

GroupWhat lands in it
ColoursFills, text and border colours, clustered, each with its usage count
TypeThe size / weight / line-height / tracking combinations actually in use
FontsEvery family on the page, classified, with substitution suggestions
SpacingThe margin and padding steps, and how close they are to a grid
RadiiThe corner scale
ShadowsElevation steps, deduplicated
BordersWidths and colours
ComponentsRecurring recipes the scan recognised — buttons, badges, cards, inputs
Fig. 12 — the board, and the run ledger beside it: every operation applied, and revertible one at a time.

Editing the system

Recolour a token
Change a token's hex and every element resolving to it updates live on the page.
Rename a font family
Type a new family and the page re-renders in it. This is the one feature that touches Google Fonts — the stylesheet and files are fetched only when you rename a family.
Refine & complete
A plain-language box: “rename primary to brand · add hover + disabled states · name the greys”. Complete system runs the whole set of completions at once.
The run ledger
Every operation you applied, as a transcript, each individually revertible. Design-system work is exploratory; the ledger is how it stays reversible.

Apply to page

Snap this page to tokens rewrites every element's colours and corner radius to the nearest token, in place. It is a deterministic pass, not a model call, and it reverts cleanly. Its real use is as a preparation step: snap first, then export, and Figma receives a page that is already token-aligned instead of one carrying eleven greys into your file.

Export

ButtonProduces
Copy to FigmaA visual board — swatches, the type scale, buttons, a card — as an importable payload
Copy shadcn kitThe full shadcn/ui gallery skinned with these tokens, published as real Figma components with variant sets
CSSA plain CSS custom-properties stylesheet
TailwindA theme extension
W3C JSONA W3C Design Tokens file

Only tokens are exported. Detected components are code, not tokens, so the writers ignore them — they appear in the header comment for provenance. Naming is role-first: a token's semantic role wins, otherwise its deterministic name is kebab-cased, and collisions are numbered so the map is always one-to-one.


Export to Figma#

The serializer walks the live DOM and emits an intermediate design JSON: real frames, text, vectors, images, auto-layout, gradients, strokes, effects. Not a screenshot, and not a re-implementation of the HTML — a translation of what the browser actually rendered.

Fig. 13 — pick a section; it arrives in Figma as layers with their real names.

Three scopes

An element
Export → Pick an element…, or ⌥⇧E. Hover to tag, click to take.
A page
Export → Whole page. Off-screen content is scrolled into view first, because a viewport capture only ever sees the viewport.
A whole site
A crawl. Two passes, with you in the middle. Below →

Four ways out

Fig. 14 — the export record, its copy formats, and the viewport presets.
FormatFidelityNeeds the plugin
Figma pluginFull — frames, text, images, auto-layout, stylesYes
Figma plugin + skeletonFull, plus the derived loading state side by sideYes
SVGEditable layers, no pluginNo
PNGA flat image that pastes anywhereNo

The plugin formats are copied to the clipboard gzipped and base64'd, prefixed DLOOP1:. Larger captures — and every crawl — download as a .designloop file that the plugin accepts by drag-and-drop.

The skeleton pass

A skeleton is a page's loading state, and designers usually draw them by hand, guessing at line counts and bar heights. DesignLoop already holds the real tree, so it derives one: content becomes bones (text, images, SVG, form controls), containers are left exactly as they are. The card keeps its fill, its border, its 12px radius and its padding — only the words and the picture turn grey. The skeleton is pixel-aligned to the component by construction.

Multi-viewport capture

Ticking more than one viewport preset captures the page at each width, so a responsive layout arrives in Figma as the set of layouts it actually has. This is the one feature that attaches Chrome's debugger to your own tab — to call setDeviceMetricsOverride so the page reflows, and nothing else. It detaches immediately afterwards, including on error. Single-viewport export never attaches it.

Naming

Layer names come from the DOM: tag, classes, and the component name when a React or Vue dev build exposes one. On-device naming can be run over the tree to turn div.flex.items-center into something a designer can find in the layers panel.

Crawling a whole app

A crawl drives a same-origin app in its own background window — navigating page by page, injecting the content script, waiting for each page to settle, capturing it, and optionally probing it for modal and drawer states. It ends as one .designloop file: every page a board, every captured state its own frame.

Fig. 15 — discover, review, capture. Nothing is visited for capture until a person has looked at the list.

The review step in the middle is the point: pass one only collects routes, and nothing is captured until you have seen what the crawler thinks the app is. Anything whose label reads like a destructive action — /account/delete, remove, revoke — is unticked for you before you get there.

KnobDefault
Max pages captured40
Max link depth4
Max routes discovered200
Max states probed per page12
Per-page timeout40s
Interaction probing (modals, drawers)On
Route discovery by clicking in-app linksOn, up to 40 per page

A crawl survives the service worker being evicted: the frontier, the visited set and the capture list live in storage, rewritten after every step, and an alarm heartbeat resumes the loop from the cursor. A page the worker died inside is retried once, then recorded as a failure rather than retried forever.


The Figma plugin#

The consumer half of the contract. It takes the payload, rebuilds it as Figma nodes, and tells you the truth about everything it could not do.

Fig. 16 — the import report. Every substitution is listed and can be overridden, then re-imported.
  1. Plugins → DesignLoop Import.
  2. Paste the DLOOP1: payload with ⌘V, or drop a .designloop file onto the panel.
  3. Set the three options, then Import.
  4. Read the report — then fix any font substitution and re-import without re-exporting.
OptionDefaultWhat it changes
Use auto-layout where detectedOnFlex containers become auto-layout frames. Off gives pixel-exact absolute positioning everywhere.
Create Figma styles for colours & textOnEmits real colour and text styles and binds nodes to them, instead of loose fills.
Import scale10.25 – 4. Scales the whole import on the way in.

Fonts

Figma cannot embed webfonts. Families that aren't available fall back to Inter, and every substitution is listed in the report with a dropdown to pick a different one — including the licence-gated families Figma will never load. Change them, hit re-import, and the file is rebuilt with your choices.

What can't survive the trip

  • Reversed auto-layout and real 2D grids. Figma has neither, so flex-direction: *-reverse and grid layouts fall back to absolute positioning — which is pixel-exact anyway.
  • Closed shadow roots, cross-origin iframes, matrix3d transforms. These can't be read, so they become placeholder rectangles, each one listed in the report.
  • Cross-origin images without CORS headers. They can't be inlined unless you grant the optional all-sites permission; otherwise they are skipped and reported.

Issues & handoff#

The other half of the loop. A report is worth filing only if the person receiving it can act on it without asking a question, so DesignLoop files the write-up for you: the words, the picture, the selector, the environment — and, when the report came out of an editing session, the exact change you already made.

Fig. 17 — a draft: editable title and body, the screenshot with its annotator, labels, and the target repo.

Starting one

By voice
⌥ double-click any element (or ⌥⇧I then click). The element locks with a red outline, a screenshot is taken, and the mic starts. Speak, then Stop & review.
From a session
Issue in the composer, or /issue. The whole editing session becomes the report.
By typing
With transcription set to none, the draft opens with an empty description and you write it. The recording is still kept locally, so a draft can be transcribed after the fact.

Transcription

ModeWhat happensLeaves the machine
none default No transcript. Audio is kept locally; you write the description. Nothing
webspeech Chrome's recognizer, with a live transcript in the recording HUD. Prefers the on-device recognizer and refuses rather than reaching for Google's servers — unless you tick the cloud-fallback box. Nothing, unless you ticked the fallback
whisper A real STT model on your OpenAI or OpenRouter key. Best quality. The audio, to that provider

Structure with AI turns the raw transcript into a title, steps, expected and actual. Attach the audio file controls only whether the .webm is uploaded to GitHub — the recording is kept locally either way.

What lands in the issue

SectionContents
ReportThe transcript, or the AI-structured version of it
WherePage URL, element, XPath, an optimised CSS selector, the React/Vue component name when exposed, and the rect
ScreenshotsThe element crop with a red outline, plus a collapsible full viewport shot
Proposed changeSessions only — a before→after style-diff table, before/after screenshots, the chat transcript, and a machine-readable JSON block
NotesEvery note pin from the session, with the element each was anchored to
HTML contextSanitised outerHTML, capped at 4KB
EnvironmentViewport, DPR, browser, locale, scroll position, timestamp
ConsoleThe last 20 errors and warnings, collapsed
Fig. 18 — the issue a session produces. Near-executable intent, not “this looks off”.

HTML is sanitised in the page, before it is sent anywhere: input values redacted, emails masked, token-shaped strings stripped. The draft preview shows the exact payload before you push.

Drafts

Drafts accumulate — file ten while browsing, then Push all. The panel shows the current page's drafts by default; the toggle widens it to every page. Screenshots and audio are committed to the assets branch (a separate assets repo if you set one) and linked from the issue body, because GitHub's REST API cannot attach a file to an issue.

Handing it to a coding agent

DesignLoop cannot bring your personal Claude or ChatGPT subscription to a repository. Agents run as a GitHub App or workflow installed on the repo, billed to whoever installed it. All the extension does is emit the trigger that installed agent listens for.

AgentTriggerYou install
Claude Code@claude commentThe Claude GitHub App and a workflow using anthropics/claude-code-action
OpenAI Codex@codex commentOpenAI's Codex GitHub integration
Cursor@cursor commentCursor's GitHub integration
Label onlyAn agent-fix labelYour own workflow

Handle, delivery mode and labels are all editable — verify the mention syntax against each agent's own docs, since these change.

DeliveryBehaviour
comment recommendedA follow-up comment. Re-triggerable, keeps the body clean, and lets you push now and hand off later.
bodyThe mention is inlined at the top of the issue. Fires on issues: opened; can't be replayed.
assigneeAdds the agent's login. Caveat: GitHub answers 201 and silently drops logins it can't resolve, so a successful push does not prove the agent woke up.

Hand every new draft to the agent automatically is off by default — it would spend the repo owner's agent credits on every half-formed voice note. Left off, each draft carries a checkbox and an editable instruction box instead, and already-pushed issues grow a Hand to… button so you can decide after the fact, or retry a handoff that failed. If the issue files but the mention fails, the draft is marked pushed with a warning: the issue is never lost to a failed poke.

# .github/workflows/designloop.yml — the label-only path
on:
  issues:
    types: [labeled]
jobs:
  fix:
    if: github.event.label.name == 'agent-fix'
    runs-on: ubuntu-latest
    permissions: { contents: write, pull-requests: write, issues: write }
    steps:
      - uses: actions/checkout@v4
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

Settings reference#

Everything on the options page, with the value it ships with. Right-click the extension icon → Options, or the gear in the side panel.

Fig. 19 — settings. Keys are validated where you enter them, not on first use.

GitHub

SettingDefaultNotes
TokenemptyFine-grained PAT. Lives in the worker; never given to a content script.
Repoemptyowner/name
Labelsdesign-feedback, from-designloopApplied to every issue
Assets reposame repoSet one to keep binaries out of the product repo
Assets branchdesignloop-assetsWhere screenshots and audio are committed

Capture

SettingDefaultNotes
Include sanitised HTMLOn4KB cap, redacted in the page
Include console errorsOnLast 20 errors and warnings
Include full viewport shotOnCollapsed in the issue body
Inline images in exportsOnNeeds the all-sites permission for cross-origin images without CORS
Infer auto-layoutOnFlex containers become auto-layout frames on import

Voice & transcription

SettingDefaultNotes
Trigger⌥ double-clickThe gesture that starts an issue on an element
Max recording length120sHard stop
Auto-stop on silenceOn
Attach the audio to the issueOffAudio is kept locally either way
Transcriptionnonenone · webspeech · whisper
Speech languageen-USDeliberately not the browser's UI language — that says nothing about the language you dictate in
Allow cloud speech fallbackOffWith it off, dictation refuses rather than sending audio to Google
Transcribe composer dictation with a modelOn when a key is setChrome still supplies the interim ticker; it just no longer gets the last word
Transcription modelprovider defaultopenai/gpt-4o-transcribe
OpenAI keyemptychrome.storage.local, never synced
Structure with AIOffTurns the transcript into title / steps / expected / actual

Models

SettingDefaultNotes
OpenRouter keyemptyUnlocks Plus and Pro. Scrubbed out of anything a content script can see.
Plus enginedeepseek/deepseek-chatAny OpenRouter slug
Pro engineanthropic/claude-sonnet-4.5Any OpenRouter slug

Coding agent

SettingDefaultNotes
AgentnoneNothing is ever handed off until one is chosen
Instructiona sensible prefillCopied into every draft's instruction box
Hand off automatically on pushOffAgents cost the repo owner money

Keyboard reference#

Everything below works on the page, while a session is live, and never while the caret is in a field — the page's or the dock's.

Getting in and out

⌥⇧EArm the export picker
⌥⇧IArm the issue picker
⌥ double-clickStart an issue on that element
EscCancel whatever is armed

Editing

⌘Z / ⇧⌘ZUndo / redo (⌘Y also redoes)
⌘DDuplicate the selection
Delete / BackspaceRemove the selection
Resize by 1px — Shift for 10px

Reviewing

⇧⇥Cycle Ask → Auto → Plan
⌘↵Keep every pending change
⌘⌫Revert every pending change
PHold the original — release to see the edit again

View

+ / DesignLoop's own zoom, stacked on the browser's
0Reset zoom to 100%
1 2 3In token mode: copy as CSS · Tailwind · JSON

The composer

/Open the command menu (on an empty draft)
Send, or run the highlighted command
⌘VPaste an image in as reference

The two ⌥⇧ chords are Chrome commands and can be rebound at chrome://extensions/shortcuts.


Privacy & permissions#

There is no DesignLoop server. No analytics, no tracking, no account. Every request goes straight from your browser to a service you set up, with your key. The full policy is at /privacy; this is the engineering view of it.

What leaves the browser, and when

LeavesOnly whenTo
NothingDefault settings — Nano, transcription none
The issue, its screenshots and (optionally) its audioYou press PushGitHub, on your PAT
The element or page you're working onYou selected Plus or ProOpenRouter, on your key
Microphone audioTranscription is whisperOpenAI or OpenRouter, on your key
Microphone audiowebspeech and you ticked cloud fallbackGoogle, via Chrome's recognizer
A font stylesheet requestYou rename a font familyGoogle Fonts

Permissions, and why each exists

PermissionWhat it's for
activeTabAccess to the current tab only at the moment you invoke the extension. The narrowest possible page access, and the reason there is no all-sites content script.
scriptingInjecting the picker and the serializer on demand, into that one tab. Nothing is injected on page load.
tabsReading the active tab's URL so the panel can show the drafts belonging to the page on screen. No browsing history is read, stored or transmitted.
storageSettings, credentials and drafts, in chrome.storage.local. Nothing is written to sync.
unlimitedStorageScreenshots, recordings and export payloads exceed the 5MB quota, so they live in the extension's own IndexedDB. Lifting the quota is what stops your drafts being evicted mid-session.
offscreenA service worker can't call getUserMedia or use OffscreenCanvas. Created on demand, closed when recording ends.
sidePanelThe panel is the primary interface.
clipboardWritePutting the Figma payload on the clipboard — the handoff mechanism that avoids needing a relay server.
debuggerOne optional feature: multi-viewport export. Attached to your own tab only for the duration of that capture, solely to reflow the page at each width, and detached in a finally. No script is evaluated, no traffic intercepted, no console read.
alarmsThe heartbeat that resumes a crawl after the worker is evicted.
<all_urls> optionalRequested at runtime, never at install, always behind an explicit action — exporting from, or filing on, a site you chose. The cloud models and Whisper request their own narrower origins the same way.

Deleting things

Drafts and exports are deletable individually in the panel. Blobs are garbage-collected once the draft that owns them is pushed or discarded. Uninstalling the extension removes the lot. Anything already pushed to GitHub, OpenAI or OpenRouter is governed by those services and must be deleted there.


Limits & troubleshooting#

The honest list. Most of these are properties of the browser or of Figma rather than bugs, which is why they are documented rather than fixed.

Known limits

  • captureVisibleTab only sees the viewport and is rate-limited to roughly two per second, so captures are queued and off-screen elements are scrolled into view first.
  • Cross-origin images without CORS headers can't be inlined without the optional all-sites permission. They're skipped, and reported as warnings.
  • Closed shadow roots, cross-origin iframes and matrix3d transforms can't be serialised; they become placeholder rectangles, each one listed in the import report.
  • Figma has no reversed auto-layout and no true 2D grid, so those fall back to absolute positioning.
  • On private repos, embedded screenshots render for collaborators inside GitHub's UI, but raw links won't work for logged-out viewers. Use a separate public assets repo if that matters.
  • Page edits are live DOM changes. A reload discards them — file the issue first.

When something doesn't work

The toolbar doesn't appear.

Nothing is injected until you click the icon, and Chrome refuses injection on its own pages (chrome://, the Web Store, other extensions' pages). Try a normal site.

Chat says Gemini Nano isn't available.

It needs Chrome 138 or newer, a few gigabytes free, and a one-time model download that Chrome schedules itself. Raw property: value lines still work meanwhile, and so do Plus and Pro if you have a key.

“Collapse every grey” only changed one element.

You're on Nano, whose context holds one element at a time. Sweeps need Plus or Pro.

Copy for Figma did nothing.

Chrome refuses a clipboard write while the panel doesn't hold focus, and picking an element necessarily hands focus to the page. Click back into the panel; the copy retries. For very large captures, download the .designloop file instead and drop it on the plugin.

The import looks right but the type is wrong.

Figma can't embed webfonts. Read the substitution list in the report, pick real families in the dropdowns, and re-import — no re-export needed.

Dictation refuses on a particular site.

Some sites block speech recognition with a Permissions-Policy header; you'll get a toast when they do. The recording is still kept, so the draft can be transcribed afterwards.

The issue filed, but the agent never woke up.

With assignee delivery, GitHub answers 201 and silently drops logins it can't resolve. Switch that draft to comment delivery and use Hand to… to retry.

A crawl stopped partway.

It resumes itself: the frontier lives in storage and an alarm restarts the loop. A page that killed the worker twice is recorded as a failure and skipped, so the crawl finishes rather than looping on it.


Glossary#

Session
The ordered list of steps that make up everything you've changed on a page — chat turns, voice utterances, dock edits and sweeps, in one undo history.
Step
One committed change: an element, the declarations written to it, a before/after screenshot and a computed-style diff.
Mode
Ask, Auto or Plan — how a change is allowed to land. ⇧⇥ cycles it.
Rung
One level of the model ladder: Nano, Plus or Pro.
Sweep
A change that has no single element — scoped across the page, clustered, and applied in batches.
The dock
The floating inspector drawn on the page itself.
Ledger
The pending-changes log in Ask mode: a unified diff per element, with Keep and Revert.
Board
The Design System tab's visual read-out of a scan, and the Figma artboard it exports as.
Skeleton
The loading state derived from an export: content becomes bones, containers stay.
Draft
An issue that hasn't been pushed yet.
Handoff
The trigger — a comment, a label or an assignee — that wakes the coding agent installed on the repo.
.designloop
The exported design JSON, gzipped. Also arrives on the clipboard base64'd behind a DLOOP1: prefix.

Something missing, or something here that's wrong? Mail clasicwebtools@gmail.com — this page is maintained alongside the extension, and a gap in it is a bug in it.