Nitpicks, now with issue numbers. Small gripes, properly filed. Petty? Maybe. Tracked? Always. It bugs them. Now it's a bug. From “ugh” to issue #412.
quibble is a feedback button for your web app. They write one sentence. You get an issue in your own tracker with the page, the browser and the error attached.
Email for early access Try the live demo
yeah, we know. tell us anyway.
Or hand it straight to your coding agent.
A quibble arrives as a complete issue: what they said, where they were, what
broke. That is a task a coding agent can pick up without a meeting first.
Claude Code, GitHub Copilot's coding agent, OpenAI Codex, or whatever agent
reads your tracker: point it at the quibble label and the trip from
“ugh” to a pull request gets short. On GitHub, the official GitHub MCP server
already reads them, so there's nothing extra to build or run.
What arrives with every note
Always
What they wrote, the page they were on, its title, where they came from, their screen size, browser, language and time zone.
Who sent it
Their name from the sign-in you already have. Without one, they can type it, and the issue says it's self-declared.
When something broke
The last five JavaScript errors the page threw, folded away under Diagnostics until you want them.
When they choose
The element they pointed at. A screenshot, with any region you mark private blanked out (GitHub for now; Gitea and Forgejo soon).
When you choose
Console output, failed network requests and any fields your app wants to add. Off until you turn them on.
FYI nothing leaves your infrastructure.
Hosted feedback tools receive your users' screenshots, console logs and URLs on their own servers. quibble has no servers. A note goes from the browser to your app, and from your app to your tracker, with a token only your app holds.
With nobody else in the path, there's no additional data processing agreement to sign, no additional subprocessor to list and no additional vendor security review to run.
- Browser
- Their servers
- Your tracker
- Browser
- Your app
- Your tracker
FYI a submission can never create a label.
People can tag a note, but only with labels your tracker already has. Anything else is dropped before the issue is written, so your label list stays the one you made.
Priorities stay on your scale too. Ask for a P0 and quibble files a P1.
They asked for uiurgent-from-the-ceoP0
quibble filed quibbleuiurgent-from-the-ceoP1
FYI nobody needs an account to complain.
One service token files every issue, so the people reporting never need a login on your tracker. Their name comes from the sign-in already in front of your app: Cloudflare Access, SAML, OIDC, oauth2-proxy or mTLS.
No sign-in at all? quibble can ask for a name or email, and marks it self-declared in the issue, so nobody mistakes it for a verified identity.
Behind your sign-in- Reported by: [email protected] (via quibble)
Typed in, no sign-in- Reported by: [email protected] (self-declared, via quibble)
FYI no database. No container. Nothing new to run.
Your tracker is the datastore. quibble is a Python package with no dependencies of its own, one script tag, and an adapter for FastAPI or Django. Anything else can host it by following a short, documented backend contract. It runs inside the app you already deploy.
Which is also why it can run in this page, with no server behind it. See for yourself below.
FYI your coding agent can take it from here.
Every quibble is a complete issue: what they said, where they were, what broke.
Claude Code, GitHub Copilot's coding agent, OpenAI Codex, or whatever agent reads
your tracker can pick it up from the quibble label and open the fix. On GitHub,
the official GitHub MCP server already reads them, with nothing extra to build.
- “ugh”
- Issue #412
- Your agent
- Pull request
Also in the box
Point at it
“This button, here.” People click the element that's wrong, and its selector and text go into the issue.
Private stays private
Mark a region private and its text is never captured and it's blanked out of screenshots.
Your styles can't break it
The widget lives in a shadow root. Your CSS can't reach it, and it adds none to your page.
One button, many repositories
Route notes by page or let people pick. Discover every repository in an org, and mix GitHub with a self-hosted Gitea.
A review page
Every quibble in one list, filtered by open or closed, with close and reopen one click away.
Tags you choose
Offer all your labels, an allowlist, or everything except a blocklist.
Rate-limited writes
Each signed-in sender gets a budget, so one enthusiastic user can't flood your tracker.
An off switch
Set one variable and your app mounts no quibble routes at all.
quibble doctor
Checks a deployment's token, repositories and labels, and reports what's wrong without printing a secret.
quibble demo
Runs a sample app with every feature reachable, offline, with no token.
A test kit
In-memory fakes of each supported tracker ship with quibble, so your own tests run with no network.
Remarks, if you like
Off by default. Turn them on and pick the voices, add your own lines or replace the lot.
The real thing, running in this tab
Starting the demo loads Python into this tab, and quibble into Python: the same package a server runs, unmodified. Its tracker is an in-memory stand-in from quibble's own test kit, so nothing you type leaves your browser.
Try tagging a note ui, urgent-from-the-ceo. One of those is a real label.
quibble loads in the background once this page has (about 7 MB, cached after the first visit).
Loading Python…
Network requests this page has made since quibble started: 0
That includes the note you just filed. The widget's request went to quibble running in this tab, not over the network, which is why the count didn't move.
Which trackers work
| Tracker | Tested against |
|---|---|
| GitHub.com | Live |
| Gitea | 1.27.3 |
| Forgejo | 12.0.4 |
| GitHub Enterprise Server | Not yet tested |
Every version here is one we ran quibble against, not one we read about. GitHub
Enterprise Server ships as a licensed appliance we can't stand up, so it stays marked
untested until someone runs it. This table is checked against
quibble.tracker.supported_trackers() in the current
quibble, so it can't name a tracker quibble doesn't have. More are on the way.
Want it in your app?
quibble is in early access. Write to [email protected] and say what you'd use it for. A person reads every one.