Loupely Lens Changelog
Every release, documented. What shipped, what changed, and why it matters.
16 releases
Loupely Lens ships the Cadillac build: 6 new capture fields, mismatch detection, credit refunds, and bug fixes
What this is
The Cadillac build adds 6 structured fields to every Lens capture file: dom_structure, specificity_ladder, ancestor_scan, dangerous_properties, origin_intelligence, and session_diff. These aren't extras. They're what makes the developer briefing precise. Before this build, the capture file described what Lens found. Now it describes what to do about it — minimum specificity to win, ancestor constraints causing the gap above an element, properties that will make content invisible on certain backgrounds, and how the element's CSS has changed since the last time you clicked it.
Two separate Haiku calls now run on every diagnosis. One writes the founder diagnosis in normal human terms. The other writes the situation_summary for the developer in the capture file — technical language, named selectors, specificity values, origin types. One prompt can't satisfy both audiences. Two calls means neither output hedges for the other.
Mismatch detection runs before every diagnosis. When your description contains behavioral keywords — "won't expand," "doesn't work," "nothing happens" — and the CSS classifier found a visual problem, Lens flags the gap. The triage routes to developer handoff regardless of CSS confidence. The capture file still ships. The developer gets both the CSS picture and the flag.
The triage page is rebuilt as a 3-layer structure. Option 1 (try it yourself) appears only when the route is diy_override or diy_settings. Option 2 (developer or AI handoff) is always visible on every triage page, regardless of route, without any click to reveal. The feedback form sits collapsed behind a link. Every CSS override and settings instruction carries a permanent "In testing — verify before applying" badge.
If a diagnosis is wrong, the credit comes back. Submit the feedback form within 48 hours, select a reason, and Lens refunds 1 credit automatically. One refund per diagnosis. The feedback row writes to Supabase and fires a Resend notification on every submission — inside the window or out.
Three bugs closed. The Vision Capture timing race that left the popup showing idle instead of confirming is fixed with a 6-retry polling loop covering 1200ms. The MutationObserver false-positive that cancelled active picks on pages with widget DOM mutations is fixed with a node count threshold — 10 or more non-Loupely element nodes required to treat a mutation batch as navigation. The auth callback bug that cleared capture keys during token refresh is fixed with a preserve branch in the auth handler.
What it answers
- what's in the loupely lens capture file
- how do i know what specificity to use to override a css rule
- why does the css fix not work when i apply it
- what does loupely lens do when something looks wrong on my site
- what happens if the lens diagnosis is wrong
Why we built it
The capture file has always carried the right raw data. The Cadillac build is the layer that turns raw data into a briefing a developer can act on without asking follow-up questions. Session diff tells you whether the last change made anything better. Ancestor scan tells you which parent element is causing the gap. Specificity ladder tells you exactly what to write to win the cascade. The data was there. Now it's organized.
Improvements 15
- BuildDomStructure added to capture pipeline — tag, classes, children (up to 5, sensitive elements redacted), rendered text, and child count on every capture
- BuildSpecificityLadder added to capture pipeline — winner, competitors (up to 10 per property), and override_prescription with minimum specificity, whether !important is required, and confidence rating
- BuildAncestorScan added to capture pipeline — scans ancestor chain for layout constraints, overflow conditions, and spacing sources; 7 flags including LIKELY_SPACE_CAUSE, WIDTH_CONSTRAINT, OVERFLOW_HIDDEN, and FLEX_CONSTRAINT
- BuildOriginIntelligence added to capture pipeline — aggregates all origins in stylesheet_map with editability, fix strategy, and !important count per origin
- BuildSessionDiff added to capture pipeline — diffs current capture against most recent matching capture by tag + sorted classes + URL + ancestor depth; reports changed properties with FIXED/REGRESSED/CHANGED assessment and remaining ancestor issues
- User_context block added to capture file — records founder description, timestamp, behavioral keyword match, detected problem class, and mismatch flag before the diagnose call
- Mismatch detection added to popup.js — case-insensitive substring match against 20 behavioral keywords; fires class_mismatch = true and routes to developer_handoff when description implies behavior and CSS classifier found a visual problem class
- Situation_summary added as second Haiku call in lens-diagnose.ts — technical developer briefing generated from structured capture fields, not raw CSS data; non-blocking, ships as null if call fails
- Triage.rationale added to diagnosis response — 1-2 sentences explaining why the route was chosen; generated in the same call as triage route; non-blocking
- Prompt iteration deployed to Supabase — CONSTRAINT_PROPS expanded to include padding and margin properties; diagnosis quality gate-tested on 3 captures
- Triage page rebuilt as 3-layer structure — Option 1 (diy routes only), Option 2 (developer/AI handoff, always visible, never hidden), feedback form (collapsed by default behind inline link)
- Feedback table created in Supabase — 12 columns including session_id UNIQUE constraint, denormalized problem_class/triage_route/page_builder, refund_issued flag, and product column for future Core compatibility
- Lens-feedback Edge Function deployed — JWT validation, session ownership check, duplicate guard (409 before DB constraint), denormalization, 48-hour refund window check, credits.balance +1 increment, Resend notification on every submission
- Credit refund wired end-to-end — 1 credit, within 48 hours, one per session_id; annual plan users get refund_issued = false (no credits to refund), feedback row and Resend fire on the same terms
- FORCE_CREDIT_REFRESH sent to service worker after confirmed refund — popup credit balance updates without requiring a sign-out/sign-in cycle
Bug Fixes 4
- Bug 1 fixed: popup showed idle state instead of confirming after a capture — Vision Capture round trip (up to 1048ms) completed after initPopup read storage; fixed with 6-retry / 1200ms polling loop after getCreditStatus; confirmed 7/7 on second test day (v1.0.78)
- Bug 2 fixed: picker overlay disappeared after first click before second click was possible — MutationObserver was firing on page widget DOM mutations (8 nodes on fibermarketexchange.com) and treating them as AJAX navigation; fixed by replacing mutations.some() with a node count threshold of 10 or more non-Loupely element nodes; confirmed with full pick on previously-failing page (v1.0.80)
- Bug 3 fixed: auth callback was clearing capture keys during token refresh — LOUPELY_LENS_AUTH_CALLBACK read ll_capture_ready before clearing and preserved it when present (v1.0.70/1.0.75)
- Bug 4 fixed: picker_pending was cancelled within 1ms on some pick attempts — same root cause as Bug 2; resolved by the same MutationObserver node count threshold fix (v1.0.80)
wp-admin guard and null result guard shipped
What this is
2 session-breaking bugs were found in live testing and fixed in a single release. The first: if you clicked an element that was part of the WordPress admin bar, Lens ran a diagnosis on it, consumed a credit, and returned a result for an element that only exists for logged-in admins. The fix built a 3-layer guard: content.js flags is_wp_admin via closest('#wpadminbar'), popup.js checks the flag before entering the confirming state, and lens-diagnose.ts verifies server-side. The guard was verified live against span.ab-icon, an admin bar element that had been picked in testing without the user realising it.
The second: if the AI returned an empty result, the popup showed a blank screen and still decremented the credit. The null result guard fixed this. When claudeResult is null, the session row is flagged null_result: true, no credit is decremented, and the popup shows a specific message instead of a blank screen.
What it answers
- why did loupely lens diagnose my wordpress admin bar
- does loupely lens charge me if it diagnoses the wrong element
- what happens if loupely lens returns an empty diagnosis
- does loupely lens refund credits for a null result
- how does loupely lens detect the wordpress admin bar
Why we built it
A credit charged for a null result and a credit charged for an admin bar element are both the same broken promise. Both were caught in testing and fixed before any paying user could hit them.
Improvements 4
- Wp-admin guard added to content.js: is_wp_admin flagged via closest('#wpadminbar') and written to capture meta
- Wp-admin check added to popup.js: handleSuccessfulCapture checks is_wp_admin before showConfirmingState(), clears keys and shows idle if true
- Wp-admin guard added to lens-diagnose.ts: server-side guard uses DOM flag only (session_fields.is_wp_admin), stylesheet-origin branch removed to prevent false positives on front-end WordPress pages
- Null result guard added to lens-diagnose.ts and popup.js: empty claudeResult flags null_result: true on the session row, skips credit decrement, shows specific popup message
Bug Fixes 2
- Admin bar picks no longer consume a credit or run a diagnosis; popup returns to idle with a specific message
- Empty AI responses no longer consume a credit; null result guard catches the failure before it reaches the user
Ancestor traversal optimised: PERF-4 budget met on plugin-heavy sites
What this is
The ancestor traversal performance budget was exceeded in the previous release. The root cause was buildMatchedRuleMap: it ran a CSS selector match test against every rule in every stylesheet for every ancestor level, making the cost O(ancestors times total rules). On a 121-stylesheet fixture, that was about 96,000 match tests per pick, with buildMatchedRuleMap accounting for 93% of total ancestor time.
The fix restructured the function so per-rule preprocessing (selector split, pseudo-strip, origin classification) ran once per page and the result was reused across the full ancestor chain. A conservative pre-filter was added to fast-reject simple single-token rules before calling el.matches(). The output-equality contract was verified: the rule map fingerprint was identical before and after the change across 30 maps and 646 total entries.
The final performance sweep at 5 runs showed ancestor traversal at 135 to 163 milliseconds across all runs. Every run under budget, with 37 milliseconds of headroom at worst.
What it answers
- why is loupely lens slow on wordpress with many plugins
- how fast is loupely lens on a plugin heavy wordpress site
- does loupely lens work on sites with 100 plus stylesheets
- how did loupely lens fix its ancestor traversal performance
- what is the loupely lens performance budget for css capture
Why we built it
The target user has a plugin-heavy WordPress site. That is the exact profile that hits 100-plus stylesheets and would have felt the performance problem hardest. The fix was required before launch.
Improvements 4
- BuildPreparedRuleIndex added: builds a once-per-page prepared rule array (origin, source URL, sub-selectors pre-split and pseudo-stripped), reused across all ancestor levels
- Conservative pre-filter added to buildMatchedRuleMap: simple single-token rules (tag, .class, #id) that cannot match the element are skipped before el.matches()
- Output-equality contract verified: fingerprint 3984cee64d5440c6689b63532e509c86207c9f7afc2c1d8036c6bad7c9a56e66 identical before and after across 30 rule maps
- PERF-4 final sweep: 135-163ms across 5 runs on the 121-stylesheet stress fixture, all under the 200ms budget
Performance instrumentation and @container bug fix
What this is
Latency instrumentation was added to the sessions table: two permanent fields, server_latency_ms and image_sent, recorded on every session from this release forward. The first datapoint: Haiku averaged 5.6 seconds text-only, with about 2 seconds of overhead. Latency was dominated by Haiku, not by the extension pipeline.
The @container cap, which had been shipping in every version without ever working, was fixed. The original check used containerRule.type === 12, which was incorrect: container rules are post-freeze CSS rule types that return 0 in current Chrome, not 12. The check never matched, so the cap never fired. The fix used instanceof CSSContainerRule. Verified via a 3-point fixture test at 150, 200, and 250 container queries.
Performance instrumentation was built into the diagnostics page: last_capture_timings, a pass/fail panel, and per-sub-step timing. The PERF sweep on a 121-stylesheet stress fixture showed PERF-1 and PERF-3 passing, and PERF-4 (ancestor traversal) exceeding its budget at 192 to 234 milliseconds against a 200-millisecond budget.
What it answers
- how fast is loupely lens css diagnosis
- does loupely lens measure diagnosis latency
- what was wrong with the loupely lens container query cap
- how does loupely lens measure performance
- what is the loupely lens ancestor traversal speed
Why we built it
You can't fix what you don't measure. The latency instrumentation built the data moat. The performance sweep found the one budget that was failing.
Improvements 5
- Server_latency_ms (INTEGER NULL) and image_sent (BOOLEAN NULL) added to the shared sessions table as permanent moat fields
- Server_latency_ms (INTEGER NULL) and image_sent (BOOLEAN NULL) added to the shared sessions table as permanent moat fields
- OD-7 verified closed via 3-point fixture: 150 queries (cap absent), 200 queries (cap true), 250 queries (cap true)
- PERF tooling built: last_capture_timings written to storage, pass/fail diagnostics panel added
- PERF sweep run on 121-stylesheet stress fixture: PERF-1 pass (222-305ms), PERF-3 pass, PERF-4 exceeded (192-234ms at 25 levels, budget 200ms)
Bug Fixes 2
- @container cap fixed: instanceof CSSContainerRule replaces broken type === 12 check in getAccessibleSheets()
- PERF sweep run on 121-stylesheet stress fixture: PERF-1 pass (222-305ms), PERF-3 pass, PERF-4 exceeded (192-234ms at 25 levels, budget 200ms)
Shadow tests pass and glossary tooltips ship: Milestone C reached
What this is
19 shadow tests ran against the production build. 9 full passes, 7 partials needing better fixtures, 0 hard failures. No wrong routes, no crashes, no regressions. Shopify layout, Webflow overlap, Squarespace platform detection, Styled Components CSS-in-JS, flex-computed gap, popup close during diagnosis, session row fields: all passed cleanly. The 7 partials (dense DOM depth, wrong element picked, Tailwind losing rules, dark mode, adoptedStyleSheets, preference_breakage, variable chain truncation) needed fixture improvements but none were launch blockers.
Glossary tooltips shipped in the same build: 21 technical terms on the triage page with hover and tap definitions in normal human terms. Mobile-friendly tap interaction. Never in the diagnosis text itself. Optional teaching affordance, never forced.
What it answers
- has loupely lens been tested on real websites
- does loupely lens work on shopify and webflow
- what are the glossary tooltips in loupely lens
- does loupely lens pass shadow tests before launch
- what does loupely lens milestone c mean
Why we built it
The shadow tests were the gate between building the product and trusting it on real user sites. Milestone C meant the product worked against real-world diversity, not just controlled fixtures.
Improvements 3
- 19 shadow tests run against v1.0.43 production build across Elementor, Shopify, Webflow, WordPress mobile, Squarespace, Styled Components, Tailwind, dark mode, and adoptedStyleSheets
- Glossary tooltips shipped: 21 terms on the triage page, hover and tap, mobile-friendly, triage page only
- Icon consistency verified at all required sizes; OD-4 and OD-5 closed
