webpage-review lets a client mark up a live web page. She installs a Chrome extension, opens the page and writes a comment on the part she means. Herman reads the threads in an admin panel. The tool is mounted under the public tools host.

The parts

Project

A piece of work under review. It holds a site whitelist, reviewer sessions and every thread.

Whitelist

The addresses that a reviewer may comment on. The server checks this, not only the extension.

Session

One reviewer token, with a role and an expiry date. Herman mints it in the admin panel.

Thread

One comment position on one page, with its replies and its open or resolved status.

How a reviewer works

1

Install the extension

Read the install page. The page is bilingual.
2

Paste the token

Herman sends one token. The token names the project and carries the expiry date.
3

Open a whitelisted page

The extension only works on the addresses in the project whitelist.
4

Write a comment

Select the part of the page. Write the comment. The extension anchors the comment to that position.
The reviewer chooses a display name one time. After that the name is fixed. The server reports canSetDisplayName so the extension offers the prompt exactly once.

When a comment loses its place

A comment is anchored to a fingerprint of the element it was written on: where the element sits, what it contains, what stands above it, and what kind of element it is. When the page changes, the extension scores every candidate on the page against that fingerprint. If nothing scores well enough, or if two candidates score too close together, the comment is marked 失 / needs re-pinning. The comment itself is never lost. Only its position is.
Ambiguity is marked lost on purpose. A comment silently attached to the wrong paragraph is worse than a comment that says it no longer knows where it belongs — the first is discovered by a designer acting on it, the second by the reviewer, immediately.

Why an unchanged page can still lose one

Repeating elements are the common case. A status chip repeated down a table column is identical in every stored signal except its row index — same tag, same words, same classes, same heading above it. The right one still scores highest, but not by enough for the tool to be sure, so it refuses to choose. Since 26 August 2026 the picker handles this when the comment is written: if the element cannot be told apart from its twins, the selection widens to the nearest ancestor that can — usually the row, which carries a name or a label. The composer says so when it happens, and 較窄 in the breadcrumb walks back in if the reviewer disagrees.
This is a limitation, not a bug being worked around. An element that repeats identically has no fingerprint of its own, and no scoring rule can invent one. If the widened pin is still not specific enough, say what the comment is about in the text — “the third row” — rather than relying on the pin alone.

Re-pinning

Open the comment in the panel and press 重新標示位置, then click the new position on the page and confirm it. That replaces the anchor and clears the mark in one step.
Any reviewer can re-pin a comment in their own project. It was administrator-only until 25 August 2026, which left the one person who could see a lost comment with nothing to press — the reviewer’s own browser is what runs the scoring and reports the loss in the first place.
A re-pin is the only thing that clears the mark. In particular, undoing a page edit with 全部還原 restores the page and does not restore the register: comments orphaned by the edit stay marked until somebody re-pins them.

What the panel shows on a page it does not cover

The panel follows the current browser tab strictly. On a tab that no project claims there is no active project at all, and the panel lists every project the reviewer is signed in to instead: name, address and that project’s unread total, alphabetically. Clicking a row opens that project’s site in a new tab.
The count comes from the server for each project. Nothing is counted in the browser, so a row cannot disagree with the number in the masthead above it. A count that could not be fetched shows a dash and never a blank — absent and zero are opposite news.
The toolbar badge counts every signed-in project, while the masthead counts the active one. They are meant to disagree: a badge reading zero because the reviewer happens to be on their email would be a lie about whether anything needs them.
The build number sits in the panel’s masthead. Quote it in a bug report — it answers “which version am I running” without anyone having to guess.

Three rules the server enforces

The extension checks these too. The server checks them again, because a changed client must not be able to pass.
An unknown token gives 401. A revoked or expired token gives 410. The two answers are different on purpose: 410 tells the extension to stop and ask for a new token.
One origin can hold many unrelated projects. A single GitHub Pages account serves every repository of that user. A changed client that skipped its own check must still be stopped here, or one project’s token could write into another project.
The limit counts against the token, not the network address. One reviewer cannot spend another reviewer’s budget, and a shared office address cannot be used to throttle her.

Uploads

A reviewer can attach files to a comment. Three limits apply.
The API reports the effective file limit, which is the smaller of the project value and the server cap. Before 10 August 2026 it reported the project value alone. A project set to 200 MB confirmed 200 MB, and the server then refused at 64 MB. A limit is honest only when the number shown is the number enforced.

A refusal has to survive the upload still being in flight

The server decides on the declared size, so it answers while the file is still arriving. It then drains the rest of the body and leaves the connection open, which is what lets the client finish writing and then read the answer already waiting for it.
Until 21 August 2026 the refusal also closed the connection, on the reasoning that closing prevents a reset. Closing caused one. The client was reset mid-upload and never saw the status, so an oversized file failed with a network error instead of the message naming the limit. Measured on the old behaviour, an 8 MB file lost its refusal 119 times out of 120, and the per-file ceiling is 50 MB.

Getting the review out

The panel exports in three formats, and the filters chosen in the export dialog apply to all three. Markdown and JSON name each attachment by its on-box path and its download URL. Neither is any use to somebody who is not on the box and does not hold the gateway cookie, which is why the ZIP exists: it is the only export that hands over the files themselves. Inside the archive, every attachment sits under a folder named for its ticket reference — the same string the Markdown prints as its heading — so a line in the manifest says which folder to open. A file recorded in the review whose bytes are no longer on the box is named in a MISSING.txt at the archive root rather than being quietly absent.
Entries are stored, not compressed. The payload is images and PDFs, which are already compressed, so deflating them would spend time to save almost nothing — and storing is what lets the server stream the archive one file at a time instead of building it in memory. Measured on 17 September 2026: every upload on the box, across every project, was 8.5 MB in 50 files.

Paths are never shown to a reviewer

An /admin/api/* response can contain a filesystem path, such as boxPath. An /api/* response never contains one.
The reviewer is a person outside the project. The layout of the admin’s home directory is not hers to learn.