Skip to content

Repository files navigation

Corrigenda

Corrigenda: the things to be corrected.

Point at what a page got wrong, on the page itself. What arrives is the evidence rather than a description of it — the element, the CSS rules that styled it, what the browser was complaining about at the time, and a screenshot if you asked for one.

It is a capture tool for the people who run a site, not a public feedback box: the reporter is already logged in, reports land on disk, and somebody reads them when they choose to. No mail, no queue, no moderation.

The widget's panel over a page: the title bar names the picked
element and its size, a typed sentence describes the defect,
and six switches say what will be sent — element, CSS rules,
computed styles, diagnostics, accessibility, screenshot.

Pick the thing that is wrong, say what is wrong with it, and press send. The switches are what turns a complaint into evidence, and they are yours to change before it goes: the panel says how many kilobytes that adds up to, and will show you the whole payload first.

It pairs well with an agent. Evidence is the difference between a report somebody has to reproduce first and one a program can act on: a selector and an XPath to find the element again, every rule that matched it with the stylesheet and cascade layer it came from, the computed values, the errors and failed loads, the viewport and colour scheme it was all seen under. Point an AI at the JSON interface and it can read what came in, reproduce the defect under the conditions the report names, fix it where the rule lives, and write down what it did with an after-picture beside the original. It can claim proposed; calling something fixed stays a person's word, after their own look. Nothing needs installing for that — the service serves the manual and the schema an agent reads. For a program, and AGENT-USAGE.md.

Quick start

Ruby 3.4 and bundler. Three lines and the widget is in front of you:

bundle install
cp deploy/corrigenda-template.yml deploy/corrigenda.yml
./run -f -p 9393

Open http://127.0.0.1:9393/fixture.html. The widget opens on its own menu — it was loaded on purpose, so the menu is what you came for — and Debug in the corner is the way back in after closing it. File a report about the test page: pick a kind, pick an element, type a sentence, send. It lands in /var/tmp/corrigenda-fixture and shows up at http://127.0.0.1:9393/review/. The template needs no editing for this — -f serves the fixture page and drops the site allowlist, which is why it is a playground and not a deployment.

Three things it does not do yet, in the order you will want them:

  1. Put the widget on a real site. The landing page at the mount carries a bookmarklet — drag it to the toolbar and it loads the widget on whatever page you are looking at, no install on the site at all. Below in Getting a report in.
  2. Answer somewhere permanent. Edit deploy/corrigenda.yml — the store, the endpoint, who may reach it — and let deploy/macro write the Apache configuration from it rather than writing it twice. deploy/README.md has the wiring.
  3. Let a program read the reports. api: true in the config, and /api answers JSON that describes itself. For a program, and the manual an agent is handed is AGENT-USAGE.md.

Whatever you change, this says whether it still works:

bundle exec rake test           the endpoint, ~2 seconds
bundle exec rake test:browser   the widget, in real browsers

What it is made of

A widget (one JavaScript file, CSS inlined, living in a shadow root), a small Sinatra service that receives and stores reports and shows them back, and an optional browser add-on that improves one thing: the screenshot.

client/corrigenda.js   the widget
lib/, views/           the service and its two pages
extension/             the add-on
deploy/                config and the Apache macro generated from it

Reports are directories, one per report, under a store you name — the report as JSON, the screenshot beside it, a state file, and a journal.jsonl of what has been done about it — plus one index.jsonl line for listing. There is no database, and at tens of reports a month there does not need to be.

Getting a report in

Three ways, in the order they cost you anything:

  1. The bookmarklet. The landing page carries one; drag it to the toolbar and it loads the widget on whatever page you are looking at. Nothing has to be installed on the site.
  2. The site loads it. A MoXoW site says corrigenda="https://tools.example.com|<who>" in its framework declaration, and the widget is on the page before its own images and scripts have finished failing — so those failures reach the report.
  3. The add-on, alongside either of the above. A page can only photograph itself through the browser's screen-sharing permission, which cannot crop to an element or black out a password field. The add-on takes the picture through the browser's own capture API instead. See extension/README.md.

What a report carries follows the kind it is. A defect arrives with the evidence around it — the element, the rules, what the browser was complaining about, and the conditions it was all seen under. An idea does not: a suggestion or a question is a sentence somebody wants read, and one that asked for nothing sends nothing but its message and the page it is about — no browser, no viewport, no language or timezone. Switch a channel on and the conditions come back with it, because evidence nobody can place is not evidence. If the idea turns out to be about something on the page, the element switches are also the way to point at it: press one and the picker opens.

A screenshot is optional, and you choose how much of the page it holds. Cropping to the picked element keeps a 16-pixel margin around it — a defect is usually a relationship with whatever sits next to the element, and a crop at its own edges throws that away. On a high-DPI screen the stored image shows that margin at the screen's own scale, so it looks like more than sixteen. Form fields are blacked out before the image is encoded, and nothing off-screen is ever captured.

Running it for real

Ruby 3.4, Sinatra 4.2, Puma 8. Gems install into vendor/. The quick start above is the same thing with the editing skipped; this is the line that was skipped:

$EDITOR deploy/corrigenda.yml      # the store, the endpoint, who may reach it
./run                              # no -f: the configured socket, the real store

The config is the deployment: where reports are written, the socket Apache proxies to, which sites may be reported on, how long a report is kept. It is not tracked, and the Apache configuration is generated from it rather than written twice — deploy/README.md has the whole wiring, including why the service starts by hand.

Reading what came in

The review listing: five reports, each a row giving when it was filed, its kind, the page, a summary, coloured letters for what it carries, who sent it, its state as a coloured control, and an archived switch.

The review UI is at /review behind the same login as the endpoint. A report is triaged from its row in the listing as well as from its own page. Two controls, because they answer two questions: a list of open, proposed, fixed or wontfix says what happened to the defect — it shows the state the report is in, in that state's colour, and the button beside it applies what you pick — and an archived switch says whether anybody still wants it in front of them. A morning's reports do not mean opening each one.

proposed is the claim before the confirmation: whoever did the work — usually an agent — believes the defect gone, and the report waits for your look before anyone calls it fixed. A blue row in the listing is work claiming to be done; the journal beside it says what was done and shows the after-picture to check it by.

Archiving keeps everything: the switch goes back the other way, from the archived list or from the report. Deleting is on the report's own page, at the far end of the row, and asks again.

From a terminal on the host, the same store, without the proxy:

rake data:list                     the working list
rake data:list ALL=1               with the archived ones
rake data:show     ID=<report>     one report in full
rake data:status   ID=<report>     what happened to it…
rake data:status   ID=<report> SET=fixed NOTE="what you changed"
rake data:status   ID=<report> NOTE="…" SHOT=after.webp
rake data:archive  ID=<report>     done looking at it (UNDO=1 to undo)

A note can carry a picture: SHOT= files it beside the report, and the report's page shows it on that line of the trail with the original screenshot beside it on request. That is the comparison worth having before anyone believes a report is fixed — the same page, before and after, rather than a sentence saying so. VIEWPORT= and SCHEME= ride with SHOT= and say what that picture was really taken at; the page prints them beside the conditions the report names, so a comparison made at another width is one a reader can see rather than one they have to trust.

The listing carries a six-character column for what a report holds — E element, R css rules, C computed styles, D diagnostics, A accessibility, S screenshot, a dot where a channel is missing. The same letters appear on the review UI's chips.

For a program

Optional, and absent unless the config asks for it: a JSON interface under /api, for a reader that is a program — an agent asked to fix what was reported, a script that files a ticket. Switch it on with api: true, and it lists reports, hands over one in full, and serves the screenshot as an image. It describes itself at /api/, so a client that knows nothing else can start there.

curl --unix-socket /var/run/corrigenda/corrigenda.sock \
     http://localhost/api/reports

It describes itself: openapi.yaml, served at /api/openapi.json and rendered at /apidocs, where the masthead grows an API tab on a deployment that has switched it on. llms.txt at the mount is the convention-shaped pointer to the same thing, for an agent that arrives knowing nothing else; generated, so it says the interface is off rather than linking a dead route.

Reading needs nothing further. Changing anything is listed, one grant at a time:

api:
    write: [journal, archive, state]

journal adds a line to a report's trail, archive takes it out of the working list or puts it back, state says what happened to the defect. Three rather than one because they are wrong in three ways: a journal line is additive, so a wrong one costs noise; archiving is reversible; a state is a claim somebody will trust and stop checking behind. write: [journal] is where to start. write: true is all of them. Deleting a report is not offered at any setting.

token: <secret> adds a Bearer token on top of whatever Apache already asked for. With no api: key, every path under /api answers 404 rather than 403 — a route that is switched off should not advertise that it exists.

The landing page and the service's own startup line both say which of those a deployment allows, so you never have to read the config to find out what is switched on.

Setting an agent to work

Nothing here needs a plugin or an integration: the interface describes itself, so an agent with a shell has enough. A running service prints the prompt for you — the landing page carries one written for that deployment, with the socket it is actually listening on, a site it actually accepts reports about, and a last paragraph that says what the agent is allowed to do there. Copy it from the page rather than from here, where it could only say <site>.

Three things worth keeping if you write your own: point it at the interface's own root first, so it learns what it may do rather than discovering a 403 halfway through — and write that address in full, because only the socket sees this app unmounted; through the web it is <endpoint>/api/, and a bare /api/ is a path on the host's root where nothing answers. Say what fixed means to you — otherwise it means "I changed something", which is a different claim and the one you will be reading later. And ask for the picture, since a screenshot taken after the work sits beside the one the report came with and settles the claim without you opening the site. An agent handed no prompt at all still has a front door: llms.txt at the mount says what this is and points at both the manual and the schema.

An agent given write: [journal] and nothing else is a good way to start: it tells you what it found and what it tried, and every state on the board still moved because a person moved it.

The protocol itself — the review loop, what fixed means, the journal voice, what is never the agent's to do — is written once, in AGENT-USAGE.md, so a prompt can stay short and the deployment's own page can stay the authority on its specifics. A running service serves it at /agent/usage.md, which is where the prompt sends the agent — only the socket is needed, no checkout. /agent/ is the directory for anything written for one; llms.txt stays at the mount, because a convention with a fixed spelling is only found where it is looked for.

Nothing expires unless the config says it should:

retention:
    archived: 90     # days since somebody archived it
    any:      365    # days since filing, whatever its state

rake data:purge:show     what that would take, and take nothing
rake data:purge          take it

Tests

bundle exec rake test           the endpoint, ~2 seconds
bundle exec rake test:browser   the widget, in real Firefox and Chromium

The second half exists because the picker, the CSSOM walk, the sanitiser and the CORS dance only exist in a browser — see test/browser/README.md.

Where the rest is written down

DESIGN.md what it captures and why, the payload, the storage model — the reasoning under all of it
HISTORY.md approaches taken and abandoned, and the failures that shaped what is here
openapi.yaml the JSON interface in full — the only description of it, rendered at /apidocs
AGENT-USAGE.md using a deployment as an agent: the review loop, the journal, what is never yours
deploy/README.md installing it, the Apache wiring, retention in cron
extension/README.md the add-on: build, install, permissions
test/browser/README.md the browser checks and what each covers
CLAUDE.md conventions and host quirks, for agents working in this repo

Licence

MIT — LICENSE.

SPDX-License-Identifier: MIT is carried by the files that travel away from this repository on their own: client/corrigenda.js, which any site may serve to its visitors, and the add-on's background.js and content.js, which end up installed in a browser. The package takes a copy of the licence with it for the same reason — a WebExtension manifest has no field to name one. Everything else is covered by the file above, where a reader who has the repository will look.

About

In-page defect reporting for the people who run a site: the evidence arrives with the report.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages