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.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.
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.
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.The token must be valid
The token must be valid
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.
The page must be inside the whitelist
The page must be inside the whitelist
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.
Sixty writes each minute for each token
Sixty writes each minute for each token
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.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.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.