# The HTML report

## Reading the report

Every crawl writes an HTML report next to its JSON file: one file you can open in any browser,
email to a client or keep. It is built as a work queue. The first screen answers "what is wrong,
how bad is it, and what do I do first", and everything below it is there when you need the detail.

### The header and the grade

The top of the report names the site, the date of the crawl, how many pages Raptor crawled, how
long it took, and whether it ran the pages' JavaScript.

Beside that is the grade: a letter, the score out of 100 and the grade's name. In Raptor's voice
the names are Apex predator (A), Healthy pack (B), Limping (C), Wounded (D) and Fossilised (F). A
report in the plain voice shows the plain verdict instead, such as "Good".

Under the headline, one sentence says what costs the most points. A line of counts follows: how
many findings there are to fix, by priority; how many pages have something to fix; how many Notes
there are; and how many checks passed. A finding is one check that found something, however many
pages it found it on, so "12 to fix" means twelve problems, not twelve affected URLs.

Anything that limits what the crawl could see, such as stopping at the page limit or pages that
could not be fetched, appears as a blue line under the grade.

### How is this calculated?

Open "How is this calculated?" to see where the points went. The score starts at 100, and each
kind of problem takes off points up to a limit for that kind: broken pages, pages with major
defects, site-level problems and minor issues. The table shows how many points each kind took and
whether it hit its limit. Below it is the rule that orders the findings. [How scoring
works](scoring.md) explains both in full.

A crawl saved by an older version of Raptor was scored by older rules, so its breakdown would not
add up to its score. The report says so instead of showing one.

### Fix first

Fix first lists up to six fixes, most important first: by priority (Fix now, High, Medium, Low)
and then by the points each fix would win back. Each card shows, without a click:

- the priority, a plain title with the count built in, and its reach: "Every page — fix once" when
  one change in a template fixes it everywhere, "/blog/ pages" when it sits in one section,
  "Versions of /blog" when it is on one page's query-string versions (such as
  `/blog?category=seo`), or the number of pages;
- the points fixing it alone would win back, and the share of indexable pages it affects
  (indexable pages are the ones search engines are allowed to list);
- What we found, Why it matters and How to fix;
- one real example: the page's title over its address, with the measured detail;
- **Copy fix brief** and "Show all N pages".

When two or more findings affect exactly the same pages, they share one card, because one change
usually fixes them together. The card lists the others under "Also on these pages".

### All findings

Every finding is in one list, in the same order as Fix first. The strip of six areas above it
follows the order a search engine meets a site: can it be reached, can it be indexed, is it unique,
can it be found, is it well described, and is it fast and safe. Click an area to show only its
findings; an area with nothing wrong says "Clear". You can also filter by priority, by reach, and
by typing part of a finding's title.

Open a finding to see its three blocks, its example and its affected pages.

### Affected pages

A finding's affected pages are grouped by folder first ("/blog/ 33", "/industry/ 21") so you can
see where a problem lives before you read a list. Click a folder to show only its pages. The list
shows each page's title over its address, 25 at a time, with the detail Raptor measured on the
right. When every page shares the same detail, it is said once above the list instead of on every
row. Click a page to open its details.

**Copy URLs** copies the address of every affected page, and **Download CSV** saves them as a
spreadsheet with the page title, folder and detail, whichever folder you are looking at. A cell
that would start a spreadsheet formula (with `=`, `+`, `-` or `@`) gets a leading apostrophe, so a
hostile page title cannot run as a formula when a client opens the file.

### Notes, passed checks and checks not run

Three groups sit closed at the end of the list:

- **Notes** are things worth knowing that are often deliberate, such as pages that name another
  page as their main version, or pages kept out of search on purpose. They cost no points.
- **Checks passed** lists every check that found nothing, by area.
- **Not checked** lists the checks this crawl could not run, and why: they need PageSpeed data,
  pages rendered in a browser, the site's sitemaps, or its robots.txt.

### Pages and the side panel

The Pages table lists every crawled page with its title, address, status code, whether search
engines may list it, its findings, word count and response time. Search by address or title,
filter by status or to pages with findings, and click a column heading to sort.

Click a page, here or anywhere else in the report, to open its side panel: its facts (status,
depth, words, response time, and how many links on the page point within the site and how many
point elsewhere; these are links on the page, not links to it), every finding and Note on it, its main heading, its description, its preferred address (its canonical
URL), any redirects that led to it and the structured data it declares. "Open live page" opens the
page itself in a new tab.

### Site structure

The structure tree shows the site folder by folder, with how many pages each folder holds and how
many of them have something to fix. A page with a query string, such as `/blog?category=seo`, is
its own page in the tree.

### About this crawl

The last section says what this crawl could not see (JavaScript it did not run, speed it did not
measure, URLs it could not fetch and why), the settings it ran with, how the pages answered, and
the exact command that runs the same crawl again, with **Copy command**.

## Keyboard and links

| Key | What it does |
|---|---|
| ⌘K or Ctrl-K | Search findings and pages. It lists the first 8 findings and 12 pages that match and says how many more there are. Arrow keys move, Enter opens, Esc closes. |
| `j` and `k` | Move down and up the findings list. Enter opens or closes the finding you are on. |
| Esc | Close search or the page panel. |

The report also takes two kinds of link, so you can point a colleague at one thing:

- `crawl.html#finding=<check id>` opens that finding, for example
  `crawl.html#finding=content.exact_duplicate`. Each finding shows its check id.
- `crawl.html#page=<address>` opens a page's panel, for example `crawl.html#page=/blog/x`. In an
  address with a query string, the `?`, `=` and `&` are written as `%3F`, `%3D` and `%26`:
  `#page=/blog%3Fcategory%3Dseo`. Clicking a page in the report puts its link in the address bar.

The sun and moon button switches between light and dark, and the report remembers your choice.
Otherwise it follows your system.

## Sharing and printing

- **Copy fix brief** copies one finding as a short Markdown task, ready to paste into Claude Code,
  Linear, Jira or an email: what was found, where, why it matters, how to fix it, and up to 25
  affected addresses with the true total.
- **Copy all fixes** copies every finding to fix as one brief, most important first.
- **Copy URLs** and **Download CSV** export every affected address of one finding.
- **Save as PDF**: print the report from your browser (File, Print, Save as PDF). The printout is a
  clean summary: the header, the grade, Fix first in full, one line for every other finding and
  every Note, the crawl's limits and the footer. Buttons, filters, Pages and Structure are left
  out, and it always prints in the light theme, whatever theme you are reading in.

## Large crawls

The report holds every crawled page, with nothing left out, so a finding's affected pages are
always complete and Copy URLs and Download CSV are exact. Only the per-page details are capped at
100 per finding; when a finding has more, the report says so, and the exports still list every
address. A 20,000-page report is about 6 MB, and up to about 9 MB when nearly every page has
findings. It opens in well under a second and scrolls smoothly, because the Pages table only
builds the rows you can see.

## Opening it elsewhere

The report makes no network requests: its fonts and data are inside the file, so it works offline
and tells no one you opened it. It also carries its own security policy (a Content-Security-Policy
in its head) that lets only its own scripts run and loads nothing from the network, so text copied
from a crawled page can never act as code, even if a future bug let some through unescaped.

A report downloaded from the REST API comes with a strict security policy that blocks its scripts.
The first screen, every finding with its what, why and how, Notes and About still read in full,
and the embedded fonts still load, but the filters, the affected-page lists, the Pages table, the
structure tree and search need scripts. Open the downloaded file directly in a browser to use
them.

## Customising the report

An agency can put its own name, logo, colour and contact line on the reports Raptor writes. You
set this once, in a small text file called `brand.toml`, and every crawl uses it, including the
crawls the REST API and the MCP server run.

### Where Raptor looks

For `raptor crawl`, Raptor uses the first of these that applies:

1. `--no-brand`: the plain Raptor brand, whatever else is set.
2. `--brand <FILE>`: that file. Setting the `RAPTOR_BRAND` environment variable to a path does
   the same. If the file does not exist, the crawl stops.
3. `brand.toml` in Raptor's home folder, if it exists: `~/.raptor/brand.toml`, or
   `~/raptor/brand.toml` on Windows. If `RAPTOR_HOME` is set, Raptor looks in that folder instead.
4. Otherwise, the Raptor brand.

`raptor serve` (the REST API and the MCP server) takes no brand flags. It reads `RAPTOR_BRAND`, or
the home folder's `brand.toml`, once when it starts, so restart it after you edit the file.

### An example

```toml
agency_name  = "Ravana Solutions"
logo         = "ravana-logo.svg"
accent       = "#1F4E79"
accent_dark  = "#8AB8E6"
contact_line = "Questions? hello@ravanasolutions.com"
contact_url  = "https://www.ravanasolutions.com/contact"
voice        = "raptor"
mascot       = true
```

Every key is optional. Leave one out and the report keeps Raptor's choice for it.

| Key | What it sets | Limit |
|---|---|---|
| `agency_name` | Your name on the report. It is also the logo's alt text, the words a screen reader says in place of the image | 80 characters, one line |
| `logo` | Your logo. The path is relative to the folder `brand.toml` is in | An SVG or PNG file of 256 KB or less |
| `accent` | Your brand colour on the light theme | A colour written as `#RRGGBB` |
| `accent_dark` | Your brand colour on the dark theme. Without it, Raptor tries `accent` on the dark theme too | A colour written as `#RRGGBB` |
| `contact_line` | A line of contact details, such as how to reach you | 160 characters, one line |
| `contact_url` | The web address the contact line links to. With no `contact_line`, the address itself is shown | An `http://` or `https://` address |
| `voice` | How the report's headlines and empty states speak: `raptor` is playful, `plain` is not | `raptor` or `plain` |
| `mascot` | Whether the raptor drawing appears when there is nothing to fix or something went wrong | `true` or `false` |

### What stops a crawl, and what only warns

A report with half of a client's brand on it is worse than no report. So when the file is wrong,
the crawl stops before it fetches a single page. It exits with code 1 and an error that names the
file and the key:

```
error: brand.toml: accent: "green" is not a #RRGGBB colour
```

Raptor refuses:

- a file it cannot read, that is not valid TOML, that has a key it does not know (usually a typo),
  or that has a value of the wrong kind, such as a number where text belongs;
- an `accent` or `accent_dark` that is not written as `#RRGGBB`;
- a logo that is missing, is not an SVG or PNG file, or is over 256 KB;
- an SVG logo that could run code or load something from the internet. Raptor reads the file the
  way a browser does, so a prefix such as `<s:script>` or an escaped character does not hide
  anything. It refuses a logo:
  - with a `<script>`, `<foreignObject>`, `<iframe>`, `<object>`, `<embed>` or `<handler>` element;
  - with an `on…=` attribute such as `onload=`, or a `srcdoc=` attribute;
  - with a `javascript:` address anywhere, or an animation (`<set>` or `<animate>`) that changes
    a link or an `on…` attribute;
  - with a link (`href`, `src` or CSS `url(…)`) to anything other than a part of the same file or
    an image embedded as `data:image/`;
  - that defines its own XML entities (a `<!DOCTYPE …[ … ]>` block), which can hide code, or that
    is not well-formed XML;
- a `contact_url` that is not an `http://` or `https://` address;
- a value over its limit.

A colour that is hard to read does not stop the crawl. Raptor measures the contrast between your
accent and each background of that theme, and asks for at least 4.5:1. That is the level the WCAG
accessibility guidelines set for ordinary text. Button text on the accent, in white or near-black,
must reach 4.5:1 as well. A theme that falls short keeps Raptor green, and the crawl carries on
with a warning that gives the measured contrast:

```
warning: brand.toml: #C8E6C9 reaches only 1.21:1 against the light background #F6F3ED, below the 4.5:1 text needs, so the light theme uses Raptor green
```

The light and dark themes are checked separately, so a pale accent can still be used on the dark
theme.

### What your accent changes

Your accent takes the place of Raptor's brand colour only: links, the main button and the keyboard
focus ring. The green that marks a passing check or a healthy score stays Raptor's, so it means the
same thing in every report. "Made with Raptor" in the footer and the small Raptor logo always
appear and cannot be turned off.

### Voice and mascot

`voice = "plain"` swaps Raptor's playful lines, such as "Hunt complete. 125 pages tracked down in
31s.", for plain ones, such as "Crawl complete: 125 pages in 31s." The findings read the same in
both voices: they are always plain. `mascot = false` hides the raptor drawing.

### MCP panels

The panels Claude shows inside a conversation ignore your logo, accent and agency name, because the
apps that host them do not allow logo headers in panels. They do follow `voice` and `mascot`. The
text summaries the MCP tools return always use the plain voice.

If `raptor serve` cannot use the brand file, it still starts, logs the error, and writes reports
with the Raptor brand until the file is fixed.
