Documentation

Everything the extension does, and what ends up in a shared review or an exported file.

Contents

  1. Getting started
  2. Annotating a page
  3. Navigating comments
  4. Sharing a review
  5. Review history & deleting
  6. Exporting comments as JSON
  7. Reading a review over MCP
  8. JSON format reference
  9. Limits & retention
  10. Troubleshooting

1. Getting started

Website Feedback Tool is a Chrome extension for visual feedback. You click on a page to leave comment pins, then share a link. Whoever opens the link sees a frozen copy of the page with your pins on it — no extension, no account, no setup on their side.

  1. Install the extension from the Chrome Web Store.
  2. Open the page you want to comment on and click the extension icon.
  3. Click Annotate this page — or press Alt + A.

The extension only ever activates on the tab you point it at, and only when you ask it to.

2. Annotating a page

Comment pins

In annotation mode, hovering highlights the element under the cursor. Click it, type your note, press Enter. A numbered pin appears and stays anchored to that element — it follows the element when the page scrolls, resizes, or reflows.

Area selection

Press and drag to draw a rectangle instead of a single point. Useful when the problem is a region — a broken layout block, a group of items — rather than one element. Drags under 10 px count as clicks, so a shaky hand won't create stray boxes.

Editing and deleting

Pause and resume

Alt + A pauses annotation without discarding anything — handy when you need to click a link, open a menu, or scroll a widget that annotation mode would otherwise intercept. Press it again to resume.

Comments are stored locally per page URL, so if you close the tab and come back later, your pins are still there. Nothing leaves your browser until you press Save & Share.

4. Sharing a review

Save & Share captures a snapshot of the page — markup, styles, images, canvas and video frames as they are at that moment — uploads it, copies the link to your clipboard and opens it in a new tab. Your original page is left clean: pins are cleared and annotation mode switches off.

Whoever opens the link can:

Anyone with the link can open the review. There is no password. The link is long and random, so it won't be guessed, but treat it like an unlisted URL — a snapshot captures whatever was on screen, including anything private that was visible at the time.

5. Review history & deleting

The clock icon in the popup header opens Your reviews — everything you have shared, newest first, with the page title, comment count and age. Click one to reopen it.

Deleting a review

Hover an entry and click ✕, then confirm. The snapshot is deleted from the server immediately and the link stops working for everyone.

Deletion works through a secret token stored in your browser when the review was created — that's what proves the review is yours, since there are no accounts. Consequences worth knowing:

6. Exporting comments as JSON

Export comments as JSON in the popup downloads the comments as a file — available both while annotating and while viewing a shared review.

The file is built to be acted on, not just read. Every comment carries the CSS selector of the element it's about, what that element is, its computed styles, the CSS rules that match it, and its ancestor chain — enough to open devtools and go straight to the fix. Page and viewport dimensions travel along, because a coordinate on its own means nothing on someone else's screen.

Typical uses: attaching to a bug ticket, feeding an AI assistant, or scripting a report.

Files are named wft-comments-<host>-<date>.json. Exporting from a review takes the styles as they were when the snapshot was captured, not as the live site looks today.

Handing the export to an AI coding agent

The point of the selector, computed styles and matched CSS rules in each comment is that an agent doesn't need to open the browser to act on the feedback — it can go straight to the source file. Drop the exported file into your repo (or point the agent at its path) and ask it to work through the list:

I exported feedback comments from a website review tool as wft-comments-example.com-2026-09-27.json.
For each comment in the file:
1. Skip it if target.found is false (note it instead — the element is gone).
2. Use target.selector and target.element to find the matching markup/component in this repo.
3. Use target.matchedRules[].source to find the stylesheet, and target.matchedRules[].selector
   to find the rule to edit — target.styles is the computed result, not what to copy in.
4. Apply the fix described in the comment's "text".
Report back which comments you fixed, which you couldn't map to a file, and which were skipped.

Two things make this reliable: check formatVersion first — an agent built against this doc should refuse to guess on a version it doesn't recognise — and treat target.found: false as a hard stop for that comment, not something to work around from lastKnownRect alone.

7. Reading a review over MCP

The export above is a file you hand over. The other route is to let the agent fetch the review itself: the MCP server exposes one tool, get_review, which takes a share link (or the bare review id) and returns the review as JSON — page URL and title, capture time, viewport, and the numbered comments with their CSS selectors.

uv run --with 'mcp>=2.0.0' --with 'httpx>=0.27.0' python wft_mcp.py

Two environment variables configure it:

Variable Default What it is
WFT_API_BASE https://website-feedback-tool.com The API host — not the host in the share link. Shared snapshots live on a separate sandbox origin that serves only /reviews/* and 404s /api/*.
WFT_TIMEOUT 20 Request timeout in seconds.

What it returns, and what it cannot

The MCP payload is a deliberate subset of the file export above, and marks itself as one: producer: "server" and targetDetail: "none". Computed styles, matched CSS rules and the ancestor chain are missing — not withheld, but unbuildable. The extension resolves every comment against a live DOM; the server has no layout engine and would have to invent them. For an agent working inside your source that is usually no loss: the CSS is right there, unminified.

A review id is 128 bits of token_urlsafe, and anyone holding the link can already read every comment by opening it. The tool serves the same content in a shape a model can act on and grants no new access — but a share link is still a bearer token, so hand it to an agent you would hand the link to.

Side-by-side guidance on choosing between the file and the server is on the AI agents page.

8. JSON format reference

Current version: formatVersion: 1. Check this field before parsing — it is bumped whenever the shape changes incompatibly.

Top level

FieldTypeMeaning
toolstringAlways "Website Feedback Tool" — lets a consumer recognise the file.
formatVersionnumberSchema version.
exportedAtstringISO-8601 UTC time the file was produced.
pageobjectThe page the comments are about.
environmentobjectBrowser and screen facts: ua, platform, language, screen, viewport, dpr, colorDepth. May be null.
commentCountnumberLength of comments.
commentsarrayComments in the order they are numbered on the page.

page

FieldTypeMeaning
urlstringAddress of the annotated page — for an export made from a review, the original page, not the review link.
titlestringPage title at capture time.
capturedAtstringISO-8601. From a review, this is when the snapshot was taken, which may be long before exportedAt.
layout.viewportobject{width, height} of the visible area, CSS pixels.
layout.documentobject{width, height} of the full scrollable page.
layout.scrollobject{x, y} scroll offset — relates page coordinates to viewport ones.
layout.devicePixelRationumber2 on a typical HiDPI screen.

comments[]

FieldTypeMeaning
numbernumber1-based — the number drawn on the pin.
idstringInternal id. Useful for de-duplication inside one file, nothing more.
textstringWhat the reviewer wrote. It's user input — escape it before rendering as HTML.
typestring"point" (a pin) or "rect" (a dragged region).
positionobject{x, y} page coordinates of the pin; for a region, its top-left corner.
sizeobject{width, height} — regions only.
targetobjectWhat the comment is about. See below.

comments[].target

Resolved when the export runs, against the DOM in front of you. From a review page that DOM is the captured page, so the styles are the ones the reviewer saw.

FieldTypeMeaning
foundbooleanWhether the element was located. If false, the block carries reason, plus selector and lastKnownRect where known.
reasonstring"not-in-dom" — the element is gone; "area-selection" — a region whose centre couldn't be hit-tested; "no-selector".
lastKnownRectobjectGeometry recorded when the comment was made, for elements that have since disappeared.
resolvedBystring"selector", or "area-center" when the element was found by hit-testing the centre of a region.
selectorstringCSS selector for the element — paste it into devtools.
elementobjecttag, id, classes, attributes, text, truncated html.
rectobjectpage and viewport coordinates plus width/height.
pointInElementobject{x, y} as fractions 0..1 of the element's box — the one part of the geometry that doesn't depend on screen size. Absent for regions.
stylesobjectComputed values grouped as layout, box, typography, visual.
matchedRulesarrayThe authored CSS rules matching the element: selector, css, optional media, and source.
ancestorsarrayUp to 3 parents with display, position, overflow, transform, zIndex, size.
scrollContainerobjectSelector of the nearest scrollable ancestor, when the comment sits inside one.
pinnedToViewportbooleanPresent when the element was fixed or sticky at the time the comment was made.

What is deliberately missing

Example

{
  "tool": "Website Feedback Tool",
  "formatVersion": 1,
  "exportedAt": "2026-08-20T09:20:00.000Z",
  "page": {
    "url": "https://example.com/pricing",
    "title": "Pricing — Example",
    "capturedAt": "2026-08-20T09:15:00.000Z",
    "layout": {
      "viewport": { "width": 1280, "height": 720 },
      "document": { "width": 1280, "height": 4200 },
      "scroll": { "x": 0, "y": 900 },
      "devicePixelRatio": 1
    }
  },
  "commentCount": 1,
  "comments": [
    {
      "number": 1,
      "id": "m9x2k1a",
      "text": "The button doesn't respond on mobile",
      "type": "point",
      "position": { "x": 412, "y": 1180 },
      "target": {
        "found": true,
        "resolvedBy": "selector",
        "selector": "#pricing > div > button",
        "element": {
          "tag": "button",
          "classes": ["cta"],
          "attributes": { "data-testid": "buy", "aria-label": "Buy Pro" },
          "text": "Buy now"
        },
        "rect": {
          "page": { "x": 412, "y": 1180 },
          "viewport": { "x": 412, "y": 280 },
          "width": 132, "height": 44
        },
        "styles": {
          "layout": { "display": "inline-block", "position": "static" },
          "box": { "padding": "12px 20px", "boxSizing": "border-box" },
          "visual": { "backgroundColor": "rgb(91, 91, 214)", "pointerEvents": "auto" }
        },
        "matchedRules": [
          {
            "selector": ".pricing-card .cta",
            "css": "background: #5B5BD6; color: #fff; padding: 12px 20px;",
            "source": "https://example.com/assets/app.css"
          },
          {
            "selector": ".pricing-card .cta",
            "media": "(max-width: 600px)",
            "css": "padding: 8px 12px;",
            "source": "https://example.com/assets/app.css"
          }
        ],
        "ancestors": [
          { "selector": "#pricing", "tag": "section",
            "styles": { "display": "block", "overflow": "hidden" } }
        ],
        "pointInElement": { "x": 0.5, "y": 0.41 }
      }
    }
  ]
}

9. Limits & retention

WhatLimit
Shared review lifetime90 days, then permanently deleted
Locally stored comments30 days per page, cleaned up automatically
Review history in the popupLast 100 shares
Snapshot size50 MB per review
Sharing rate5 snapshots per minute, 30 per hour
Total storage10 GB; above it new snapshots are refused until old ones expire
AccountsNone — nothing to sign up for

What is stored and for how long is spelled out in the Privacy Policy.

10. Troubleshooting

The popup shows "Annotate this page" but nothing happens

Chrome doesn't allow extensions on its own pages (chrome://, the Web Store, other extensions' pages). Try a regular site.

Nothing works on a local file

For file:// pages, turn on Allow access to file URLs in the extension's settings, then reload the page. The popup links you straight there.

"Too many snapshots in a row"

Sharing is limited to 5 snapshots a minute and 30 an hour, which keeps one script from filling the server. Your comments stay on the page — wait the stated time and press Try again.

"Couldn't reach the server" when sharing

The snapshot couldn't be uploaded. Your comments are not lost — Try again keeps them. If it persists, save the page via File → Save as MHTML and send that instead.

The snapshot looks different from the live page

Snapshots freeze the DOM and strip scripts, so anything that only exists while JavaScript is running — a carousel mid-animation, a lazily loaded image below the fold — is captured as it was at that moment. Scroll the whole page before sharing to give lazy content a chance to load.

Pins moved after the page changed

Pins anchor to elements, not coordinates. If the site itself changed since you commented, an anchor may no longer exist; in a JSON export those comments come back as found: false with the geometry last recorded for them.

A review link stopped working

Either it expired after 90 days, or it was deleted from the review history. There's no way to restore it — take a fresh snapshot.