Sriram Kansala › Notate
Notate
Click any element, file a ticket · 2026
Available in the Chrome Web Store
TL;DR
Notate turns whatever you see in a browser into a ticket. Click an element or drag an area and write a note. It files to Jira, Linear, GitLab or Trello with the URL, viewport, DOM path, console errors and a screenshot attached. The app under test needs no code added.
My team asked for it, and I designed it and shipped it at work by directing Claude Code. I wrote the specs, prompts and QA notes; it wrote the code. I first submitted it to the store four weeks in. It is live in the Chrome Web Store at v1.1.5. On 2 Nov 2026 it had 21 users, most of them on my team. Next time, I’d put it in front of people outside my team sooner.
Every push also carries a notate-context.md, written to be pasted into a coding agent. My colleague Ankit Kejriwal had that idea and built its first version; I shaped it from there.
The gap a designer can’t fill
Notate began with a gap my team kept hitting in design review. A designer opens a build on staging and sees in seconds that a component has shifted at 1280px. To fix it, a developer also needs the viewport, the exact element and what the console said. Most of that lives in DevTools, where design review doesn’t happen.
So the report leaves without it. The usual flow looks like this:
- The designer pastes a screenshot into Slack.
- A PM files a ticket from what survives the thread: wrong URL, no viewport, component name guessed.
- The developer asks for context, and the bug stays open for days.
Each handoff drops some of that context.
Other tools already work on this. Jam, Marker.io and BugHerd handle annotation well (my reading of their docs, not a benchmark). Jam is a Chrome extension, so it reaches localhost. Marker.io and BugHerd offer a site snippet and an extension. What I wanted to own was the ticket: what it carries when it lands, and who it is written for.
So we built Notate around the ticket. The person who sees the bug files it from the page, and the context goes with it, so there is no handoff left to drop it.
Extension only, a team decision
For that to work, it had to run wherever a designer reviews, so Notate ships as an extension with no site snippet. Its content script runs inside the page, as every in-page tool must, but nobody adds anything to the app. Localhost, a staging URL and an internal admin tool work the moment the extension is on.
Being an extension has costs. Host permissions let an extension read any page, so storage is local by default and the store listing has to be exact. Every capability ships in a bundle Chrome reviews, so a rushed release becomes a submission cycle. Server-side flags soften that: a feature such as Rewind can be switched off in production without a store review.
Who did what
Shipping only as an extension was a team decision. Most of the other product calls were mine. My team asked for Notate, and I built it at work, as part of my job. Turinglabs is fine with me showing it, and Notate is mine. I designed every surface, and my head of design gave feedback in reviews.
Claude Code wrote the code: a React and TypeScript side panel, a Manifest V3 service worker and a Supabase backend. I wrote the specs, the prompts, and the QA and review notes; none of the code is hand-written. When its UI drifted from my design, I sent it back. Ankit Kejriwal had the idea for the agent context file and built its first version.
Capturing an element
Capture starts where the old report did: someone looking at a problem on the page. Hit the shortcut and a ring snaps to whatever is under the cursor. Click, write, save, and a numbered pin stays on the page. A pin has to survive the page changing, so each anchor stores a CSS selector, the element’s rectangle as ratios of the document, and the click point, and tries them in that order. Selectors never use class names, because generated and utility class names change whenever the styling does.
Capturing an area
Not everything wrong on a page is an element. Layout problems and whitespace have nothing to click, so the first version had a separate Screenshot mode. I watched teammates lose track of which mode they were in, so I removed the switch. One Inspect tool now reads the gesture: a click resolves an element, a drag past four pixels resolves an area. The merge removed a shortcut, a message type and a state machine. Capture is the visible viewport only.
Dragging across an iframe froze the selection, so a transparent veil now sits under the cursor from the first press. A stress harness proved the fix over a hundred runs.
Rewind: the minute before you noticed
Both captures show the page as it is now, and a bug report often misses what happened just before it broke. Rewind keeps a rolling window of the page so you can attach it after the fact. It records DOM mutations, not pixels: sixty seconds by default, capped at 50MB, inputs masked if you choose, off until you switch it on, and a Pro feature.
The buffer started in the side panel, moved to the service worker, and ended up in the content script. Chrome suspends a service worker after about thirty seconds idle, so a sixty-second window held only the seconds since its last wake. The content script outlives that and is torn down on navigation, exactly when the buffer should be discarded.
Recording, and drawing while you do
Rewind replays what the page did, but some bugs need narration. Record closes the panel, counts down 3, 2, 1, and captures the tab for up to thirty seconds, so the clip is the whole page and not a page with a panel on it. Chrome grants tab capture to the extension’s service worker, not its panel, so the worker takes a stream ID and hands it to an offscreen document that records.
While recording, a pen paints a transparent canvas over the page itself, so your circle is in the video. Strokes are stored in document coordinates and repainted on scroll, which keeps them on what they point at.
How the evidence survives the trip
None of this helps unless it reaches the developer intact, and that trip is where the old flow lost everything. A replay is uploaded to a hosted viewer and attached as a link, because a developer opens a link and does not open an unknown blob. If the upload fails, a self-contained file goes up instead, so a replay never blocks the push. GitLab gets a real upload and Trello an attachment plus a comment with the link.
GitLab and Trello issue keys start with a hash, and an unescaped hash in a storage path truncates the URL. The upload succeeded and the link looked right, but the viewer told the recipient the replay had expired. Uploads now key on an internal comment id.
Writing the ticket for a model
Then there is who the ticket is written for. A developer is no longer its only reader, so every push carries a notate-context.md, written to be pasted into a coding agent. It holds the element’s tag, visible text, DOM path and selector, plus its component name in React apps. It adds the position, the viewport, the console errors at capture, an inferred issue type and priority, and a raw JSON block for automated agents.
Four prices in about six weeks
A tool in the store also needs a price. In about six weeks it went through four points ($9, $19, $5, $7) on three processors (Stripe, Gumroad, Dodo). Each change followed feedback from my team or my lead. Billing moved from per-user to per-workspace and back. No one has paid yet, so no buyer has tested any of the four.
Workspace billing died on a bug. A local workspace has a client-made id and no membership row on the server, so checkout rejected it. People opened the upgrade card in the workspace they used and were told they were not a member. With collaboration off, there was no shared workspace to sell. Billing went back to one subscription per user, $7 a month or $36 a year. Every account gets 25 free pushes for life, and a usage ring turns amber at 15.
The toggle shows both plans per month, so $7 sits beside $3. The $3 is never charged: the sub-line and the button carry the real $36 a year.
AI enhance, five versions in
A ticket is only as good as the note typed into it, so the composer got help. Press a shortcut there and AI improves the draft: a description with no title gets one, and a rough pair gets a copy-edit. The result types back as a preview you accept or discard. It has been through five versions: a stub that echoed your text to test the interaction, Chrome’s on-device model, a hosted model, a field-aware rewrite and a prompt rework.
The on-device model reached about 60% of users, so four in ten would have had a feature that silently did not exist. The hosted path replaced it.
The server owns the prompts as three named tasks, so the endpoint cannot be driven as a free general model. Around that sit a per-IP rate limit, a kill switch and an input cap. The feature is built, and its flag is off in production.
Six built, four lit
A ticket has to land where the team already works, so Jira and Linear came first. GitLab and Trello came later. Slack and Notion are built and switched off.
Slack and Notion sit behind admin flags because neither can carry the whole ticket. Slack takes the screenshot and the context file but not the recording or replay. Notion takes neither: it gets a text note and a context block.
The live four are not copies of one integration. Jira’s required fields are discovered at runtime, because every project configures its own: the dialog reads the project’s create metadata and shows only what is mandatory, plus assignee and labels, and labels can be created inline. Each provider maps a different subset of what Notate captures onto what that tool understands.
A seventh costs little in the extension and more on the server: providers with confidential OAuth clients are pinned to an allowlist in an edge function so their secrets never ship in a bundle. The test suite hard-codes six trackers, so adding a seventh fails the build until someone updates it.
The feature I finished and did not ship
Slack and Notion are not the only finished work I held back. Collaboration is built end to end: cloud workspaces, invitations, and comments, pins and screenshots syncing between members in realtime. Invites travel by a six-character join code. The backend is Supabase, where every rule is a row-level security policy or a stored procedure, so there is no separate server to run.
Then I had Claude Code red-team it. It returned seven findings, two of them high. One was a revocation bypass that let a removed invitee re-insert their own membership and walk back in. The other was an anonymous flood of the error log. Six were fixed. I accepted the seventh, token-based invite preview, because it works as designed. Claude Code also wrote that code, so the review was not independent. Separately, the v1.1.2 submission build passed 18 of 18 checks in a store-compliance audit.
I kept it off. Shipping it means a support load, a permissions model and an abuse risk, and none of them was ready.
What the product still does not answer
Some problems a flag can’t hold back, and three are still open. Local-first has a seam. Comments live on the device by default, but once a workspace is shared they upload the moment they are created, not when a ticket is pushed. A sync-policy check names that pattern as a violation, and for now it only warns.
Instrumentation is thin. Three events fire: session start, pin created and panel time (the tracker-push event sits in a screen nothing reaches). Rows carry no user id, so the table I own cannot say whether someone who captures once comes back and pushes.
And I have the least evidence on whether the ticket alone is reason enough to switch. Most people using Notate are on my team, so it is still a hypothesis.
Where it stands
Even with those open, Notate is live: v1.1.5 on the Chrome Web Store, out of beta. Shipped: element and area capture, screen recording with an on-page marker, and Pro session replay. Every ticket goes to Jira, Linear, GitLab or Trello with its context file. The price is $7 a month or $36 a year, with 25 free pushes for life.
Collaboration, AI enhance, and the Slack and Notion integrations are built and switched off. On 2 Nov 2026 the store showed 21 users, most of them my team. No one has paid yet.