kfeedback
One script tag. Leave feedback in context on any page. It stays in your browser until you copy it.
This page is running the widget. Long-press anywhere (hold for about ¾ of a second), press Alt+F, or use the button in the bottom-right corner. Select some text first and the quote rides along with your note.
What it is
A dependency-free, client-only feedback widget. You drop one <script>
tag onto a site, and from then on you can leave a thumbs-up, a thumbs-down, a question
or a note on the page you are looking at — with the URL, the element you pressed
on and any text you had selected captured automatically.
Everything is written to that browser’s localStorage. There is no
account, no server, and no network request of any kind. When you want the feedback out,
you open the review screen and either copy it or save it as a file — then paste or attach
it wherever it is useful, typically a Claude session.
The loop it is built for
- Browse a site you are developing, leaving notes as questions occur to you.
- Open
#kfeedbackon that site to see the lot. - Copy (JSON or Markdown) or Save file.
- Paste into — or attach the file to — a Claude session as context.
Install
<script src="https://kfeed.kpay.uk/kfeedback.js"
data-project="my-site"></script>
That is the whole integration. Put it before </body>, on every page
you want to annotate. Nothing else on the page is touched: the widget renders inside a
shadow root, so your CSS cannot reach it and its CSS cannot reach you.
Options
| Attribute | Default | What it does |
|---|---|---|
data-project | the hostname | Labels the export, and namespaces storage so two projects on one origin stay apart. |
data-button | 1 |
The floating button. 0 hides it — then use the hotkey, a long-press, or KFeedback.open(). |
data-longpress | 1 |
Long-press anywhere to open the composer. |
data-hold-ms | 700 |
How long that press has to be held. |
data-hotkey | alt+f |
Keyboard shortcut. Set to "" to disable. |
data-diagnostics | 1 |
Offers the “include diagnostics” checkbox (browser, viewport, screen). Always off until ticked. |
data-position | bottom-right |
Corner for the button: bottom-right, bottom-left, top-right, top-left. |
data-theme | auto |
auto follows the OS; light or dark pin it. |
Reviewing and getting it out
The review screen opens from the composer’s View all, from
Alt-clicking the floating button, from KFeedback.review(), or by
visiting #kfeedback on any page of the site. It is deliberately full screen,
so a single screenshot captures the whole list.
You can filter by kind, by search text, or to the current page only; delete any single
note with its ✕; and export what is on screen. Two axes control the export:
- Format — JSON for an assistant to read, Markdown for something that stays legible pasted straight into a chat box.
- Detail — Brief is the note plus where it happened. Full detail adds diagnostics and the raw element and selection data. Brief is usually the one you want; full detail is for a rendering bug where the browser matters.
Note that Copy and Save file both act on the filtered list, not the whole store — so the count on the Copy button always tells you what you are about to take.
What a note looks like
{
"kfeedback": 1,
"project": "my-site",
"site": "https://my-site.example",
"exportedAt": "2026-09-21T10:15:00.000Z",
"detail": "brief",
"count": 1,
"items": [
{
"id": "a91c4f2e8b03",
"at": "2026-09-21T10:12:03.884Z",
"kind": "question",
"message": "Why is the Pay button still disabled after I pick a card?",
"page": {
"url": "https://my-site.example/checkout?step=2",
"path": "/checkout?step=2",
"title": "Checkout"
},
"target": { "summary": "button#pay.btn.btn-primary — \"Pay now\"" },
"selection": { "text": "your card will be charged today" }
}
]
}
JavaScript API
| Call | Does |
|---|---|
KFeedback.open() | Opens the composer for the current page. |
KFeedback.review() | Opens the full-screen list. |
KFeedback.items() | Every stored note, oldest first. |
KFeedback.add(msg, kind) | Stores a note without any UI — wire it to your own “report a problem” control or an error handler. kind is up, down, question or note. |
KFeedback.export(fmt, detail) | Returns the serialized export as a string. |
KFeedback.clear(id?) | Deletes one note, or all of them. |
What it does not do
- It does not sync. Notes live in one browser, on one origin. Another device, another browser, or a cleared site-data is a different, empty store.
- It does not authenticate. It does not need to — nothing leaves the machine. Anyone at that browser can read the notes, exactly as they could read the page.
- It is not a bug tracker. It is a notepad that knows what page you were on. Getting notes somewhere durable is the copy or the download, and that step is deliberately yours.
Privacy, plainly. The widget makes no network requests. But what you capture can be sensitive: the URL, its query string, the text you had selected and the text of the element you pressed on all end up in the note, and the export carries them wherever you paste it. On a page showing real customer data, read what you are about to copy before you copy it.
The store is per-origin localStorage, so it is readable by any script
running on that origin. Do not use it to hold secrets.
Where this sits
This widget is the deliberately simple half of kFeed. It stands completely alone: no build step, no dependency, no backend, and no shared code with the kFeed service in the same repository. The server-side pipeline — ingestion, screening, agent transformation, signed patch recommendations — is built separately and neither half requires the other.
Starting client-only is the point. There is nothing to provision and nothing to secure, so the widget can be dropped onto any site the moment it is useful. If a project later wants notes pooled across people or devices, that is a wire-up to the service, not a rewrite of this.