feat: robots.txt editor and tester screen

- golem15.seo.robots singleton (UI-SPEC C10): staging callout and the
  served file (P4); the plugin's own extension adds the editor, the
  tester and the AI bots tab, saved to the robots row with history
- field type robotstxt (E4, live lint, size meter; Decode refuses over
  500 KB) and widget golem15-seo-robotstester (E5) on robots_test, which
  tests the unsaved text and switches through grobotstxt
- saving rules that block a noindex address answers robots.blocks_noindex
This commit is contained in:
2026-10-27 11:51:41 +01:00
parent 3dd936202f
commit 51413fbda4
21 changed files with 1336 additions and 26 deletions

View File

@@ -1,11 +1,13 @@
/* SEO field elements (UI-SPEC C4, C5, C6, C14). Light DOM, every rule
/* SEO field elements (UI-SPEC C4, C5, C6, C10, C14). Light DOM, every rule
scoped under its element tag, colours from the public --c-* variables
only (except the E-1 mimic palette inside golem15-seo-serp .serp-card). */
golem15-seo-text,
golem15-seo-robots,
golem15-seo-serp,
golem15-seo-image {
golem15-seo-image,
golem15-seo-robotstxt,
golem15-seo-robotstester {
display: block;
color: var(--c-text);
font-size: 14px;
@@ -440,6 +442,187 @@ golem15-seo-image.seo-image--invalid .seo-image__img {
border-color: var(--c-danger);
}
/* E4 golem15-seo-robotstxt */
golem15-seo-robotstxt .seo-robotstxt__control {
box-sizing: border-box;
display: block;
width: 100%;
min-height: 320px;
padding: 12px 14px;
border: 1px solid var(--c-border-strong);
border-radius: 10px;
background: var(--c-surface);
color: var(--c-text);
font-family: 'DM Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 13px;
line-height: 1.6;
white-space: pre;
overflow-x: auto;
resize: vertical;
transition: border-color 150ms, box-shadow 150ms;
}
golem15-seo-robotstxt .seo-robotstxt__control:focus {
outline: none;
border-color: var(--c-primary);
box-shadow: 0 0 0 3px var(--c-ring);
}
golem15-seo-robotstxt.seo-robotstxt--invalid .seo-robotstxt__control,
golem15-seo-robotstxt.seo-robotstxt--over .seo-robotstxt__control {
border-color: var(--c-danger);
}
golem15-seo-robotstxt .seo-robotstxt__lint {
margin-top: 8px;
padding: 12px 14px;
border: 1px solid var(--c-border);
border-radius: 10px;
background: var(--c-subtle);
font-size: 13px;
}
golem15-seo-robotstxt .seo-robotstxt__finding {
display: flex;
align-items: flex-start;
gap: 6px;
overflow-wrap: anywhere;
}
golem15-seo-robotstxt .seo-robotstxt__finding + .seo-robotstxt__finding {
margin-top: 4px;
}
golem15-seo-robotstxt .seo-robotstxt__finding .seo-icon {
flex: none;
margin-top: 3px;
}
golem15-seo-robotstxt .seo-robotstxt__finding--clean {
color: var(--c-ok-text, var(--c-text));
}
golem15-seo-robotstxt .seo-robotstxt__more {
margin-top: 4px;
color: var(--c-muted);
}
golem15-seo-robotstxt .seo-robotstxt__size {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 6px;
color: var(--c-muted);
font-size: 13px;
font-variant-numeric: tabular-nums;
}
golem15-seo-robotstxt .seo-robotstxt__over {
color: var(--c-danger);
}
/* E5 golem15-seo-robotstester */
golem15-seo-robotstester .seo-robotstester__row {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 8px;
}
golem15-seo-robotstester .seo-robotstester__field {
display: flex;
flex-direction: column;
gap: 4px;
}
golem15-seo-robotstester .seo-robotstester__field--url {
flex: 1 1 240px;
}
golem15-seo-robotstester .seo-robotstester__field--agent {
min-width: 200px;
}
golem15-seo-robotstester .seo-robotstester__label {
color: var(--c-muted);
font-size: 13px;
}
golem15-seo-robotstester .seo-robotstester__input,
golem15-seo-robotstester .seo-robotstester__select {
box-sizing: border-box;
height: 44px;
padding: 0 14px;
border: 1px solid var(--c-border-strong);
border-radius: 10px;
background: var(--c-surface);
color: var(--c-text);
font: inherit;
font-size: 14px;
transition: border-color 150ms, box-shadow 150ms;
}
golem15-seo-robotstester .seo-robotstester__input:focus,
golem15-seo-robotstester .seo-robotstester__select:focus {
outline: none;
border-color: var(--c-primary);
box-shadow: 0 0 0 3px var(--c-ring);
}
golem15-seo-robotstester .seo-robotstester__button {
height: 42px;
padding: 0 16px;
border: 1px solid var(--c-border-strong);
border-radius: 10px;
background: var(--c-surface);
color: var(--c-text);
font: inherit;
font-size: 14px;
cursor: pointer;
}
golem15-seo-robotstester .seo-robotstester__button:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--c-ring);
}
golem15-seo-robotstester .seo-robotstester__button:disabled {
cursor: default;
opacity: 0.6;
}
golem15-seo-robotstester .seo-robotstester__result {
display: flex;
align-items: flex-start;
gap: 6px;
margin-top: 10px;
font-size: 14px;
overflow-wrap: anywhere;
}
golem15-seo-robotstester .seo-robotstester__result .seo-icon {
flex: none;
margin-top: 3px;
}
golem15-seo-robotstester .seo-robotstester__result--allowed {
color: var(--c-ok-text, var(--c-text));
}
golem15-seo-robotstester .seo-robotstester__result--blocked {
color: var(--c-text);
}
golem15-seo-robotstester .seo-robotstester__result--error {
color: var(--c-danger);
}
golem15-seo-robotstester .seo-robotstester__note {
margin: 6px 0 0;
color: var(--c-muted);
font-size: 13px;
}
@media (max-width: 600px) {
golem15-seo-robots .seo-robots__panel {
grid-template-columns: 1fr;
@@ -451,6 +634,9 @@ golem15-seo-image.seo-image--invalid .seo-image__img {
}
@media (prefers-reduced-motion: reduce) {
golem15-seo-robotstxt .seo-robotstxt__control,
golem15-seo-robotstester .seo-robotstester__input,
golem15-seo-robotstester .seo-robotstester__select,
golem15-seo-text .seo-text__control,
golem15-seo-robots .seo-robots__segment,
golem15-seo-serp .seo-serp__segment {