Lazyweb Deep Design Research
Evidence-backed design research that reads the user's current screen, names its frictions, forms 2-4 genuinely divergent redesign bets, and renders a visual-first HTML report where the recommended prototype sits side by side with the control.
People learn by seeing. Every claim in the report is carried by a large, legible visual; nothing important hides behind a click. Chrome stays quiet: no chip clutter, no legend tables, no explanatory paragraphs next to the proof.
CRITICAL: Output Behavior
This skill produces FILES, not a plan. Regardless of whether you are in plan mode or not, ALWAYS:
- Write the HTML report to
.lazyweb/deep-design-research/{topic}-{date}/report.html - Embed Lazyweb references directly with their returned
imageUrl/image_url; save only current-state and web-captured screenshots under.lazyweb/deep-design-research/{topic}-{date}/references/ - Do NOT create
report.mdor any other Markdown report artifact - Do NOT write research content into a plan file
- Publish a shareable link (see "Publish a Shareable Link" below) - automatic, non-blocking
- After saving, show the user a concise summary, the recommended bet, the exact report path, and the shareable link if publishing succeeded
- Ask the user if the research looks good
- If in plan mode, exit plan mode after the user confirms - the research is done
- Suggest next steps: "You can now use this research to inform your implementation,
ask
/lazywebto improve your current design, or start building."
The visible report is: Agent Instructions, Goal, Recommendation, and optional Inspo — in that order. Do not produce the older busy structure with key examples, findings, sources, broad recommendation lists, or long prose analysis sections.
The Recommendation is built like lazyweb-optimize-paywall's hypothesis
engine, with screenshot evidence taking the role experiment evidence plays
there: read the control, name its specific frictions, form 2-4 falsifiable and
structurally divergent bets (Safe bet / Bold bet / Wild card — a thinking
discipline, not visible chips), prototype each as a generated image, and carry
the decision. When a current page or screenshot exists, render Control and
the recommended prototype side by side in equal, height-locked frames with
a ◀ ▶ variant switcher on the right frame so the user can flip through the
other bets in place; runner-up bets also appear in a snap carousel of
same-size cards. Prefer generated bitmap prototype images over hand-coded HTML
mockups when image generation is available; use HTML/CSS only as a fallback or
when the user asks for implementation-ready code. Generate prototype images in
parallel at medium effort by default, or low effort when the user asks for
speed/exploration.
Publish a Shareable Link (always, right after writing report.html)
Every report is auto-published to lazyweb.com so the user can share it with
teammates — ONCE, when it is complete. Never publish partial, skeleton, or
in-progress states; the user sees a report only when it is done. Before publishing, run this contract gate with $REPORT_DIR set to
.lazyweb/deep-design-research/{topic}-{date}:
REPORT_HTML="$REPORT_DIR/report.html"
python3 - "$REPORT_HTML" <<'REPORT_CONTRACT_EOF'
import pathlib, re, sys
path = pathlib.Path(sys.argv[1])
html = path.read_text(encoding="utf-8")
# Forbidden-content checks run on RENDERED content only — HTML comments
# (including the template's own instruction comments) don't render.
rendered = re.sub(r"<!--[\s\S]*?-->", "", html)
required_groups = {
"Agent Instructions copy block": [
r'class=["'][^"']*\bagent-instructions\b',
r'FOR THE CODING AGENT',
],
"Recommendation option deck": [
r'class=["'][^"']*\boption-deck\b',
r'class=["'][^"']*\bprototype-option\b',
r'Recommended',
],
}
missing = []
for label, patterns in required_groups.items():
for pattern in patterns:
if not re.search(pattern, html, re.I):
missing.append(f"{label}: missing {pattern}")
if re.search(r'<h2[^>]*>\s*Inspo\s*</h2>', html, re.I) and not re.search(r'class=["'][^"']*\binspo-map\b', html, re.I):
missing.append("Inspo section must use .inspo-map")
for label, pattern in {
"old tabbed recommendation UI": r'class=["'][^"']*\boption-tabs\b|class=["'][^"']*\boption-panel\b',
"old axis/bubble inspo UI": r'class=["'][^"']*\baxis\b|class=["'][^"']*\bbubble\b',
"old prototype wrapper": r'class=["'][^"']*\bprototype-image\b',
"old evidence sections": r'Reference Evidence|Source Notes|Key Examples|<h2[^>]*>\s*Findings\s*</h2>|<h2[^>]*>\s*Sources\s*</h2>',
"removed patterns section": r'class=["'][^"']*\bpattern-shot\b|class=["'][^"']*\bpatterns-grid\b|<h2[^>]*>\s*Interesting Patterns\s*</h2>',
"in-progress leftovers (reports publish only when complete)": r'class=["'][^"']*\b(?:genbar|pending-ref|pending-strip)\b|http-equiv=["']refresh["']|lazyweb-report-state',
"unfilled template example content": r'EXAMPLE-|picsum\.photos|placehold\.co|\bdata-ex=|\{\{[A-Z0-9_]+\}\}',
}.items():
if re.search(pattern, rendered, re.I):
missing.append(f"Forbidden {label}: {pattern}")
if missing:
print("REPORT_CONTRACT_FAILED")
for item in missing:
print(f"- {item}")
raise SystemExit(1)
print("REPORT_CONTRACT_OK")
REPORT_CONTRACT_EOF
Only proceed when stdout contains REPORT_CONTRACT_OK. If it fails, rewrite
the report once against the "Report v3 Contract" below and rerun this gate.
Never publish a lazyweb-deep-design-research report that fails this gate.
Then run this with the same $REPORT_DIR:
IDEMPOTENCY_KEY="${REPORT_DIR##*.lazyweb/}" # stable per-report key (e.g. deep-design-research/{topic}-{date}) — works for absolute and relative $REPORT_DIR; send the SAME value every attempt so retries dedupe to one link
LAZYWEB_TOKEN=$(cat "$HOME/.lazyweb/lazyweb_mcp_token" 2>/dev/null || true)
if [ -n "$LAZYWEB_TOKEN" ]; then
# Tier 1 - local install: direct POST (idempotency_key dedupes a re-run)
python3 - "$REPORT_DIR" "$LAZYWEB_TOKEN" "deep-design-research" "$IDEMPOTENCY_KEY" <<'PUBLISH_EOF'
import base64, json, pathlib, sys, urllib.error, urllib.request
report_dir, token, skill, idem = pathlib.Path(sys.argv[1]), sys.argv[2], sys.argv[3], sys.argv[4]
version_file = pathlib.Path.home() / ".lazyweb" / "VERSION"
version = version_file.read_text().strip() if version_file.exists() else "0.0.0"
html = (report_dir / "report.html").read_text(encoding="utf-8")
refs = report_dir / "references"
assets = [
{"name": p.name, "b64": base64.b64encode(p.read_bytes()).decode()}
for p in (sorted(refs.iterdir()) if refs.is_dir() else [])
if p.is_file() and not p.name.startswith(".")
]
body = json.dumps({"skill": skill, "version": version, "html": html, "assets": assets, "idempotency_key": idem}).encode()
req = urllib.request.Request(
"https://www.lazyweb.com/api/reports",
data=body,
headers={"Content-Type": "application/json", "Authorization": f"Bearer {token}"},
)
try:
resp = json.loads(urllib.request.urlopen(req, timeout=90).read())
print(f"SHAREABLE_URL: {resp['url']}")
except urllib.error.HTTPError as exc:
print(f"PUBLISH_FAILED: {exc.code} {exc.read().decode()[:500]}")
except Exception as exc:
print(f"PUBLISH_SKIPPED: {exc}")
PUBLISH_EOF
else
# Tier 2 - no local token (hosted/cloud agent): publish via the MCP tool (see below)
echo "PUBLISH_VIA_MCP_TOOL idempotency_key=$IDEMPOTENCY_KEY report_dir=$REPORT_DIR"
fi
Exactly one tier runs - never both.
- Tier 1
SHAREABLE_URL:- include the link: "Shareable link: {url} (unlisted - anyone with the link can view)". - Tier 1
PUBLISH_FAILED: 400 ...- the body names what is unhostable (e.g.missing_assets). Fix the report and re-run the publish ONCE. - Tier 1
PUBLISH_SKIPPED:- say nothing; the local report stands (the user has the file). - Tier 2
PUBLISH_VIA_MCP_TOOL ...- you have no local token (hosted session), so publish with the Lazyweb MCP tool instead:- Size-check first: if
report.htmlplus thereferences/files together exceed ~7MB, do NOT call the tool - tell the user the report was too large to publish from a hosted session (it is saved locally) and stop. - Otherwise call
lazyweb_publish_reportwith:html= the contents ofreport.html;assets= eachreferences/file as{"name": <filename>, "b64": <base64 of the bytes>};report_skill= "deep-design-research";idempotency_key= the value printed afteridempotency_key=. - On
{ ok: true, url }-> show "Shareable link: {url} (unlisted - anyone with the link can view)". - On
{ ok: false }-> tell the user publishing failed and why (theerrorfield); the report is saved locally. IfcodeisREPORT_VALIDATION_ERRORanddetailnames missing assets, fix and call ONCE more; otherwise do not retry. Unlike Tier 1, do NOT stay silent on a Tier-2 failure - a hosted user has no local file to fall back on, so they need the link or the reason.
- Size-check first: if
Hosting-safe HTML (the template already complies - keep it that way)
The hosted copy is served byte-for-byte, so the report must only use:
- inline CSS and inline
<script>- never an external<script src=...> - images via the absolute
imageUrl/image_urlURLs Lazyweb returns, or relativereferences/{filename}paths for locally saved screenshots - no
file://URLs and no absolute local paths (/Users/...,C:\...)
When to Use This
- User wants to understand a design space before building
- User needs competitive analysis for a feature
- User asks "what are best practices for X"
- User wants to see how the best apps solve a specific problem
When NOT to Use This
- User just wants to see a few screenshots quickly -> route to
lazyweb-lite-design-research - User has an existing design and wants improvement ideas -> route to
lazyweb-design-improve - User wants creative/unconventional ideas -> route to
lazyweb-design-brainstorm
Lazyweb MCP Setup
Use the hosted Lazyweb MCP tools at https://www.lazyweb.com/mcp for all Lazyweb database access.
Required MCP tools:
lazyweb_search- text search over mobile and desktop screenshotslazyweb_find_similar- more results like a returned LazywebimageUrlor image payloadlazyweb_compare_image- visual search fromimage_base64+mime_typeorimage_urllazyweb_health- connectivity check
Optional MCP tools:
lazyweb_search_ab_tests- mobile-only supporting experiment evidence for pricing, paywall, checkout, onboarding, and other growth/monetization screens when the live schema exposes itlazyweb_publish_report- hosted-session publish path (see Tier 2 above)
Pass skill: "deep-design-research" on every Lazyweb call. Include "skill": "deep-design-research" in the arguments of each lazyweb_* tool call - for example {"query": "pricing page", "limit": 30, "skill": "deep-design-research"}. This is optional analytics metadata; never drop or change a real argument for it.
Also pass version: "<x.y.z>" on every call. Read ~/.lazyweb/VERSION once per session at skill start (e.g. cat "$HOME/.lazyweb/VERSION" 2>/dev/null || echo 0.0.0); fall back to "0.0.0" if the file is missing or unreadable — never block on this. Include "version": "<that-value>" in the arguments of every lazyweb_* tool call alongside the existing skill arg — for example {"query": "pricing page", "limit": 30, "skill": "deep-design-research", "version": "0.4.5"}. Optional analytics metadata Lazyweb uses to track which skill-pack versions are running; never drop or change a real argument for it.
These are the current public gateway names. Backend/internal surfaces may also
expose canonical tools such as search_screenshots, list_filters,
vision_screenshots, and metadata_screenshots; prefer the lazyweb_* names
in this skill. Use high_design_bar: true only when the live tool schema exposes
it and the user asks for high-design-bar companies, premium examples,
best-designed apps, or stronger visual-quality filtering. That filter is backed
by companies.high_design_bar = true.
Before searching, verify MCP is available by listing tools and running
lazyweb_health.
If Lazyweb MCP is not installed or auth fails:
Tell the user: "Lazyweb MCP is not installed. Run curl -fsSL https://www.lazyweb.com/install.sh | bash, reload this client, then rerun this skill. Lazyweb is free; the bearer token is only for no-billing UI reference tools and is okay in ignored local config."
Then proceed with web research only - the skill still works, just without Lazyweb's database.
Browse Setup (run BEFORE any web capture)
LB=""
# Check the standalone Lazyweb checkout first
for _P in "$(pwd)/.lazyweb/repos/lazyweb-skill/browse/dist/browse" ~/.lazyweb/repos/lazyweb-skill/browse/dist/browse; do
[ -x "$_P" ] && LB="$_P" && break
done
# Fall back to gstack browse
if [ -z "$LB" ]; then
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
[ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/browse/dist/browse" ] && LB="$_ROOT/.claude/skills/gstack/browse/dist/browse"
[ -z "$LB" ] && [ -x ~/.claude/skills/gstack/browse/dist/browse ] && LB=~/.claude/skills/gstack/browse/dist/browse
fi
[ -x "$LB" ] && echo "BROWSE_READY: $LB" || echo "NO_BROWSE"
Immediately after BROWSE_READY, set a real viewport — the daemon's default
window can be arbitrarily small and silently produces unusable captures:
$LB viewport 1440x900
Use $LB screenshot --viewport <path> for viewport-window shots; the default
screenshot is full-page.
If NO_BROWSE: Web screenshot capture is unavailable. Lazyweb results still work -
just describe web examples in text without screenshots. To enable web captures,
run: cd ~/.lazyweb/repos/lazyweb-skill/browse && ./setup
Workflow
0. Ground the search
Before searching, ground the work in what the user is building:
- Run
lazyweb-context-detect(onPATHwhen installed by setup; otherwise~/.lazyweb/repos/lazyweb-skill/bin/lazyweb-context-detect). Use its project/platform/stack output to bias theplatformfilter and captions. - Clarify only what cannot be inferred. If platform is unknown, or the product/screen/outcome is unclear, ask the user ONE short clarifying question to pin down product/screen, mobile vs desktop, and the specific outcome.
1. Understand the research question
Pin down:
- The specific screen, flow, or feature
- The product type, audience, and platform
- The design outcome the recommendation should improve
2. Capture current state (if applicable)
If the user is researching a specific page or app they are building, capture the current state:
- Running dev server or URL available: use preview/browse tools to screenshot it
- Mobile app: ask the user to provide a screenshot
- General topic only: skip this step
Define the report directory FIRST (steps 2-7 write into it):
REPORT_DIR="$(pwd)/.lazyweb/deep-design-research/{topic-slug}-{YYYY-MM-DD}"
mkdir -p "$REPORT_DIR/references" "$REPORT_DIR/work"
Save as $REPORT_DIR/references/current-state.png. This image becomes Control
in the side-by-side Recommendation comparison. Do not create a separate visible
"Current State" section.
3. Read the control (required when a current state exists)
Before any searching or ideation, read the control the way
lazyweb-optimize-paywall reads a paywall. Identify:
- Components present: header, hero, value prop, proof, pricing, CTAs, trust signals, navigation, FAQ, footer — whatever the screen type implies
- Layout pattern: single-column stack, hero + grid, comparison layout, dashboard shell, feed, wizard, etc.
- Strategic moves: what the screen is trying to do — anchoring, social proof, demonstration, urgency, curiosity, authority, personalization
- Audience and user state: who lands here and how warm they are
- Named frictions: 2-5 specific, observable weaknesses of THIS screen ("proof arrives below the fold", "CTA copy is generic", "hero asserts value without showing the product"). Every later hypothesis must attack one of these by name.
If there is no current state (greenfield research), substitute a baseline read: the convention set the category expects, and which conventions the user's product can or cannot honor. Hypotheses then attack gaps between that baseline and the strongest references.
4. Identify competitors and adjacent companies
Think about two groups:
- Direct competitors - apps that solve the same problem
- Adjacent companies with great design - apps in related spaces known for excellent UX
5. Search Lazyweb (go deep — the corpus is the product)
Fast path (default): run the evidence script, not agent gatherers.
A deterministic fetcher ships next to this skill: fetch-evidence.py (python3
stdlib only). Build the full Pass A + Pass B query plan as JSON first, then run
it once — all queries fire in parallel (capped at 6 in-flight, 20s timeouts,
one Retry-After-honoring retry on 429/5xx):
cat > "$REPORT_DIR/work/query-plan.json" <<'PLAN'
{"skill":"deep-design-research","version":"<from ~/.lazyweb/VERSION>","queries":[
{"id":"a1","pass":"A","tool":"lazyweb_search","args":{"query":"<screen/component>","platform":"desktop","limit":15}},
{"id":"b1","pass":"B","tool":"lazyweb_search","args":{"query":"<underlying function>","platform":"desktop","limit":15}}
]}
PLAN
python3 "{skill-base-dir}/fetch-evidence.py" --plan "$REPORT_DIR/work/query-plan.json" --out "$REPORT_DIR/work/evidence.json" || echo "FETCH_FALLBACK"
On success, work/evidence.json holds merged, same-company-deduped references
(imageUrl + visionDescription verbatim) plus a coverage_summary, and
work/evidence-summary.json holds a compact no-URL digest. Then:
- One selection + clustering pass (you, the main agent): READ ONLY
evidence-summary.json(indices + truncated descriptions — a fraction of the tokens), select 12-20 references and form the 2-4 clusters, then pull just the selected indices' full records fromevidence.jsonfor embedding. You may view at most the top ~10 candidate images before the final pick — never the whole corpus. - One bounded top-up round — ALSO through the script, never via raw MCP
tool calls (the v3.4 timed run lost 12 minutes to MCP token dumps here).
Write a second small plan and run
fetch-evidence.pyagain towork/evidence-topup.json:lazyweb_find_similaron the 2-3 strongest results, passing each reference'simageUrlstring asimage_url,"limit": 5;lazyweb_compare_imageis OMITTED from the fast path (measured: low yield and payload-hostile — inline base64 through chat costs more than it returns). Only the agent-fallback path may use it, with the downscaled ≤500px viewport-crop JPEG. Read ONLY the script's stderr verdict line (TOPUP_SATURATED:/TOPUP: N attachable) andevidence-topup-summary.json— never the raw top-up file (its signed URLs are payload-hostile). Expect description-less near-dupes more often than not: budget at most 2 vision-verifications, and treat an empty yield as saturation confirmation (your corpus was already complete), not failure. When search_ab_tests returns 0 references, its prose learnings are in the queries'analysisfields.
- Coverage honesty: if
coverage_summaryshows failed or low_coverage queries — even when the script exits 0 — carry that into the report's.corpusbanner when the selected corpus lands under 8 references or a whole pass came back thin.
Agent fallback (REQUIRED to keep working — do not remove): when the script
exits non-zero, prints FETCH_FALLBACK, emits invalid JSON, or python3 is
missing, gather via the Lazyweb MCP tools yourself instead: run the same
Pass A/Pass B plan as batched agent tool calls — three roles (median mapper /
edge hunter / web + control) dispatched as parallel subagents when the host
has an Agent tool, sequential phases otherwise. Gatherer prompts MUST state:
(a) the output directory already exists — use the Write tool only, never
Bash/mkdir; (b) copy each returned imageUrl string VERBATIM — a reference
without it cannot be embedded; (c) expansion results lacking a
visionDescription are kept (top ≤5) as pending_vision entries for the main
agent to vision-verify after the merge.
Text before image (hard rule, applies to every gatherer): select and rank
references from TEXT — visionDescription, captions, coverage, warnings,
similarity scores — before fetching or viewing ANY image. An image may be
viewed only after its text fields qualify it for the report (or when
vision-verifying an agent-described result). Viewing images first is the
single biggest avoidable token-and-time cost in this phase.
Search discipline: never repeat an identical query; results are deterministic.
Page deeper with offset and follow the response's pagination.next_offset.
Read coverage and warnings on every response. On no_matches/low_coverage,
use the closest result, strip the query to its core 2-6 word UI pattern, or note
the coverage gap in the report. On company_not_in_library, use a suggested
company or drop the filter.
Keep a running search log at $REPORT_DIR/work/search-log.json — append every
query with its filters/offset as you run it (gatherers append to their own
work/gatherer-{n}.json; the merge step consolidates). This is what makes a
crashed run resumable and is the ground truth for "never repeat an identical
query".
Run 6-10 searches minimum, split into two mandatory passes:
Pass A — map the median (2-4 searches). The in-category baseline: what everyone in the user's space does. This is what the Safe bet completes and what the Bold bet must NOT resemble.
{"query":"<specific screen/component>","limit":15}
{"query":"<screen type>","company":"<competitor>","limit":15}
{"query":"<screen type>","category":"<category>","limit":15}
{"query":"<different description of same thing>","limit":15}
Pass B — hunt the edges (4-6 searches, REQUIRED — never skip). Deliberately search OUTSIDE the obvious category and BELOW the screen-name level. This pass exists to feed the Bold and Wild-card bets; a corpus that only contains the median can only produce median recommendations.
{"query":"<the underlying FUNCTION, not the screen name — 'data visualization with gamification' not 'dashboard'>","limit":15}
{"query":"<same screen type>","category":"<deliberately unrelated category: Gaming, Entertainment, Music, Editorial...>","limit":15}
{"query":"<the persuasion mechanism itself, e.g. 'live activity feed', 'interactive product demo'>","limit":15}
{"query":"<a second unrelated category doing the same job>","limit":15}
Cross-pollination routing: finance → look at Gaming/Entertainment/Music; productivity → Fitness/Travel/Social; e-commerce → Education/Health; developer tools → Editorial/Games. The more distant the category, the more novel the transferable mechanism. Yield ranking from live runs: function-level and mechanism-level queries find the most usable outliers; screen-type + unrelated-category is the weakest shape (often low coverage) — run it last and drop it first when budget-constrained. While reading Pass B results, collect outliers: references that do something structurally unlike everything in Pass A. Outliers are the raw material of the Bold and Wild-card bets — note for each one the mechanism (what it DOES, not what it looks like), why it works in its home context, and what would have to adapt to transfer.
Then expand with lazyweb_find_similar on the 2-3 strongest results
(highest similarity + best visionDescription fit) to pull in their visual
neighbors. This is how the corpus gets from "three or four screenshots" to a
real reference set.
When a current-state screenshot exists, also run lazyweb_compare_image with
it (image_base64 + mime_type) and fold the top structural matches into the
reference set — visual similarity from the control itself is the strongest
grounding move available.
lazyweb_compare_image and lazyweb_find_similar results often come back
without a visionDescription and sometimes with null/near-duplicate metadata.
Handle them explicitly:
- A result with no
visionDescriptionis usable ONLY if you view the image yourself (vision) and write the caption from what you actually see — tag it "agent-described". Never attach it unviewed. - Skip entries with null
siteId/pageUrlAND no description. - Dedupe same-company near-duplicates: keep at most one screen per company per cluster unless the duplicates demonstrate different patterns.
Keep limit at 15 (10-20 band): larger results overflow many hosts' tool-result cap,
forcing a dump-to-file + re-read round trip that costs more time than a second
page. Page with offset when you genuinely need more. When sending the control
to lazyweb_compare_image, crop it to its top viewport window FIRST (a
full-page capture downscaled whole becomes an unembeddable sliver and the
server rejects it), then downscale that window to a ≤500px-wide JPEG before
base64 — a full 1500px PNG exceeds tool-call limits.
Platform routing:
- SaaS, web, desktop app, admin surface, or marketing page -> use
platform: "desktop" - iPhone/Android app -> use
platform: "mobile" - General research or cross-platform -> omit platform and judge returned images
Assess quality:
matchCount2/3 or 3/3 = strongmatchCount1/3 = weaksimilarity> 0.4 = good
Selection target: 12-20 references for a normal run (floor: 8 before the
report can claim a healthy corpus; if fewer survive screening, add a .corpus
thin-evidence banner and say so). Relevance is the only bar — more relevant
references is strictly better; padding with loose matches is worse than fewer.
Rules for attaching references to the report:
- Read
visionDescriptionbefore using ANY screenshot. - The screenshot MUST directly illustrate the point it supports.
- If
visionDescriptiondoes not match your suggestion, do not use it. - Never guess what is in a screenshot. If there is no
visionDescription, skip it (or vision-verify it yourself per the rule above). - Use
visionDescriptionto write accurate captions andalttext.
Mismatched references destroy user trust faster than anything else.
6. Search connected inspiration libraries
Check if ~/.lazyweb/libraries.json exists and has connected libraries:
cat ~/.lazyweb/libraries.json 2>/dev/null
If libraries are configured, search each one using the browse tool. For each library:
- Navigate to the library search URL:
$LB goto "{searchUrl}" - Snapshot the page:
$LB snapshot -i - Search for the research query:
$LB fill @eN "{query}" - Submit and wait:
$LB press Enterthen$LB snapshot -i - Screenshot only the most relevant results:
$LB screenshot "$REPORT_DIR/references/{library}-{company}-{screen}.png" - Label all library-sourced references in the report with
[Mobbin],[Savee], etc.
If a library session has expired, tell the user and skip it. Do not block the run.
7. Web research and live screenshot capture
Lazyweb gives curated screenshots. Web captures give the latest competitor state. Do both unless MCP is unavailable and the user wants a web-only fallback.
Find URLs via WebSearch — cover both the median and the edges:
- Search for "[topic] UX best practices [current year]"
- Search for "[competitor name] [screen type]"
- Search for "best [screen type] examples"
- Search for "unconventional [screen type] design" and "creative [screen type] examples [current year]" — Awwwards / FWA / CSS Design Awards winners and experimental sites are often the strongest Bold/Wild-card seeds, because nobody in the user's category is looking at them
Collect 3-8 URLs. For the most useful ones, capture viewport screenshots into
work/ first; move (or trim) a capture into references/ only once the
report actually embeds it:
if [ -x "$LB" ]; then
$LB goto "https://example.com/pricing"
$LB screenshot "$REPORT_DIR/work/example-pricing-page.png"
fi
If browse capture is unavailable, include web evidence only when you can describe it accurately from a reliable source. Do not invent a screenshot.
Inspect every capture before using it. If a capture is defective (cookie/email
modal covering the page, blank below the fold, half-loaded), dismiss the modal
via browse and recapture, or trim a copy to the loaded region. Never present a
broken capture as evidence; keep originals in $REPORT_DIR/work/, not
references/.
8. Experiment evidence (growth/monetization screens only)
For landing pages, pricing, paywalls, checkout, onboarding, referral, and other
growth/monetization screens, call lazyweb_search_ab_tests when available to
validate or challenge a bet you already formed from reading the control. Treat
learnings as directional unless the tool returns measured lift. If the tool is
unavailable or returns no on-context experiments, say so in the relevant card
("design-prevalence signal") — never imply measured lift.
Run it THROUGH fetch-evidence.py (add it as an entry in the top-up plan —
the script speaks generic tools/call) so the response lands in a file instead
of a tool-result dump; even capped calls (include_images: false,
analysis_experiment_limit: 8) have returned 98KB, past most hosts' caps.
Context traps with this tool:
- Discard off-context experiments (wrong platform or screen type, e.g. mobile paywall tests for a web landing page) instead of citing them.
- The tool's own
confidencefield grades corpus retrieval, not evidence strength — your evidence wording comes from the honesty taxonomy, never from that field. - Use
categoryas the industry filter. Do not pass the user's product name as a company filter; treatproductas target context only, and check the responsewarningsfor silently-applied filters before trusting a zero-result answer.
9. Cluster the corpus and prepare references
$REPORT_DIR was created in step 2 (create it now with the same mkdir if
step 2 was skipped).
Group the selected references into 2-4 named clusters of similar approaches ("Proof-wall heroes", "Product-demo-first", "Editorial minimal", "Data-dense operator"). Clusters drive both the Inspo map (cluster labels over neighboring points) and the bets (each bet should draw mainly on one cluster). A cluster needs 2+ members; singletons are outliers — usable as a Wild-card seed or a pattern, but not a cluster.
Do not download Lazyweb database images. Use the returned imageUrl/image_url
directly in HTML. Supabase storage-backed image URLs are signed for 365 days and
intended for report embedding. If a selected Lazyweb result has no returned image
URL, omit the image and rely on visionDescription plus text.
For web-captured examples, save descriptive filenames such as
stripe-pricing-page.png or linear-onboarding-step1.png.
Keep references/ publish-clean: the publish step uploads every file in
references/. Only files actually referenced by report.html belong there;
working files (full-page originals, base64 payloads, untrimmed captures,
search logs) live in $REPORT_DIR/work/, which is never uploaded.
Hypothesis Engine (the core of the Recommendation)
The unit of analysis is a falsifiable bet, not a component list and not a theme.
This mirrors lazyweb-optimize-paywall, with the screenshot corpus playing
the role of the experiment corpus — and it indexes on creativity: the value
of this report over a competent designer's first instinct is the bets a median
competitor would never generate. A set of three reasonable suggestions is a
failed run, even if every section renders perfectly.
A good hypothesis takes this form:
Making [specific change] should [specific outcome] because [specific mechanism].
Good: "Replacing the testimonial-quote hero with a numbers-first proof wall (subscriber count, named outcomes, logos) should lift email signups because this audience buys evidence of results, not promises." Bad: "Improve the hero." / "Make it more premium."
Grounding (required)
Every hypothesis must be anchored to a named friction from the control read (step 3) — not to a reference you happened to like. References and prevalence support a hypothesis; they never originate it. Anti-hybrid checksum: before writing each bet, confirm it answers "what would you change about THIS screen, and why" — not "what does reference X look like". If a bet reads as a description of someone else's screenshot, rewrite it.
Creativity engine (mandatory ideation pass — run BEFORE choosing bets)
LLMs and corpora both regress to the mode: left alone, every "option" becomes a tasteful rearrangement of the category median. This pass exists to fight that. Do it in working notes, before committing to bets:
- Name the dominant convention set from Pass A — the 3-5 moves everyone in this category makes ("testimonial hero", "3-column feature grid", "logos + CTA"). This is the median you must beat, not the menu you pick from.
- Overgenerate: draft 8-12 candidate moves, forcing coverage of these
operators (at least one candidate per operator):
- Inversion — do the opposite of a dominant convention (everyone claims value in copy → remove the copy and show only the product; everyone gates content → give the best content away on the landing page).
- Format transplant — rebuild the page as a different artifact: a live feed, an interactive demo, a terminal, a letter from the founder, a quiz, a gallery, a receipt, a game. The page stops being "a landing page that describes X" and becomes "X itself".
- Cross-category mechanism transfer — take an outlier from Pass B, extract what it DOES (not what it looks like), and apply it here.
- Extremify — find the category's most timid version of a promising idea and push it to its logical extreme (one testimonial → a wall of 400; one stat → the entire hero is the live number).
- Constraint flip — delete a "required" element entirely (no hero, no nav, no pricing table, no sign-up form) and design what fills the void.
- Score each candidate on novelty-in-category (would any Pass A reference do this?) × mechanism fit (is there a real reason it converts HERE?). Discard weird-for-weird's-sake (high novelty, no mechanism) and median-with-makeup (mechanism, no novelty).
- The Bold and Wild-card slots MUST be filled from the surviving high-novelty candidates. If none survive, the corpus is the problem — go back to Pass B and the unconventional web search, don't ship three reasonable bets.
Bet archetypes (forced divergence — a thinking tool, not visible chips)
Produce 2-4 bets and assign each exactly one archetype. The archetypes exist to force divergence during ideation; they are NOT rendered as chips in the report — the option card's one-to-two-sentence description carries the idea in plain words.
- Safe bet — completes the highest-prevalence conventions the control is missing or mis-using. Low risk, evidence-rich, ships fastest. Cite the prevalence count ("7 of 14 references do X; control does not"). This is the ONLY bet allowed to sound reasonable on first read.
- Bold bet — breaks or inverts a dominant category convention, or restructures the page around a model no direct competitor uses. NOT "the strongest cluster's strategy" — that is the median of the best, and it belongs in the Safe bet. Prevalence ceiling: if more than ~20% of the in-category corpus already does it, it is not bold — relabel it Safe and ideate again. Apply the ceiling to the bet's actual structural move at the granularity the bet specifies (e.g. "renders a FULL issue as the page", not the broader "shows content previews"), and state that slice explicitly in the evidence line so the count is checkable. Evidence for a Bold bet is mechanism proof (outlier or cross-category references showing the mechanism working), plus the in-category absence stated as the opportunity ("0 of 14 in-category references do this — whitespace, not risk-free").
- Wild card — a full cross-category or format transplant from the creativity engine: the kind of move that makes the reader pause. Cite the off-category source honestly in the description ("single source, outside this category") and name the risk. Grounded novelty means the MECHANISM has proof somewhere real — it does not mean the move is common anywhere.
A normal run ships one Safe bet, one Bold bet, and a Wild card (optionally a second Bold with a different mechanism). Never ship two bets with the same persuasion mechanism, regardless of archetype.
Anti-collapse rules (the reason options used to look the same)
- Each bet must differ from every other bet on at least two of: page strategy, persuasion mechanism, information architecture, trust source, and primary component set.
- Reject bets that only vary palette, typography, density, theme, or tone.
- Reject a bet that recommends a convention the control already uses unless it changes how that convention is used — verify against the step-3 control read.
- The reasonableness test: read the three bets cold. If every one of them sounds obviously sensible — if nothing makes you pause — the set has collapsed to the median. Regenerate the Bold and Wild slots from the creativity engine. A good set has exactly one bet that reads "of course", one that reads "that's a real swing", and one that reads "wait — really?" (and survives the mechanism question). The test grades the MOVE, not the proof: strong evidence never makes a wild move "too reasonable" — attach maximum proof to the wildest bets, never under-evidence one to keep it sounding daring.
- The planning-meeting test (Bold/Wild): would the move survive a median competitor's planning meeting without anyone calling it risky? If yes, it is not bold. "Add social proof", "clarify the value prop", "restructure the hero" sail through unchallenged — they fail. "Delete the signup form above the fold" gets someone saying "wait, is that safe?" — it passes.
- Pre-imagegen self-check: if all prompts could plausibly produce the same hero/form/card layout with different colors, rewrite them before generating.
- Each bet should draw mainly on a different slice of the corpus (Safe ← Pass A median, Bold ← outliers/edges, Wild ← cross-category). If two bets cite the same three references, they are probably one bet.
Each bet must carry (in the working notes)
- Its archetype (Safe / Bold / Wild card)
- The hypothesis sentence ("Making X should Y because Z")
- The named control friction it attacks
- Evidence, matched to the archetype: a Safe bet cites prevalence ("7 of 14 do X"); a Bold/Wild bet cites mechanism proof (the outlier or cross-category references where the mechanism demonstrably works) plus the in-category absence as the opportunity ("0 of 14 in-category references do this"), backed by evidence-of-search. Each bet embeds the 2-3 references that prove its claim (+ experiment learning when step 8 found one)
- A one-line skip condition (when this bet is the wrong move)
- A detailed
.build-promptspecific enough that another agent could implement it: audience, tone, layout, hierarchy, components, copy strategy, visual rules, references to borrow from and to avoid, and the outcome it optimizes
In the rendered option card, only items 2-3 surface, as a two-b
…(truncated)