← getloupe.org
Changelog
Every release, straight from the repo — current version 2.8.1, page generated 2026-10-21.
All notable changes to Loupe. Versions match the Chrome Web Store releases;
the manifest version is generated from package.json.
2.8.1 — 2026-10-21
Fixed
- Clicking a finding no longer collapses the list it came from. In the design-system lint audit, locating a finding on the page could flip the section back to its "needs a profile" setup card for a single frame and straight back again — long enough to close every group you had open, with the findings still sitting there. The profile binding is per-tab truth and no longer travels between tabs at all, and each audit section now owns its groups' open/closed state, so a blip like that can't reach them.
2.8.0 — 2026-10-15
Changed
- The annotated screenshot drops the box-model diagram. It's the element with its spacing bands and badges drawn in place — the separate box-model drawing that doubled the image height (a second reading of the same numbers) is gone.
- Picking a colour is context-aware now. With an element selected, a pick jumps to the Element tab and flashes its Fill section — the colour editor, where you're about to apply it. With nothing selected it still lands on Page's picked stack (sampling). Clearing a selection now also empties the Element tab, instead of leaving a ghost element.
Added
- Opacity in the colour editor. The picker grows an alpha slider (over a checkerboard, so transparency is visible) — drag it and the element commits
rgba(…); an element already semi-transparent opens at its real opacity. Works in the panel's Style editor and the on-page B editor.
Fixed
- The colour picker no longer samples Loupe's own UI. With the picker on, moving the cursor over the panel or dock kept showing the magnifier reading Loupe's own pixels; it now hides over any Loupe surface, and a click there does its normal job instead of picking.
2.7.1 — 2026-10-14
Changed
- Flex and Grid controls are icon buttons now. Direction, justify, align and wrap show a DevTools-style row of icons — the active value highlighted, each labelled on hover — instead of dropdowns, so you can see and pick the option at a glance.
2.7.0 — 2026-10-11
Added
Shift+M opens multi-screen. The keyboard shortcut for the three-screen view (plain M stays the single device preview). Also on the ModeBar's Screens button and the command palette.
- Drag to resize a screen. In the multi-screen view, grab a frame's right, bottom or corner grip and drag — the device width/height follows the cursor and the layout reflows live, so you can find the exact width a breakpoint breaks at without hunting through presets. Release snaps the row back to fit.
2.6.0 — 2026-10-10
Added
- Grid, editable. Select a grid container and the Style editor grows a Grid group: column and row track templates (type
1fr 2fr, repeat(3, 1fr), anything valid), auto-flow, justify, align and gap. A block element now offers Make grid beside Make flex. Every change records, undoes with ⌘Z, and exports like any other adjustment.
2.5.0 — 2026-10-10
Fixed
M exits responsive mode. Pressing M again now leaves the device preview — previously the only way out was the popover's Exit button. The background owns the session now, so M toggles it reliably from the page, dock or panel; the Exit button advertises the shortcut.
Added
- Edit across screen sizes (Pro). Unlock editing, open Screens, and up to three device sizes sit side by side. Edit at one — a padding tweak, a colour, a text change — and it mirrors live onto the others, so you can see at a glance whether the fix that works at 390 holds at 1280. Each screen has its own preset picker; the change list labels every edit with the width it was made at.
- Flexbox, editable. Select a flex container and the Style editor grows a Flex group: direction, justify, align, wrap and gap (with the token picker). A block element offers one quiet "Make flex" button. Every change records, undoes with
⌘Z, and exports like any other adjustment.
2.4.0 — 2026-10-08
Added
- Export a PR comment. Beside "Copy AI prompt" on the Nudges list: a reviewer-ready GitHub checklist — selectors as code, before → after inline, a checkbox per element so the comment doubles as the developer's punch list. Same phrasing engine as the AI prompt, so the two exports always describe the same review; none of the agent instructions a human reviewer doesn't need.
2.3.0 — 2026-10-07
Added
- Picked colours apply straight to the selection. Click a picked swatch and the formats block offers Text / Fill / Border — one click sets that property on the selected element, recorded and undoable like any other adjustment. The full loop is now: pick with
C, apply from Page or from inside any colour editor, check contrast from the selection — no clipboard, no retyping.
- The contrast checker loads from the selection. One click fills the Text and Background slots from the selected element's own text colour and its effective background — composited up the ancestor chain, the way the a11y audit computes it — so "does this element's text pass?" is answered without hand-assigning swatches. Backgrounds that cannot be resolved (images, gradients) are left alone rather than invented.
- Picked colours appear inside the colour editor. Sample a colour anywhere with the picker and it shows up as a tappable swatch in every colour popover — panel Style editor and the on-page editor alike — so "steal that colour from the page and put it on this element" is two clicks with no clipboard round-trip. Tapping a swatch applies it live and snaps the editor's thumbs to match.
2.2.0 — 2026-10-06
Added
- Edit colours in place, on the page. With editing unlocked, press
B (or the pill's palette button) and the colour popover opens anchored to the selected element — Text, Fill and Border targets, eyedropper, hex — no panel needed. Picks record as adjustments: they undo with ⌘Z, appear in the change list, and export with everything else.
- The colour picker is Loupe's own now. Clicking a swatch in the Style editor opens a house popover — saturation field, hue rail, eyedropper (pick straight from the page), hex readout — instead of the browser's native dialog, which could never match the panel. Every drag commits live and coalesces into one undo; Esc closes the popover and nothing else.
Changed
- Picking a colour opens the Page tab. The picked stack is pinned at the top of the Page index; the panel now flips there the moment a pick lands, so the copy is visible landing instead of silent.
Fixed
- Logging in unlocks Pro without a wait. After a login or purchase the padlock could sit locked for up to a minute: returning from the extensionpay tab re-checked at the exact moment the licence cache was freshest-stale, and nothing ever pushed. The background now broadcasts the flip to every surface the moment ExtPay reports it, and walking through any upgrade/login door schedules its own re-checks (30s–4min, alarm-based so the service worker's teardown can't kill them).
- The camera button captures what you see. The Element tab's two capture buttons had the icons backwards: the camera took the clean, annotation-free shot and the unassuming frame icon took the annotated one — pressing "screenshot" lost the spacing bands you were looking at. The camera is the annotated capture now (same as
T); the frame is the explicitly-labelled "element only" shot. Also fixed: on a locked, untouched page the padlock chip was baked into every annotated capture.
- The colour popover never opens clipped. Opened from the Border swatch it ran past the panel's right edge; from Background it sank below the fold — the panel's scroll containers clipped it both ways. It positions itself now: anchored to its trigger, measured, clamped into the viewport, flipping above when the bottom is tight. And
B works with the panel focused too, not just the page.
- The contrast ratio is always readable. The checker's result chip rendered the ratio in the tested pair itself — at 1.18:1 the number was as unreadable as the pairing it measured. The "Aa" preview still shows the pair (its legibility IS the result); the ratio now sits beside it in panel ink.
- Style editor fields share one left edge again. The label column was a pixel-count too narrow for "Background", which collided with its swatch and broke the alignment; and both selects' chevrons sat flush against their right border (Chrome's native arrow ignores padding) — the chevron is drawn by us now, on theme tokens, with a real inset.
- The tooltip's key chip is readable in light theme. The dock tooltip is deliberately dark over any page, but its key chip followed the global theme — in light mode that was dark-on-dark, an invisible letter. Chips now follow the surface they sit on.
2.1.0 — 2026-10-06
Added
- The crosshair is deterministic now. Snapping to a full-bleed element (dashboard shells, full-width sections) used to paint all four guide lines on the viewport edges — a crosshair that existed but was invisible, which read as "the crosshair never appears" on app-style pages. An element with no edge on screen now falls back to plain pointer lines, so something always marks the pointer. And the behaviour is visible, switchable state: the Rulers dock button opens a popover with "Ruler bars" and "Snap crosshair to elements" toggles.
- Hover a dock button to learn the tool. Dwelling on a tool upgrades its tooltip to a capability card — an animated vignette of the tool's own annotations, a one-line description, and the shortcuts that used to live only in the ? popover: the padlock, the ancestry walk, T-capture. Instant tooltips stay for quick identification; keyboard focus opens the card immediately; reduced motion shows the finished frame.
2.0.2 — 2026-10-05
Fixed
- Distance pills stay on screen. Measuring between full-width elements with a slim gap flipped the value pill past the left viewport edge — visible as a sliver of "px" hugging the screen edge. Pills now clamp into the viewport; one overlapping the measured boxes beats one you cannot see.
Changed
- Uninstalling opens a short feedback page. A plain page visit, no identifiers attached; answering is optional and composes an email in your own mail app. Documented in the privacy policy.
2.0.1 — 2026-10-04
Added
- The box-model diagram is editable, DevTools-style. Click any padding, margin or border value in the diagram and arrow it up or down (Shift = ×10). Each step writes through the same path as the Style rows — it records in the history, exports in the change list, one
⌘Z walks a burst back, and the first step opens the padlock by itself.
Changed
- Code is unwrapped by default. One declaration per line, horizontal scroll for the rest; the wrap toggle still persists either preference.
Fixed
- Esc typed in a panel field no longer runs the mode ladder. Cancelling a Style field's draft re-locked the padlock, and a second Esc killed the mode —
exit bypasses the typing guard by design, so the guard now lives in the panel's innermost rung: an Esc in a field leaves the field, nothing else.
- Arrow keys work with panel focus. Focus parks in the panel after any panel click and page clicks don't reclaim it, so arrows silently did nothing — they now drive the same walk/move the page's arrows do, standing aside only for controls that use arrows themselves (box-model cells, selects).
- Box-model cells show their armed state after a mouse click. The focus ring used
:focus-visible, which hides after pointer interaction — so the cell was armed for arrow keys with nothing on screen saying so. It now shows the ring and accent wash whenever focused: focus IS the armed state.
- The locked token note now offers a way in. It said the token picker was part of Loupe Pro and stopped there — an upsell with no door. It now carries the same three actions as every other locked surface: start the free trial, upgrade, and log in — the last one mattering most, since the person a doorless upsell fails hardest is a paying customer on a new browser.
2.0.0 — 2026-10-04
Changed
- Inspect and Nudge are one mode now, behind a padlock.
I opens it read-only: hover, pin with a click, walk the tree — the page cannot change. N unlocks it: everything Nudge could do — drag with snapping, resize, groups, duplicate/remove, text and style edits — and N again re-locks. The always-visible pill announces the state (🔒 Inspecting · N to edit when locked) and clicking it flips the padlock; editing CSS from the panel unlocks automatically, so there is no mode to hop. The entrance always states the padlock — I is always read-only, N always editing, no memory. One deliberate change in muscle memory: N no longer exits — it re-locks; I, Esc and the dock still exit. Adjustments still survive locking and leaving the mode; Clear all and reload still reset. A legacy shim keeps old nudge mode requests (another tab mid-update, saved automation) working as "the mode, unlocked".
- The panel grew a mode bar, and the pill knows when it's redundant. While the panel is open, the merged mode's controls — the padlock, ✕ Remove, undo/redo, Clear all — live in a slim bar under the panel's tabs, and the on-page pill collapses to a bare 🔒/🔓 state chip; close the panel and the full pill returns, because it is then the only control surface. The same stateful control never renders twice on screen.
- The first-run tour teaches the padlock — a new step between selecting and measuring: unlock with
N, drag, ⌘Z, Esc re-locks. The empty states teach it too: the Element tab's "Inspecting…" now says click to pin and N to edit, and Checks → Nudges starts from I then N.
Fixed
- Esc cancels a text edit instead of silently saving it. The window-level exit handler fired before the editing element's own keydown and committed the edit on its way out of the mode — Esc, the universal "forget that", was saving. It is now the first rung of one ordered Esc ladder: cancel the edit (reverting it) → leave ✕ Remove → release the pinned selection → re-lock the padlock → exit the mode. One layer per press, innermost first.
- Xray's pin releases on Esc. Xray pins a clicked element exactly as the read-only inspector does, but the old Esc guard only recognised the pin in one mode — in xray, Esc abandoned the pin and left. The ladder releases a pin wherever one is held.
- Locating an element from the panel no longer pins it while editing. Locate used the default pin-on-select, and a pin taken while the padlock was open froze hover with nothing on screen able to release it. While editing, locate selects without pinning; in every read-only surface it still pins, which is what lets the annotations survive the mouse trip.
- Leaving the mode locks the padlock. The unlock is a property of being in the mode, not ambient state — a stale unlock leaking into xray made its clicks refuse to pin.
- The pinned badge now reads
· pinned · Esc — "lock" belongs solely to the padlock.
1.2.0 — 2026-10-04
Added
- Nudge's screenshots capture the whole page, not the viewport. An adjustment below the fold was invisible in the handoff — which is often the half of the page the reviewer scrolled to in order to find the problem. Both the clean and the annotated shot are stitched from scrolled tiles, because the annotations live in a fixed overlay drawn in viewport coordinates: a single beyond-viewport render would draw them once, at the top, and lose every ghost below it. Pages past 20 screens are truncated and say so.
- Every scan can be cleared. The accessibility audit, DS lint, dark-mode diff and DS components each gain a Clear beside their run button — results outlive the page they were run on, so without it the panel kept reporting findings for a page you had navigated away from. Clearing the component scan takes its overlay off the page too, rather than leaving the panel and the page disagreeing about what was found.
- The audit run bar is laid out as two rows. Run and Clear sit together on one, with Clear at the far end — away from the button you must not hit by accident — and the error/warning counts on the line beneath. Inline, the counts pushed Clear onto a row of its own and the two buttons stopped reading as a pair.
- Picked colours can be cleared. They are a 12-slot MRU and cannot grow without bound, but they outlive the page they were sampled from, so a palette carried over from another site reads as this one's.
Fixed
- Figma compare now opens for org-licensed teams reliably. Its gate asked only "is the ACTIVE tab licensed", while every other gate asks "is the tab that SENT this licensed" and falls back to the active one. The same licence therefore opened the audits but could refuse Figma whenever the licensed page was not in the foreground — on the tier whose headline feature it is. The sender's tab is what Chrome sets and a page cannot forge, so this is more precise than the fallback, not weaker.
- Clearing a scan now releases the element it highlighted on the page. The panel went empty while the page still showed
span.flex-1 · locked · Esc over an element whose finding no longer existed anywhere. The clear only releases a selection that came from an issue row, so it cannot wipe one you made yourself in Inspect.
- A failed screenshot no longer reports "Nothing is nudged". The same message covered an empty change list and a capture that failed, which sent anyone debugging it to entirely the wrong place. The real reason is reported — the first one it surfaced was Chrome's capture rate limit, which the capture now backs off and retries rather than losing the screenshot to.
- The control pill no longer appears in full-page screenshots. Scrolling fires the overlay's own scroll listener, which repaints without the capture flag — so the pill was drawn into every tile, seven copies down the side of one image.
1.1.0 — 2026-10-03
Added
- The accessibility audit now sees inside shadow roots.
querySelectorAll stops at a shadow boundary, so a page built from web components was scanned as though it were nearly empty — a silent hole in a paid feature. Elements, text and labels inside open shadow roots are now audited, with aria-labelledby resolved against the root that declares the id rather than the document.
- Keyboard access is asserted, not assumed. The same self-check now tabs through the panel in each of its tabs and through the dock layered over a page, verifying that every rendered control is reachable, that each shows a focus indicator which actually differs from its resting state, and that the dock does not swallow the page's own tab order. A negative control — a button that suppresses its own focus style — runs first, so a check that could never fail would be caught before its passes are believed.
- Loupe is audited by its own audit.
pnpm a11y:self points the same rules at Loupe's panel (both surfaces, both themes) and its dock, and fails CI on any error. It is the same code the product runs, not a copy — a harness that reimplements the rules only proves it agrees with itself.
- Edit an element's style from the panel. Element → Style: padding, margin and gap; font family, size, line-height, weight and tracking; text and background colour; border width, style and colour; and corner radius — all applied to the live page. Numbers can be typed, arrowed (
Shift ×10) or dragged, which is the Figma habit and the reason to do this on the page rather than in an editor. Per-side rows are linked by default and unlink for a single side. Every change is a Nudge adjustment: one ⌘Z per interaction, listed in Checks → Nudges, exported with the rest of the change list, and gone on reload. The first edit turns Nudge on by itself.
- A restyle applies to the whole Shift+click group. Pick several cards, change the radius once, and all of them take it — in one undo step, the same property the group drag buys. "Give all of these the same corner" is the reason to have selected them in the first place.
- The style editor offers the page's own design tokens (Pro). Each value row gets a token picker listing the custom properties the page declares, semantic names first. Picking one applies
var(--space-6) rather than the 24px it happens to resolve to — so the page shows the real value and the exported change list names the token, with no mapping table in between. Editing values stays free; reading a team's vocabulary and naming it in the handoff is the design-system layer. Gated in the background broker, not the panel, and the raw pro_required sentinel never reaches the UI — it becomes a short note explaining what Pro adds.
⌘Z / ⇧⌘Z now work while the panel has focus. The page-side listener never hears a keypress made in the panel, and the panel is where the editing happens — the chord is relayed to the content script.
- Nudge has a remove mode — point at a block and click it away. Modelled on Safari's "Hide distracting items": toggle ✕ Remove in the Nudge pill, hover tints the block a click would take, and it fades out. The targeting is the whole feature — pointing at a paragraph gets you the card it sits in, not the paragraph, because what you want to remove is an item rather than the text inside one. ↑/↓ widen or narrow the target before you commit, which Safari doesn't offer, and the adjustment carries to the next element as an offset.
Esc leaves the sub-mode and stays in Nudge, so the adjustments made so far survive. Still preview-only: undo restores the element at its original index, Clear all brings everything back, and a reload resets the page.
- The Nudge pill is now visible from the moment the mode starts, reading "Drag to move" at rest. It used to appear only once something had been changed, which hid the undo, clear and remove controls behind having already done the thing they control.
- A themeable
Remove target colour joins the overlay style editor alongside the other roles.
Changed
- The Pro surfaces have tests for the first time. The QA report export, the Figma comparison's summary grouping, and the Figma URL parser were all shipped and sold with nothing covering them. 27 tests now do, including two privacy-policy commitments that had only ever been verified by eye: the exported report issues no network request when opened, and carries no credential or account email.
- Nudge's remove-target policy moved to
shared/dom/removeTarget.ts and is unit-tested for the first time. Measurement stays with the overlay; only the decision moved. That decision took three attempts against a live page and could previously only be checked by driving a browser — as plain numbers it is checked in milliseconds, and the tests encode the exact cases that broke it: a paragraph inside a card, a single-child <div class=card> wrapper, and a page-wide element with no peers.
- The overlay's chrome builders moved to
nudgeChrome.ts — the part that takes values and returns DOM, with no mode state to reason about.
Fixed
- The remove target could fall back to the page-wide wrapper it had just rejected. The last-resort branch returned the last element in the chain rather than the last one actually considered, so on a page where nothing looked like an item a click could have taken the whole layout. Found by the new tests, not in use.
- Closing the command palette gives focus back.
Esc dropped focus on <body>, so a keyboard user had to tab from the top of the panel again — the palette is meant to be the shortcut, not a detour. It now returns focus to whatever had it when the palette opened.
- Three contrast failures in Loupe's own UI, found by the above: the dim text token was 4.07:1 on dark and 3.45:1 on light against the 4.5:1 the product itself demands, and white on the brand violet was 4.35:1. All three now pass AA. The violet moved only as far as it had to; it is a background token everywhere it is used, so nothing that was readable got worse.
- Nudge's crosshair readout no longer contradicts the remove highlight. The rulers layer tracks whatever is under the pointer, so it sat beside a label reading
293 × 225 and announced 247 × 99 — the paragraph inside the card. It's suppressed while removing, along with the inspect engine's own hover highlight.
1.0.0 — 2026-10-02
The launch release. No functional change from 0.11.0 — the version number
is the point: Loupe is feature-complete for a public launch, and 1.0.0 is the
build the launch-day assets and outreach point at. What it carries, in one
place: inspect with a true box model and per-side spacing, element-to-element
measurement, layout grid and rulers, colour picker with WCAG contrast, xray and
find, Tailwind xray, device-emulated responsive preview, a page inventory
(fonts, colours, layout, assets, a11y), and Nudge — move, snap, resize,
group-move, duplicate and remove elements on the live page, then hand the whole
change list to a developer or a coding agent. Pro adds Figma design-vs-DOM
comparison with AI explanations, the a11y audit, breakpoint batch compare, and
the QA report export; teams get offline org licences bound to their own design
system profiles.
Refreshed for launch: store screenshots 01, 02, 03, 05 and 06 re-shot against
this build (01 now shows the DevTools-arrangement box model and working syntax
highlighting, neither of which existed when the old set was taken), and the
grid shot no longer buries the page under a half-opacity wash.
0.11.0 — 2026-10-02
Added
- Nudge can duplicate and remove elements.
⌘C/⌘V inserts a copy of the selected element right after the original and selects the copy, so the next drag or arrow key positions it; ⌘X/⌘⌫ takes an element out. It answers the layout question Nudge exists for — what this looks like with three cards instead of two, or without the promo block — and stays inside the same preview model: one undo per change, Clear all reverts the lot, and everything resets on reload. Copies never carry the source's id (a second #foo silently retargets the page's own CSS and scripts), get no ghost outline (there is no position they came from), and are marked + copy on the page and in Checks → Nudges. The AI handoff asks for a real second instance — a repeated component, another array entry — rather than copied markup, and ⌘C/⌘X stand aside whenever text is selected, where the browser's own copy is what you meant.
- Nudge moves a group. Shift+click adds elements to the selection (and Shift+click again removes them); a drag or an arrow key then moves the whole set by one shared delta, so internal spacing is preserved. The pill reads
N selected · M adjusted, every member carries its own ring so the set is visible rather than remembered, and because the history already stored one op per gesture as a list of entries, a single ⌘Z steps the entire group back. A plain press outside the group abandons it, so a stale selection can't move elements the user had stopped thinking about. The move is reported once for the whole set (Δ +33, -21px · 2 elements) rather than stamping the same figure on every member; per-element badges keep only what is genuinely per-element, a resize or a text edit.
- Nudge can resize, not just move. The selected element gets eight drag handles; the
Δ badge reports the new size, the Checks list shows it, and the AI handoff prompt asks for a real size change rather than an inline pin. Resizing reflows the page — unlike a move, which uses translate specifically because it doesn't — so from the first resize onward every ghost is drawn from a snapshot of where things sat beforehand, instead of being derived from current positions. Without that, a neighbour pushed by the reflow would have its ghost drawn somewhere it never was.
- Nudge snaps to the elements around it. Dragging now aligns to nearby elements' edges and centres when within 6px, with a guide drawn through whatever matched — so "line this up with the card next to it" is exact instead of eyeballed. Each axis resolves independently, so an element can snap horizontally while staying free vertically. Hold Alt to suspend snapping when the offset is deliberate. The threshold and the coarse arrow-key step (Shift+arrow, 10px) are both configurable.
Fixed
- A node that leaves the document and comes back keeps its id.
resolveNodeId prunes its forward entry whenever a node is detached, and registerNode then handed the same id back out without re-arming it — so the id could never resolve again. Nudge's remove-then-undo hit this (the panel's "Put this element back" answered ok and did nothing), and so would any framework that re-parents a subtree.
- Nudge badges no longer hide under the inspector's label. They were drawn at each element's top-left, which is where the inspect engine — always on under Nudge — puts its tag label, so the badge underneath was unreadable. They now hang off the top-right, the one corner the inspector leaves free (its size chip takes bottom-left).
- Shift+click along a selected card's edge joins the group instead of resizing it. The resize handles sit on top of the selected element's own edges and were checked before the selection modifier, so a Shift+click near an edge started a resize — the element the user meant to add never joined. Shift now outranks the handles, and on a handle it resolves to the element the handles belong to.
- Clicking in Nudge no longer freezes the hover highlight. Nudge runs the inspect engine as a passenger, and both register capture-phase click listeners — so the inspector's fired first and pinned the selection, turning a click that meant "pick this up to drag" into a lock that lasted the rest of the session, complete with a
· locked · Esc badge in a mode where Esc exits. The lock stays where it was asked for, in Inspect and Measure.
- Screenshots taken while nudging now show the move. The annotated capture hid Loupe's nudge overlay along with its other chrome, so the shot showed the element in its new position with no ghost and no
Δ badge — nothing indicating it had moved at all. The ghost and badge are kept; the interactive control pill is still excluded, reusing the repaint the existing nudge screenshot path already does, and restored afterwards so the capture no longer silently removes undo/redo/clear-all.
- An annotated capture no longer widens to the whole page. The nudge overlay is a full-viewport container, and counting it as an annotation bound expanded the crop to everything on screen. Full-bleed containers are ignored when sizing the crop.
0.10.0 — 2026-10-01
Added
- Clicking an element locks the inspection to it. The highlight used to follow the pointer unconditionally, so moving toward the panel or the dock walked the selection onto whatever you crossed on the way — and the buttons you were reaching for then acted on the wrong element. A click now pins it: the label reads
· locked · Esc, roaming leaves it alone, Esc unlocks and hover preview resumes, a second Esc exits the mode, and clicking another element locks onto that one instead.
Fixed
- Wrapping in the Code block is a toggle, remembered across sessions. Wrapped by default so nothing is ever hidden; switch it off to get one line per declaration with horizontal scrolling, which reads better when you are scanning a column of values. The button shows which mode is active rather than leaving you to infer it.
- The Code block wraps, indents, and has one copy button instead of two. Long selectors (utility-class stacks run past 60 characters) used to push the opening brace off-screen with no sign the line continued — it scrolled horizontally and you had to find that out. It wraps now, declarations are indented inside the braces, and the copy affordance moved off the header, where it sat beside the Computed / Declared / Tokens switcher wearing the same pill and read as a fourth view. It lives on the code block itself, over the thing it copies — and is a real
<button> with an accessible name, keyboard focus and copied/failed states, where it had been a bare <svg> with an unhandled clipboard promise.
- The test browser is pinned.
playwright was on a caret range that resolved to a Chromium revision which segfaults inside chrome.tabs.captureVisibleTab — one screenshot per run crashed the browser about half the time, two crashed it every time, and the back half of the smoke suite became unverifiable. Pinned to the exact revision that doesn't (dev tooling only; nothing ships to users). The suite goes from "usually incomplete" to 53/53 repeatedly.
- The box model's spec rows copy real CSS. They used to put
16 · 16 · 16 · 16 on the clipboard — not valid CSS, no property name, and a middot for a separator. They now copy a declaration collapsed the way you would write it by hand: padding: 16px;, margin: 12px 0;, border-width: 1px 2px 1px;, with bare 0 for zero. The tooltip shows exactly what you will get.
- Esc clears the "locked" badge immediately. Unlocking flipped the state but never repainted, and the overlay only repaints when the hovered element changes — so the badge kept reading
· locked · Esc after the lock was gone, surviving not just the keypress but every mouse movement within the same element. The one thing telling you the state said the opposite of the truth.
- Box-model region hover works in both directions. Moving inward — margin → border → padding → content — highlighted each region correctly, but moving back out left the inner one stuck: the rings nest, and
mouseenter does not re-fire for an ancestor the pointer is already inside, so leaving content never told padding it was active again. Leaving a region now hands the highlight back to its parent. Leaving the diagram entirely also clears the page's last-hovered region, which the panel falls back to and which could only otherwise be cleared by a page mousemove that never comes while the pointer is in the panel.
- The selector label no longer covers a spacing value.
isChip() only counted an obstacle wider than 16px and a single-digit value chip renders at ~14px, so every spacing number was invisible to the chip placer and the label sat on top of one. The label is also shorter on the page now — tag plus one qualifier (div.z-dropdown), since the identifying part of a selector is at the front and the tail of a utility stack made the label wider than the element it points at. The full selector is unchanged in the panel, in Copy and in exported reports. It is placed above the whole margin box too, rather than a few pixels above the border edge where the margin's own value sits.
- Spacing values stay inside their own box. A band too thin to hold its number displaces the chip — padding inward, margin outward. Both used to displace outward, which put the padding value in the margin band, stacked two chips on one spot, and left the label nowhere to go but on top of the element's content.
- Syntax highlighting in the Code block works for the first time. Shiki has been bundled and fully wired since 0.8.0 and had never once rendered: its oniguruma engine needs WebAssembly, MV3 extension pages run under
script-src 'self' with no wasm-unsafe-eval, every highlight threw, and the catch swallowed it — so the block just looked like plain text by design. Switched to shiki's JavaScript regex engine: no CSP relaxation to justify at review, and the wasm payload leaves the bundle. The fallback now warns instead of failing quietly.
- The Code block shows the whole CSS rule, selector and braces included, which is both what the Copy button has always produced and what the grammar needs for context — without the braces, some property names highlighted and others didn't.
- Computed hides inert browser defaults. A typical element listed
top/right/bottom/left: auto, position: static, z-index: auto, opacity: 1, flex: 0 1 auto and overflow: visible — nine lines out of twenty saying nothing. Gone, per property rather than by blanket rule: the old filter dropped any 0px, which silently hid top: 0.
- Declared no longer reports values that lost. It walked top-level rules only, so everything inside
@media was invisible — most of the interesting CSS on a responsive site — and matching rules overwrote each other in source order, so a weak rule later in the sheet beat the specific one that actually applies. Now it descends into @media (only while the condition holds), @supports and @layer, and resolves by !important, then specificity, then source order, with inline styles last.
- The Tokens empty state pointed at a tab that doesn't exist — "the DS tab", which became Checks in 0.8.9.
- The panel's icon buttons say what they do. Icon-only chips relied on the native
title, which waits about a second and renders unstyled OS chrome — so a row of three icons read as unlabelled. Panel chips now show an instant, themed tooltip on hover and on keyboard focus.
- Spacing figures line up in columns under top / right / bottom / left headers, instead of running together as
2 · 0 · 0 · 0 with the legend stranded in a corner. 120 · 8 · 8 · 8 and 2 · 0 · 0 · 0 used to start at different offsets — the kind of misalignment this tool exists to catch.
- A zero-width border no longer reports a style.
border computes to 0px solid rgb(128, 128, 128) even when there is no border, which showed a default colour nobody chose as though it were a design decision.
- Element dimensions round consistently — the header read
842.000 × 52, three decimals against zero.
- Every non-zero spacing value is labelled on the page. A band thinner than its own label used to be left unlabelled — which silently swallowed a plain
padding: 12px, one of the most common values in CSS. Chips that cannot fit inside their band are now placed just outside it, so they still point at the right strip without covering it.
Changed
- The box model follows the Chrome DevTools arrangement. Each ring names itself from inside its own band and carries its four values on its four sides — zeros included, because a gap where a number should be is not information — with the element's content dimensions at the centre. Rings are a uniform thickness: an earlier build sized them to the real values, which read well until the labels moved inside, where a ring sized to a 1px border could not hold its own text. Hovering any region — margin, border, padding or content — outlines that exact region of the element on the page, and pointing at a padding band on the page lights the matching ring in the panel. Below the diagram, the aligned top/right/bottom/left table stays for scanning and click-to-copy, and border style, radius and shadow are listed under it. The centre cell now reports the content box rather than the border box, which is what it has always been drawing.
- Copy the annotated image straight to the clipboard. A new action in the Element tab puts the whole thing — element, spacing annotations and box-model card — on the clipboard as a PNG, so it pastes into CleanShot's "Annotate from clipboard", Figma, Slack or a ticket without a detour through the downloads folder. Deliberately app-agnostic rather than wired to one screenshot tool: nothing to detect, nothing to break when it isn't installed, and it works the same on every platform.
- The annotated screenshot now carries the box model. The exported PNG composites the full spec card — nested margin / border / padding rings with all their values and the content dimensions — beneath the element and its on-page annotations, so the shot is a complete handoff instead of something you have to pair with a panel to read. It is drawn, not cropped from the panel: that way
T works with the panel closed, the output never depends on panel scroll position, and no panel chrome creeps into frame. Loupe's own "locked" badge is stripped from captures for the same reason.
- Screenshot an element with its measurements,
T. A new dock button and the T shortcut capture whatever you're pointing at and save a PNG with the spacing bands, their values, the selector label and the size badge baked in — while keeping Loupe's own dock and panel out of frame. The crop grows to fit the annotations, so the margin band and chips aren't sliced off. The Element tab now offers both captures: the component on its own, or annotated.
0.9.3 — 2026-09-23
Added
- First-run tour. New installs get a four-step coach card by the dock that advances by doing, not reading: press I → select an element → press D — then a summary of P / ? / Alt+Shift+K. Skippable at every step, shown once ever, and existing users never see it (any prior Loupe preferences mark the profile as veteran).
0.9.2 — 2026-09-23
Fixed
- Figma compare explains itself when things go stale. An expired or revoked Figma token used to silently show the pristine connect form — it now says so. Pressing Esc while "Anchor to element" was armed left the "Click an element…" hint stuck — it now resets. "Re-run diff" after the anchored element left the page did nothing — it now tells you to re-anchor. And reopening the panel mid-session no longer strands you: the frame URL comes back prefilled with a note that one Fetch re-arms anchoring.
0.9.1 — 2026-09-22
Fixed
- Measuring nested elements shows the four edge gaps (inner edge → container edge, arrows and values on each side) instead of "overlap" extents — which for a contained element were just its own size. Asking "how far is this dropdown from the right edge of its card" now gets a direct answer; partially overlapping elements keep the overlap reading.
0.9.0 — 2026-09-21
Fixed
- Xray no longer outlines Loupe's own UI, and — the bigger one — the Page inventory no longer records Loupe's injected styles as the page's design: with Xray active, the entire outlines group used to be Loupe's own overlay palette (Find highlights polluted it the same way). The inventory now disables Loupe's injected stylesheets for the duration of the scan — and the same guard protects the DS lint, dark-mode diff and accessibility-contrast audits, which sampled the same polluted styles.
- Layout-grid settings survive a browser restart. Colour, opacity, columns and margins lived only in session storage and silently reset; they now mirror to durable prefs like the dock position.
- The Element tab's "CSS" copy button confirms ("Copied"/"Failed") instead of giving no feedback at all.
- Tailwind Xray says so when a page has no utilities instead of entering silently with nothing painted — the mode looked dead on non-Tailwind sites.
- Find highlights follow your overlay-style colours (Settings → Overlay appearance) instead of staying hardcoded red/amber, and the match counter no longer counts elements a SPA rerender removed.
- The colour picker can no longer show stale colours: if the post-scroll recapture fails (rate limit, window blur), the old snapshot is dropped and the bubble stays hidden until a fresh capture lands — previously it confidently sampled the pre-scroll screenshot at the new position.
- Find also stops scanning the document twice per keystroke (the dock input and the store sync both triggered the same search).
0.8.9 — 2026-09-20
Fixed
- "Copy AI prompt" actually copies on the in-page panel. The panel iframe lacked the clipboard-write permission policy, so every copy button silently failed there (the native side panel was fine) — and the failure was swallowed, so the button just looked dead. The iframe now grants clipboard-write, a legacy fallback covers the rest, failures say "Copy failed", and success reads "Copied — paste into your agent". The button is also renamed from "AI fix prompt" to Copy AI prompt with a tooltip clarifying no API key is needed — it writes a prompt for YOUR agent, it doesn't call an API.
- The AI explainer now says why it isn't working. Without an API key the "Explain with Claude" feature was simply invisible on FixCards — it now shows "AI explanations need your Anthropic API key" with an Add-API-key button that opens the key form and scrolls it into view. Key-rejection errors say so plainly with the same shortcut, and the raw
pro_required sentinel now reads "AI explanations are part of Loupe Pro."
Changed
- Design-system profiles import in one pick, right from Checks. The Components, Lint and Dark-diff checks now offer "Import profile JSON" directly (importing also binds the profile to the current site), with a link to the authoring guide — no trip through Settings. Once a profile is bound, each check shows which profile is active with an Update button that takes a replacement JSON in one pick. The full manager (URL fetch, paste, multiple profiles, unbind/delete) stays in Settings.
0.8.8 — 2026-09-20
Added
- Edit text in Nudge mode. Double-click any element to rewrite its text in place (plain-text only — pasted formatting is stripped); Enter or clicking away commits, Esc cancels. Edited elements get a ✎ badge (moved + edited shows both), text changes ride the same ⌘Z undo/redo stack as moves, appear in the Checks list, and the Copy-AI-prompt handoff asks for the copy change alongside the layout changes. Backspace/Clear-all revert text too.
0.8.7 — 2026-09-19
Added
- Customizable overlay appearance. Settings → Overlay appearance opens an editor for how Loupe annotates the page: label size and weight, plus a colour per named role — Selection, Hover target, Measure anchor, Measurements, Crosshair, Guides, Nudge, Nudge ghost — each showing its default and a one-tap reset. Reset-all, Export (JSON to clipboard) and Import (paste or file, validated) included; every edit applies live on the page and persists across browser restarts. Semantic colours stay fixed on purpose: Figma severity bands and Tailwind Xray categories must read the same in every exported report.
- Ruler bars adapt to the page. On dark sites the bars flip to a light tint with dark ticks (the dark bars used to disappear); light sites keep the original look. Automatic, based on the page's own background.
Changed
- Ruler bars push the page out of their way. Entering Rulers insets the page by the bar thickness (margin on the root), DevTools-style, so the bars overlap nothing — and the tick scale now aligns exactly with page coordinates (it was previously drawn 18px offset from the content). Hiding the bars (
R again) or exiting restores the page precisely; position:fixed page elements can't be shifted by margins, so the bars stay translucent as the fallback for sticky headers.
- Measurement lines got the classic dimension-line look. Distance lines now end in double-headed arrows, and the value sits in a light pill with a cyan border and cyan text — legible on any page background — instead of a solid colour block. Sans-serif with tabular numerals.
0.8.6 — 2026-09-18
Added
- More devices. The responsive device list grows from 9 to 21 presets — recent and legacy iPhones (17 Pro, 15 Pro Max, 11/XR, 13 mini), common Androids (Galaxy A51/71, S8+, Z Fold folded), more tablets (iPad Air, iPad Pro 12.9", Surface Pro 7) and larger desktops (15" laptop, QHD) — grouped into Phones / Tablets / Laptops & desktops in the dropdown. Viewport values match Chrome DevTools' device list.
0.8.5 — 2026-09-18 (uploaded, superseded by 0.8.6 before publish)
Added
- Centered responsive preview.
M now opens the page in a centered, device-sized frame on a dark stage — the DevTools look — instead of emulating in place at the tab's top-left corner. No debugger attachment, so Chrome's "started debugging this browser" banner is gone from the default path. The popover's new Engine switch drops to the old in-place emulation ("Device") when you need real mobile UA, touch and DPR; sites that refuse to load in a frame get a notice with a one-click switch. The Fit-window button now only appears for the Device engine, where it's still the only cure for the corner anchor.
- Inspection tools work inside the centered preview. Rulers, Measure, Inspect, Nudge, grid and the rest now operate on the mobile layout in the stage frame itself — the dock's buttons drive overlays injected into the preview (new
scripting permission, scoped to Loupe's own frame). While the stage is up, top-frame overlays yield to it and reclaim the page when it closes.
- The Responsive card floats beside the preview and is draggable. In the centered engine it places itself next to the device frame instead of over it, and its header is a drag handle — park it anywhere; the spot is kept while you stay on the page.
0.8.4 — 2026-09-18
Changed
- Inspect, Measure and Rulers are separate modes again. The 0.8.3 unification put every annotation on screen at once — selector label, size badge, X/Y/W/H readout and distance chips all crowding the same corner on dense pages. Each mode now shows only its own annotations: Inspect (
I) hover-highlights and selects, Measure (D) does anchor-and-hover distances, Rulers (R) does bars, crosshair and the X/Y/W/H readout. Measure and Rulers get their dock buttons back.
R inside Rulers mode hides the bars — crosshair, guides and readout stay, so nothing covers the page's top-left corner; a third press exits.
Fixed
- Unpacked builds are now labeled "(dev)" in chrome://extensions. The store install and a local dist/ load otherwise show the same name, icon and (between releases) version, and running the store copy while iterating locally produced a day of ghost bug reports.
pnpm package builds the clean-named release; plain pnpm build marks itself dev.
- Annotations no longer stack on top of each other. Every chip goes through one shared placer: it takes the first free anchor, dodges chips already on screen and the ruler gutter, and walks downward when a tight cluster leaves no free anchor. Long selector labels — Tailwind arbitrary variants like
[&:has([role=checkbox])]:pr-0 — are truncated instead of banner-width, and the X/Y/W/H readout sits below the element.
- Ruler bars no longer hide the page underneath them. They now fade from a readable tint at the outer edge to nearly clear at the inner edge, with haloed ticks and numbers, instead of painting an opaque strip over content.
Changed
- Responsive mode reworked. The device controls move from a strip injected into the previewed page — which could never be wider than the phone it was previewing, and covered the site's own header — into a dock popover with a device picker, size fields, rotate, and a zoom control. The preview now scales to fit the window by default (Fit / 50% / 100% / 150%), so a 390px phone fills the space instead of sitting in a black corner; the page still computes at its real CSS width, so breakpoints stay exact. The dock auto-compacts when the previewed viewport is too narrow for it, keeping the responsive control reachable. The area beside the preview is now a neutral stage colour that follows your theme, and a Fit window to device button resizes the browser window around the preview to close the gap entirely (exiting restores your previous window bounds). Chrome always paints the emulated viewport from the top-left of the tab — an extension cannot centre it the way DevTools does — so the backdrop and the optional window fit are what make the empty space read as deliberate.
0.8.3 — 2026-09-17
Changed
- Inspect, Measure and Page Rulers are now one mode. Inspect gained the other two as behaviors: with an element selected, hovering any other element draws live pixel distances between them (the old Measure flow, minus the mode switch), and the rulers layer — bars + crosshair over empty space, edge-snap guides over elements — is on by default (
R hides it). Distance lines and labels draw in their own cyan so a measurement never blends into the selection it starts from. D and R still work (D jumps into Inspect; R toggles the layer from anywhere), ⌘K keeps "Measure distances" and "Toggle page rulers" searchable by name, and the dock slims from three buttons to one.
Added
- Nudge rides on Inspect — entering Nudge keeps the whole inspection instrument live: hover highlight, pinned selection, rulers, and live distances. Select a reference element, drag another, and the gap between them updates in real time while you drag.
- Nudge mode (
N, free) — move elements on the live page by dragging or with arrow keys (Shift = 10px) and compare against a ghost of the original position with a Δ badge. Full undo/redo (⌘Z/⇧⌘Z, arrow bursts count as one step), Backspace resets the selected element, and the on-page pill carries undo/redo/Clear-all. Leaving the mode keeps the moves so the page can be browsed as-adjusted. Adjustments are inline translate only — non-destructive and gone on reload.
- Checks → Nudges — every moved element listed with its Δ and selector, per-row locate/reset, Copy AI prompt (a change list asking for real layout fixes, not translate hacks), and Screenshots — two PNGs: the adjusted page clean, and the comparison view with ghosts and Δ badges — ready to hand to a developer or coding agent.
0.8.2 — 2026-09-16
Added
- System theme — Settings → Theme gains a third option that follows the OS colour scheme live (panel and dock both), alongside Dark and Light. The ⌘K theme toggle still flips explicitly between dark and light.
0.8.1 — 2026-09-16
Fixed
- "Move to Chrome's side panel" works on first click — the header button saved the preference but never opened the side panel or closed the in-page one, so nothing visibly happened until the panel was closed and reopened.
0.8.0 — 2026-09-13
Changed
- Complete visual redesign — one token system (warm plum dark / warm paper light, a single violet accent, semantic colour families, a real elevation ramp) drives the panel, dock, and overlays; every hover/press springs; the panel slides in and out with the dock moving in lockstep; light mode no longer flashes dark on open. Reduced-motion is respected everywhere.
- Three tabs instead of five —
Element · Page · Checks. The new Checks tab is one index for every audit: Tailwind Xray, DS Components, DS Lint, Dark-mode diff, Accessibility, and Figma compare (with breakpoints and QA export inside), free and Pro side by side. No feature was renamed or removed — only the containers changed.
- Page tab flattened — the landing screen is a collection index with live counts (Typography, Text colors, Backgrounds, Shadows, Gradients & outlines, Spacing, Borders & radii, Assets, Tech & meta); one click to any inventory, back restores your exact scroll position. Picked colors + the WCAG contrast checker are pinned at the top.
- One-row header — the marketing title is gone; in its place a live context line (the page's hostname, or the selected element's clickable ancestry breadcrumb, which replaces the old Document section). The mark / PRO badge opens a new Settings sheet holding theme, text size, the design-system profile import/binding, Tailwind brand prefixes, rating, and licence.
- Drag-resizable panel — grab the in-page panel's left edge (320–640px); the width persists and wide layouts get two-column indexes.
- The Figma viewport-mismatch banner now measures the page's real viewport (it used to read the panel's own ~380px window and was permanently on).
Added
- ⌘K command palette (panel) — every mode and panel action, searchable, with keyboard navigation. The palette input sits inside the shared typing guard, so searching "grid" filters commands instead of toggling the grid.
- Trial countdown badge — the dock and panel now show
TRIAL · 5 while a Pro trial is running (amber for the last two days, TRIAL ENDED after), clickable straight through to upgrade. Deliberately styled lesser than the gradient PRO pill so buying visibly changes something and the badge changing on day 8 reads as the trial ending, not a bug. Paid and org-licensed users keep the gradient PRO.
0.7.5 — 2026-09-12
Added
- Page inventories (Page tab) — three new sections: Fonts (every family in use with size/weight variants), Layout (unique padding, border, and border-radius values with visual swatches and use counts), and Assets (images, inline SVGs, favicons, CSS backgrounds — with open, download, and multi-select download). Every row copies its CSS; Locate toggles a highlight on the page, moves it between rows, and steps through a row's matches on re-activation — the whole card is the click target (active row keeps a blue ring), with the copy and locate chips on top.
- Global refresh in the panel header — re-pulls page info and re-runs every scan that currently has results (Tailwind, DS components, inventories, Figma diff). Replaces the decorative pin button.
- Rate-us stars — an always-available row under the panel header; one click opens the Chrome Web Store review page.
- PRO badge — paid and org-licensed users get an animated iridescent PRO pill on the dock (rotates on vertical docks) and in place of the panel header's logo square. Display-only; the Pro boundary stays in the background handlers. Trial users don't get it — it marks a purchase. The panel's Pro feature labels (Accessibility, Lint audit, Dark-mode diff) wear a small version of the same pill.
- Colors inventory (Page tab, Colors view) — page-wide computed colors CSS Pully-style, one segment at a time like the Layout view: text colors (hex, text-bearing elements only), background colors, box shadows, gradients, and outlines, each as swatch cards with use counts, copy, and locate. The eyedropper palette + WCAG contrast checker moved to a "Picked" segment alongside.
Added (interface)
- Light mode — a sun/moon toggle in the panel header switches the whole panel and the on-page dock between dark (default) and light themes; the choice persists. Under the hood every panel surface now resolves through theme tokens instead of hardcoded greys.
- Dramatic hide/show — hiding the dock slides it off toward the screen edge (with a little anticipation dip) and leaves a glowing brand-gradient orb; clicking the orb slides the dock back in with an overshoot. Honors reduced-motion preferences.
Changed
- A minimal dock — seven buttons dropped from the full dock, nothing lost: select parent/child (
↑/↓), pause (Space), and shutdown (Q) are keyboard-only (listed in the ? popover); element screenshot and copy-CSS live in the panel's Element tab; Figma diff lives in the panel's Design tab. DS Components and DS Lint buttons now appear only when a design-system profile is bound to the page's origin.
- The global open-panel command defaults to
Alt+Shift+P (was Alt+Shift+L), matching the in-page P shortcut. Chrome only applies suggested keys on fresh installs — existing installs keep whatever they have bound; check chrome://extensions/shortcuts to rebind.
- Pricing: Loupe Pro is now a one-time $20 purchase (was $8/month / $59/year / $129 lifetime) — buy once, use forever; the 7-day free trial stays. Team org licences are self-serve at getloupe.org ($249/year); the store-issued licences are signed with a new key added to
LICENSE_PUBLIC_KEYS in this release.
Fixed
- Fonts inventory no longer selects the whole page — every element inherits a computed font-family, so "715 uses" counted containers and Locate's first match was often
<body>. Font groups now only count elements with direct text, and the view lists unique font styles (family + size · weight · style · line-height cards, CSS Pully-style) clustered by family, under a family chip row that filters on click — instead of expandable family rows. Locate buttons across the panel also switched to a proper target icon.
- Anchor mode no longer disarms itself — after clicking "Anchor to element", the panel persisted a store snapshot carrying a stale
mode: 'idle', which synced back to the page and tore down the anchor inspector moments after it armed (timing-dependent; automation hit it every run, humans occasionally). The panel now mirrors the mode before writing.
- Design tab shows the diff summary again — the panel's hydrate poll never applied
figmaDiff/figmaSummary from GET_STATE (every other heavy result was wired), so severity groups and repeat offenders never rendered after a compare.
- Figma token errors are visible — a failed Save & Connect stored the error where only the connected view rendered it; the token form now shows the failure inline, names the scopes a scoped token needs (File content + Current user, read), and the scope-missing 403 gets a message saying how to fix it.
0.7.4 — 2026-09-12
Added
- Page Rulers crosshair — Rulers mode (
R) now tracks the cursor with a live crosshair and position tags on the ruler bars; hovering a real element snaps the crosshair to its edges and shows a floating X: Y: W: H: readout. Descends into same-origin iframes (common for embedded dashboards/tables) so it snaps to the real element inside rather than the iframe's own box. Shows nothing while hovering Loupe's own dock, instead of an unsnapped crosshair with nothing to measure. The ruler bars can be hidden from a popover behind the dock button, keeping just the crosshair; the choice persists across restarts.
Fixed
- Closing a mode's settings popover no longer exits the mode — the
X on the Grid and Rulers popovers used to call the same handler as turning the mode off entirely; now it only dismisses the popover, and it reopens the next time that mode is freshly activated.
0.7.3 — 2026-09-09
Added
? shows keyboard shortcuts — pressing ? now toggles the dock's shortcuts popover, same as clicking the help button.
Fixed
- Design tab Fetch always pulls a fresh tree — the explicit Fetch button now bypasses the 15-minute Figma cache, so the tweak-frame → refetch → recompare iteration loop never runs against stale data. The cache still serves batch compare and re-anchor paths.
- Figma diff labels transparent backgrounds honestly — a DOM element with no background compared against a filled Figma node now reads
background-color transparent → #… instead of inventing #000000 (alpha was dropped during parsing). Still flagged as drift — a missing fill is real — just no longer mislabeled.
- Viewport-mismatch warning only fires for page-sized frames — comparing window width to a component-sized Figma frame (a card, a button) is meaningless since the diff anchors to one element; the Design tab now keeps the warning for desktop pages (≥ 500px wide) and mobile pages (≥ 320px wide and ≥ 600px tall) and stays quiet for component frames.
0.7.2 — 2026-08-16
Changed
- Component inventory sees nested components — a slotted container (
tabs, dialog, a page-level layout div) no longer swallows everything rendered inside it into one entry. Slot values following the <root>-<part> convention still count as parts of their root; any other nested slot (button, card, select-trigger) now gets its own inventory entry with its own variant histogram. Part names outside the convention can be declared via the new optional components.partSlots profile field. Lookalike detection now also runs inside component subtrees: an unslotted native control is flagged unless its direct parent is slotted (DS wrapper internals stay exempt). The scan now only records rendered elements, so panel counts, overlay badge numbers, and visible boxes always agree — hidden triggers and collapsed menus no longer produce phantom findings.
- Org licences are sticky across profile auto-refresh — when the profile fetched from
updateUrl omits the license field, the refresh keeps the licence developers already imported. Teams can now host the profile publicly for token sync while distributing the licence privately once; a hosted profile that does include a licence still replaces the stored one (renewal rollout path). Documented in the authoring guide.
Internal
pnpm publish:store pre-flights versions before uploading: refuses when the zip's manifest version isn't greater than the store's published version (forgot the bump) or doesn't match package.json (stale zip — forgot pnpm package). --skip-version-check bypasses.
- Custom domain getloupe.org: manifest
homepage_url, privacy-policy and store-listing URLs moved off contactashpat.github.io (old URLs redirect; onboarding page at https://getloupe.org/welcome).
Added
- Profile authoring guide published at https://getloupe.org/docs/profiles/ (the repo's
docs/profile-authoring.md, which is not publicly linkable); the DS tab's Import popover now links to it.
0.7.1 — 2026-07-28
Fixed
- Typing in panel inputs no longer triggers shortcuts — entering "px-lg" into the Tailwind brand-prefix field (or any panel input: Figma URL/token, DS import…) closed the panel the moment "p" was typed, because the panel's P check deliberately sat above the typing guard. Fixed structurally: both keyboard surfaces are now thin handler maps over a single
dispatchShortcut() that enforces the typing guard per shortcut from the keymap's own flags — only non-printable keys (Esc) may bypass it, an invariant enforced by unit test. The panel also gained the DS Components (O) and DS Lint (L) shortcuts it was missing.
Changed
- Component scan honours
ignoreSelectors — the profile's ignore list (previously lint-only) now also skips subtrees in the component inventory, so consciously-accepted exceptions (e.g. elements marked with a data-ds-exception attribute the profile ignores) stop reappearing in every audit. Documented as a team convention in the authoring guide.
0.7.0 — 2026-07-26
Added
- DS Profiles for Teams: offline org licences. Profile schema v2 adds
origins[] (auto-bound on import), updateUrl, reportWebhook, and an embedded signed license blob. Licences are ECDSA-P256-signed JSON verified fully offline against public keys shipped in the extension (key list supports rotation); a valid licence covering the tab's bound profile unlocks every Pro gate (audits, Figma compare, AI, batch emulation) for the whole team — no individual accounts. 14-day renewal grace after expiry with a panel warning; the DS tab shows "Licensed to <org> · expires <date>". Issued with scripts/keygen-license.mjs + scripts/sign-license.mjs; the private key lives outside the repo. v1 profiles remain fully supported.
- Hosted-profile auto-refresh — profiles with an
updateUrl re-fetch daily and on browser start (new alarms permission); a fetched profile replaces the stored one only when it validates, keeps its id, and has a newer generatedAt — a broken deploy never bricks existing installs. Declared origins re-bind on refresh.
- "Send to team" reporting — when the bound profile declares a
reportWebhook, lint and dark-diff results gain an explicit send button that POSTs the findings to the team's own endpoint (page URL + findings + profile id + extension version; never tokens/credentials; https only; never automatic). Privacy policy updated accordingly.
- Team licensing surfaces — DS-tab "Get a team licence" link, README / authoring-guide / store-listing sections; commercial licensing is contact-based.
- "AI fix prompt" export — one click copies a self-contained markdown brief for a coding agent (Claude Code, Cursor, …) built from the audit findings: greppable class-string evidence, rendered-location selectors, per-rule fix guidance, replacement suggestions, and guard-rail constraints. Available on the component inventory (replace native controls with DS components), the lint audit, and the dark-mode diff.
- Numbered finding badges + annotated screenshot capture — component and lint overlay boxes now carry corner badges whose numbers match the prompt's
[n] markers (a documented contract between overlay and report), and a camera button beside the AI-prompt button downloads the annotated viewport as a PNG — paste the prompt, attach the shot, and a multimodal agent can cross-reference every finding visually. Visible viewport only.
- Selector generator ignores framework-generated ids (
radix-_r_N_, React useId, Headless UI…) — they regenerate per render and never appear in source, so selectors built on them were useless as evidence; class selectors are used instead.
Internal
pnpm publish:store — zero-dependency Chrome Web Store API publisher (scripts/webstore-publish.mjs): --init one-time OAuth setup (loopback flow, credentials in gitignored .webstore-credentials.json, WEBSTORE_* env vars override for CI), default run uploads loupe-<version>.zip and submits for review, --upload-only / --status / --zip flags.
0.6.0 — 2026-07-26
Added
- Panel text zoom — ZoomOut/ZoomIn buttons in the panel header (70%–160%, click the percentage to reset). Uses CSS
zoom so every panel surface scales and reflows; persisted across browser restarts, applies to both the native side panel and the injected iframe panel.
Fixed
- "Log in" is now offered at every Pro gate (a11y audit, DS lint, dark-mode diff), not just the Design tab's UpgradeCard — users with a paid licence or dashboard-granted free access on another install had no way to link their email from the DS/Page tabs.
- DS Components shortcut hint disambiguated — "Press O (the letter O)"; the monospace kbd chip read as the digit zero.
- Modes no longer resurrect after being stopped (e.g. Tailwind Xray restarting seconds after pressing
S). The panel's 1-second state poll wrote its snapshot through persisting store actions, so a response fetched just before a mode exit re-broadcast the stale mode to the page via the storage sync. Hydrate now writes through the store's raw setState — display-only, never persisted.
- Pro: dark-mode diff (DS tab) — snapshots every rendered element's colour channels, toggles the profile's
darkDiff theme axis on <html>, re-reads, and flags colours that are byte-identical across the toggle — hardcoded values the static lint can't see when consumer CSS indirects to them. Fully synchronous (snapshot → toggle → re-read → restore in one task) so page theme managers never observe an intermediate state; a no-transition style neutralises animated theme libraries; inherited channels report on the ancestor that set them, not every descendant; tokens whose light and dark values are identical by design are whitelisted. Severity: error when the static colour matches a primitive or nothing, warning otherwise; each issue suggests the nearest token.
- Design-token-aware Figma compare — with a profile bound, colour diff hints resolve to named design-system tokens (
bg-surface) instead of raw var(--…) names, flowing through FixCard chips, grouped summaries, QA reports, and AI prompts unchanged.
- DS-aware AI suggestions — the explain prompt now names the bound design system, instructs the model to prefer its semantic token utilities and avoid raw hex/deprecated aliases, and includes what the current DOM value already matches.
- QA report: dark-mode section — exported reports include the dark-diff table (with colour swatches) when a diff has been run; export uses the cached result only, so downloading a report never flashes the page theme.
0.5.0 — 2026-07-25
Added
- Pro: DS Lint audit — one-click page-wide lint against the bound design-system profile's rules: raw colour literals (classes + inline styles), primitive tokens without a dark-mode override, raw Tailwind spacing scale outside design-system components, the silently-dropped
[var(--density-…)] arbitrary form, hsl(var(--…)) wraps, deprecated aliases (suggestion names the replacement), and raw text sizes where the DS ships a type scale. Grouped results in the DS tab with click-to-locate; errors before warnings; 300-issue / 5,000-element caps.
- Pro: DS Lint overlay — new mode on
L painting red (error) / amber (warning) boxes with per-issue tooltips. The overlay requests the scan through the background, so the keyboard shortcut cannot bypass the licence gate; unlicensed use shows a trial hint toast.
- QA report: design-system section — when a profile is bound, exported reports gain a lint table (severity, issue, suggestion, evidence, selector) and the header names the profile and its error/warning counts.
- Profile authoring guide —
docs/profile-authoring.md: the released reference for design-system teams building their own profile JSON (schema, lint-rule semantics, generation, distribution, pre-release checklist).
Fixed
- DS tab no longer shows another tab's binding state. On tabs where Loupe wasn't started, the panel could display a different tab's profile meta and page origin (globally-synced store fields) while lint correctly failed with "no profile bound".
GET_STATE now always answers with the tab's own document URL and locally-derived profile meta, and the DS RPC handlers lazily load the origin binding on demand.
Internal
- Unit tests introduced (
pnpm test, Vitest 3 — v4 needs Vite 6): 49 tests covering profile schema validation, all seven lint rules, the token engine (reverse lookup, ranking, page-var fallback), and the Tailwind classifier. Standalone vitest.config.ts so the crx plugin stays out of the runner. Verification gate is now typecheck + test.
- Shared panel style atoms extracted to
src/panel/styles.ts (ghost/kbd/ input/primary/secondary), replacing per-tab duplicates.
0.4.0 — 2026-07-25
Added
- Design System Profiles (free): import a JSON profile describing your design system — tokens (CSS vars + utility classes + light/dark values), component fingerprints (slot attribute + variant attributes), theme axes, and lint rule config — and bind it to a page origin. Import from a URL, pasted JSON, or a file; profiles persist in
chrome.storage.local and never ride the session store sync. No profiles are bundled — a design system's tokens and fingerprints are distributed by its owning team, not shipped to every Web Store user.
scripts/build-profile.mjs — generic reference profile generator: merges a hand-authored base config (scripts/example-profile.base.json is the neutral starting point) with a token manifest; output lands in the gitignored profiles/ directory. Nothing design-system-specific is tracked in the repo.
- Element panel Tokens view (free): the Code section's toggle is now Computed ⇄ Declared ⇄ Tokens. Per property: declared
var() usage (exact), design-system utility classes (exact, the only route for class-only legacy aliases), and computed-color reverse lookup (ranked candidates, never claimed as certain). Flags raw values, primitives, and deprecated aliases with their replacement.
- DS Components mode (free): new mode on
O — inventories the page via the profile's slot attribute (component roots, part counts, variant histograms) and flags native button/input/select/textarea/[role=button] outside the design system as lookalikes. Teal boxes for components, red for lookalikes; lookalikes painted first under the 400-box overlay cap.
- DS panel tab: profile management (import/bind/delete, detect banner) plus the component inventory with locate-cycling and rescan.
Internal
- The rAF box/tooltip overlay machinery is extracted from Tailwind Xray into a reusable
boxOverlay factory (offscreen boxes now skip painting); Tailwind Xray behaviour is unchanged.
- New
readCssVarsDeep() walks @media/@layer/@supports and adoptedStyleSheets with theme-axis selector context (the Figma-compare var reader is untouched).
0.3.0 — 2026-07-25
Added
- Tailwind Xray (free): new mode on
S that highlights every element carrying raw Tailwind utility classes — category-colored overlay boxes, hover tooltip listing the matched classes, click-to-inspect. Detection is a heuristic classifier (variant/important/negative stripping, arbitrary values, curated utility families) with a configurable brand-prefix allowlist so design-system classes are never flagged.
- Tailwind panel tab: per-utility counts with locate-on-page cycling, brand-prefix editor with live rescan.
Changed
- Select Target removed: the dock button and
S shortcut duplicated Inspect (I); the slot now triggers Tailwind Xray.
Fixed
- Select child (↓) was a silent no-op. Parent/child navigation now walks the live DOM: ↑ remembers the child it left so ↓ returns to it, falling back to the first element child — ↑/↓ are a reversible pair.
- Side panel is now strictly per-tab: it no longer stays visible when switching tabs, and a panel closed via any path (including Chrome's own ✕) no longer reappears when returning to its tab.
Internal
- Lockfile migrated to pnpm 11 (v9 lockfile),
packageManager pinned, esbuild/sharp install scripts approved via pnpm-workspace.yaml.
- Privacy policy and store listing kit aligned with Web Store disclosures.
0.2.0 — 2026-07-19
Added
- Loupe Pro via ExtensionPay: subscriptions + lifetime plan, 7-day trial, background-enforced gating, QA report export (standalone HTML).
- Pro: accessibility audit — page scan for contrast, alt text, tap targets, form labels, control names, document language/title, heading order.
- Pro: breakpoint batch compare — re-runs the Figma diff across multiple emulated widths.
- Responsive mode:
chrome.debugger device emulation applied to the current tab in place (metrics, touch, UA + client hints), with an in-page toolbar; survives navigation while attached.
- Color picker upgrades: live magnifier bubble with pixel sampling, clipboard copy, palette, CSS-variable matches, contrast checker.
- Element screenshot and copy-as-CSS dock buttons.
- Dock UX: drag + snap to any screen edge, compact state, startup hint, shortcuts help.
Fixed
- Find-element text search.
- Service-worker registration killed by ExtPay validation (payment-page content script now runs at
document_start).
0.1.0 — 2026-07-12
Initial release: element inspector, xray outlines, layout grid, rulers,
element-to-element distance measuring, color picker, find element, side
panel / in-page panel surfaces, Figma design-vs-DOM comparison with optional
AI explanations.