- golem15.seo.issues (Site audit): current issues errors first, labels,
summary and fix columns, filters, P7 issue_details, Check this address
again, Ignore, Stop ignoring, bulk Ignore, ignored rows badged
- golem15.seo.crawls (Crawls): newest first, P6 crawl_summary with one
status callout, staging and rendered-comparison lines, stats strip and
per-kind table; Cancel; a running crawl refuses delete
- Start crawl toolbar action on both, refusing already_running and
no_site_url; navigation Site audit (gauge) and Crawls (globe)
- routes and MCP tools start/cancel/list/get/delete crawls, list_issues
with filters (open by default), recheck_url, ignore and unignore
- AdminOperations maps both controllers; toolbar actions get their own
operation kind
- TestCrawlAdmin, TestCrawlTools, TestIssueKindLabels
- seo_schema (seoschema, E8) with the C17 server rules: invalid JSON with
line and column, non-objects, 16 KB, depth 8, a literal <script and a
type outside the kind's list refused; the value re-serialised
- a custom node with a generated @id merges into it (schema.merged save
notice), other nodes are appended to the graph
- seo_primary_parent (seoparent) for article types; both values stored
in the entry data on every locale row
- the seo_graph widget action (E9 data), P1 primary_gone and the P2
schema type and breadcrumb trail rows
- GET /_seo/api/v1/graph and MCP seo_get_graph; seo_set_url_seo and
apply_batch accept primary_parent and schema
The graph tool lands with the validation: the operations parity test
binds the seo_graph widget action to its tool.
refreshEntry set entry.url to the descriptor's address unconditionally, so
a slug change the job saw before any RecordSaved listener lost its 301 for
good. An entry whose URL differs now goes through AddressChanged (via
auto), which creates the 301 and moves the URL; an empty URL is filled.