What does lens do?
A lens is one JSON graph, <lens>/model.json, built into a self-contained <lens>/index.html
that anyone can open and comment on. <lens> is the folder it was scaffolded into (default
docs/lens). <lens>/README.md documents the node kinds, relations, claim shape and integrity
rules. The build refuses a model that breaks a rule, so a green build is the first check.
This skill's own folder holds template/ (what gets copied into a repo), scripts/ (competitor
helpers) and competitors.md. Find it at .claude/skills/lens/ in the repo, or
~/.claude/skills/lens/ when installed globally; below, $SKILL means that folder.
Requirements: Node 20+ and git. No package.json, no dependencies.
Set up a new lens
-
Pick the folder. Ask the user where the lens should live; default
docs/lens. Refuse to overwrite an existingmodel.jsonthere. -
Copy the template (everything except a built
index.html):mkdir -p docs/lens && cp -R "$SKILL/template/." docs/lens/ && rm -f docs/lens/index.html echo '[]' > docs/lens/ids.lock.json # the starter's ids are not yours; start the lock empty -
Interview the user, then replace the starter model.
model.jsonships with a fictional "Acme Notes" example that shows every common kind. Keep its shape, replace its content. Ask, one topic at a time, and give a concrete draft to correct rather than a blank question:- the product's name (
title) and the owner's name (theownerperson node, idp-ownerunless they prefer another; itstitleis shown as "’s view" on every view claim); - the purpose in one sentence (
purpose, aviewclaim citing the owner's source); - the core loop, 2–5 ordered
loop-steps; - who it is for (
avatars, with a persona map), and what hurts (problems); - interviews or other evidence they already have (
source+personnodes; never invent quotes or timestamps — if there is no transcript, record it as the owner's report); - what has shipped (
surface,shipped: true, with a real screenshot) and what is only an idea (shipped: false); - what people use instead (
alternatives); - up to three bets to prove now (
hypothesisnodes withfocus: 1|2|3). Everything the user says without evidence is aview(theirs) or ahypothesis; nothing isobservedwithout a source and anat. Use placeholder names ("Interviewee A") unless the user wants real names in a file that may be public.
- the product's name (
-
Clean up the example. Delete
assets/example.pngonce no node references it, and point screenshots atassets/surfaces/<surface-id>/<slug>.png. Surfacecodeandadrspaths are relative to the repo root and must exist (pointadrsat wherever the repo keeps decision records, e.g.docs/adr/). -
Build and test:
node docs/lens/build.mjs && node --test docs/lens/tests/*.test.mjs -
Look at it. Comments are stored in the browser per origin, so serve it rather than opening the file directly:
python3 -m http.server 8767 --bind 127.0.0.1 # from the repo root open "http://127.0.0.1:8767/docs/lens/index.html" -
Suggest a
.gitignoreline fordocs/lens/assets/ads/**/*.mp4if they will track competitor ads. Commitmodel.json,ids.lock.json,index.html,assets/and the rest of the folder, following the repo's own commit rules.
Ground the work
Read the relevant nodes, their claims and their sources before asking the owner anything. Give
them a concrete synthesis to correct. For a surface, check the code it names (code, adrs) when
its behaviour matters; a decision record says what was decided, not what the code does today.
Adding evidence
Every statement is a claim with kind, evidence and an optional next:
- observed (Heard / seen): what someone said or did. Cite the source with
at(a transcript timestamp, or a section for a document). Quote or paraphrase closely; never upgrade a hedge ("could be useful"). - view (Owner’s view): the owner's interpretation. It must cite a source whose
peopleinclude the model'sowner. - hypothesis (Untested): anything untested, including agent suggestions and implementation ideas.
- A hypothesis is also a node kind: log each piece of evidence with
stance(supportsorcontradicts) andat. Never set a confidence; the meter on thefocushypotheses is computed from that evidence on the next build (distinct interviewees supporting it; one step down if anyone contradicts it; the owner's view never raises it). Log contradicting evidence as readily as support. - Give every weak claim a
next: a question specific enough to ask in a call. It appears in Next conversation. - Keep who said it straight: an idea the owner raised and someone agreed to is not their need.
- A new interview is a
sourcewithdate,people,locatorand shorttakeaways, cited by the claims it supports. Leave out personal details unrelated to the product. Archive transcripts under<lens>/sources/only if the repo is private and the interviewee agreed.
Update a surface (after changing it)
When a change alters what a surface looks like or does, in the same branch:
- Capture the changed state(s) from the running product into
<lens>/assets/surfaces/<surface-id>/<slug>.png(replace stale ones, keep 1–4). - Update the surface's
screenshotsentries:caption,capturedAt(today),origin: "app",version. The page flags any screenshot older than the last commit to the surface'scode. - Update
unique/situationsif the behaviour changed, andcode/adrsif files moved.
A brand-new shipped surface is a new surface node with shipped: true, at least one screenshot,
serves edges to the problems it addresses and implements edges to loop steps.
Add an idea (from a brainstorm)
A surface that has been discussed but not built is a surface with shipped: false; it appears
under Ideas, visually distinct. Give it unique (as hypothesis claims), situations,
heard (what people said, with evidence), and mockups as screenshots with origin: "design".
When it ships, flip shipped and replace mockups with real captures.
Add a content pillar or idea
A marketing theme is a pillar; a concrete post, video or event is a content-idea under it,
with a format. Link pillars or ideas speaks-to avatars, ideas features the surfaces they show
and like competitors already running a similar angle. Ideas and pillars are Untested until the
content has reached people; log what happened as evidence on a hypothesis about the idea.
Add a market insight
A trend from outside research is an insight on the Market insights tab. Record the article as a
Market research source (optionally a snapshot under sources/), state its facts as observed
findings, and keep what it means for an avatar as a view or hypothesis, including a finding on
the source's limits. Link it bears-on everything it affects.
Add or refresh a competitor
Follow $SKILL/competitors.md: research (real-browser ad checks, never evading a wall),
scripts/add-product.py, staged ad capture, scripts/apply-staged-ads.py (checks everything, then
applies in one step), scripts/smoke-ads.mjs, then commit without .mp4s. Compare with the
product from the lens's purpose and focus hypotheses, never its marketing page, and label that
comparison Untested.
Usability findings are not lens content
Interaction problems (a button nobody finds, a missing focus state) belong in the team's issue tracker. File them there and record only their product meaning, if any, in the lens.
Renaming or removing
ids.lock.json records every commentable id from the last build. An id that disappears must be
mapped in model.json's renamed ("old-id": "new-id", or null when removed on purpose), or
the build fails. This is what keeps existing comments attached.
Changing the lens itself
Any change under components/ (what a view shows, routing, validation, the diff) starts with a
failing test in <lens>/tests/ (node:test; shared fixture in tests/fixture.mjs, fictional
names only). Keep logic in pure modules; only components/app.js touches the DOM. Then run the
tests, then check the same scenario in a browser.
Visualize the diff
When the owner asks to see, review or visualize what changed:
node docs/lens/diff.mjs [rev] # default HEAD; prints the change list
python3 -m http.server 8767 --bind 127.0.0.1
open "http://127.0.0.1:8767/docs/lens/index.html"
Green outlines are added items and yellow are changed ones; removed items are listed in red in the banner. Each added or changed row links to its item. Changes are matched by id, so a single claim lights up without its whole card. Before committing, always clear it and check:
node docs/lens/build.mjs
grep -c LENS-V2-DIFF docs/lens/index.html # must print 0
Rebuild and check
node docs/lens/build.mjs && node --test docs/lens/tests/*.test.mjs
Then look at the affected views in the browser. Comments are browser-local to the origin; export them before switching. End with the lens link, what changed, the evidence and any limits.