Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .agents/skills/docs-seo-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ name: docs-seo-audit
description: >-
Audit docs.warp.dev for SEO issues like duplicate titles, missing meta
descriptions, title length problems, and H1 tag issues. Crawls the live
sitemap, generates a report, and fixes issues in the source markdown. Use
sitemap, generates a report, and fixes issues in the source files. Use
when asked to check SEO, fix duplicate titles, audit meta tags, improve
search rankings, or run an SEO check on the docs site.
---

# SEO Audit

Crawl the live docs.warp.dev sitemap to find SEO issues and fix them in the source markdown files.
Crawl the live docs.warp.dev sitemap to find SEO issues and fix them in the source files.

## Running the audit

Expand Down Expand Up @@ -48,7 +48,7 @@ The JSON report contains:

Each issue includes:
- `url` — The live page URL
- `source_file` — The local markdown file (if `--repo-root` was provided)
- `source_file` — The local source file (if `--repo-root` was provided)
- `severity` — `error` (must fix), `warning` (should fix), or `info` (nice to fix)
- `type` — Issue category (see below)
- `message` — Human-readable description
Expand Down Expand Up @@ -145,6 +145,7 @@ Some page titles are intentionally short or specific and must **not** be changed
- **`src/content/docs/terminal/windows/split-panes.mdx`** (`Split panes`) — Same rationale: the section header disambiguates the terminal context. The `title_too_short` warning is intentionally suppressed. Do not rename to "Terminal split panes".
- **`src/content/docs/terminal/windows/tab-configs.mdx`** (`Tab Configs`) — Same rationale: the section header disambiguates the terminal context. Additionally, "Tab Configs" is a proper feature name and should not be prefixed. The `title_too_short` warning is intentionally suppressed. Do not rename to "Terminal Tab Configs".
- **`src/content/docs/terminal/sessions/index.mdx`** (`Sessions`) — The sidebar section header ("Sessions") already provides terminal context. The `title_too_short` warning is intentionally suppressed. Do not rename to "Terminal sessions".
- **`src/content/docs/reference/cli/artifacts.mdx`** (`Artifacts`) — The Reference > CLI section provides context, and "Artifacts" matches the `oz artifact` resource that the page documents. The `title_too_short` warning is intentionally suppressed.

When the audit flags these pages for `title_too_short`, exclude them from your fix list and include a note in your report explaining they are intentional exceptions.

Expand Down Expand Up @@ -244,7 +245,7 @@ If instructed to send a report to Slack, post a summary after the audit complete

**Categorizing issues in the summary:** Before composing the message, cross-reference every issue against the title exceptions list above and check whether the issue has a local source file. Classify each issue into exactly one bucket:
- **Fixed** — issues you resolved in this run
- **Unfixable** — issues with no local source file (e.g., auto-generated API pages)
- **Unfixable** — issues with no local source file
- **Allowlisted** — issues that match a title exception entry (these are intentional, not problems)
- **Remaining** — everything else (genuine issues that still need attention)

Expand All @@ -261,7 +262,7 @@ Only include a section in the Slack message if its count is > 0. Never list allo
• Trimmed 3 overly long descriptions to ≤160 chars

*Unfixable (<count>):*
• <N> pages missing meta descriptions (auto-generated, no local source)
• <N> pages missing meta descriptions (no local source)

*Allowlisted (<count>):*
• <page1>, <page2>, <page3> (intentionally short titles)
Expand All @@ -287,7 +288,7 @@ PR: <pr_url>
<total_pages> pages scanned | <total_issues> issues found (<errors> errors, <warnings> warnings, <info> info)

*Unfixable (<count>):*
• <N> pages missing meta descriptions (auto-generated, no local source)
• <N> pages missing meta descriptions (no local source)

*Allowlisted (<count>):*
• <page1>, <page2> (intentionally short titles)
Expand Down
19 changes: 18 additions & 1 deletion .agents/skills/docs-seo-audit/scripts/seo_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,15 @@ def extract_seo(url):
# ---------------------------------------------------------------------------

def url_to_source_path(url, repo_root):
"""Best-effort mapping from a live URL to the local markdown source file.
"""Best-effort mapping from a live URL to the local source file.

Astro Starlight serves content from src/content/docs/. URL paths map
directly to that directory. Files use .mdx (preferred) or .md extensions,
and directory landing pages are index.mdx (not README.md).

Standalone Astro routes are served from src/pages/ and can be edited
directly even when their content is generated from a spec or runtime data.

Returns the relative path from repo_root, or None if no file is found.
"""
from urllib.parse import urlparse
Expand Down Expand Up @@ -225,6 +228,20 @@ def url_to_source_path(url, repo_root):
candidate = os.path.join(base, path, "index" + ext)
if os.path.isfile(candidate):
return os.path.relpath(candidate, repo_root)
# Standalone Astro routes live outside Starlight content in src/pages/.
# This maps pages like /api/ -> src/pages/api.astro and
# /openapi.yaml -> src/pages/openapi.yaml.ts so generated docs shells
# are treated as locally fixable when their source exists.
pages_base = os.path.join(repo_root, "src", "pages")
for ext in (".astro", ".ts", ".js", ".tsx", ".jsx"):
candidate = os.path.join(pages_base, path + ext)
if os.path.isfile(candidate):
return os.path.relpath(candidate, repo_root)

for ext in (".astro", ".ts", ".js", ".tsx", ".jsx"):
candidate = os.path.join(pages_base, path, "index" + ext)
if os.path.isfile(candidate):
return os.path.relpath(candidate, repo_root)

return None

Expand Down
56 changes: 38 additions & 18 deletions src/components/WarpTopbar.astro
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
/**
* Slim Warp brand topbar for standalone pages that don't sit inside Starlight
* (currently just `/api`). Renders the Warp wordmark + a breadcrumb + a small
* nav so the page reads as part of the same surface as the docs.
* (currently just `/api`). Renders the Warp wordmark + docs breadcrumb + a
* small nav so the page reads as part of the same surface as the docs.
*
* The host page must reserve `--warp-topbar-height` (3.5rem by default) at
* the top of the viewport — see `src/pages/api.astro` where the same value
Expand Down Expand Up @@ -44,23 +44,21 @@ const {
---

<header class="warp-topbar" role="banner">
{/* Wordmark links to warp.dev (matches CustomSiteTitle.astro on Starlight
pages). Users still get to docs root via the explicit "← Docs" link in
the right nav below. */}
<a href="https://warp.dev" class="warp-topbar__home" aria-label="Visit warp.dev">
<span class="warp-topbar__logo warp-topbar__logo--dark" aria-hidden="true" set:html={logoDark} />
<span class="warp-topbar__logo warp-topbar__logo--light" aria-hidden="true" set:html={logoLight} />
<div class="warp-topbar__left">
{/* Wordmark links to warp.dev (matches CustomSiteTitle.astro on Starlight
pages). Users get to docs root via the adjacent "← Docs" breadcrumb. */}
<a href="https://warp.dev" class="warp-topbar__home" aria-label="Visit warp.dev">
<span class="warp-topbar__logo warp-topbar__logo--dark" aria-hidden="true" set:html={logoDark} />
<span class="warp-topbar__logo warp-topbar__logo--light" aria-hidden="true" set:html={logoLight} />
</a>
<a href="/" class="warp-topbar__docs">
<span>Docs</span>
</a>
<span class="warp-topbar__sep" aria-hidden="true">/</span>
<span class="warp-topbar__crumb">{crumb}</span>
</a>
</div>
<div class="warp-topbar__right">
<nav class="warp-topbar__nav" aria-label="Site">
<a href="/" class="warp-topbar__link">
<svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5"/><path d="m12 19-7-7 7-7"/>
</svg>
<span>Docs</span>
</a>
{links.map((link) => (
<a
href={link.href}
Expand Down Expand Up @@ -123,19 +121,24 @@ const {
font-family: var(--scalar-font, 'Inter', sans-serif);
color: var(--scalar-color-1, #fafafa);
}
.warp-topbar__home {
.warp-topbar__left {
display: inline-flex;
align-items: center;
gap: 0.625rem;
min-width: 0;
}
.warp-topbar__home {
display: inline-flex;
align-items: center;
text-decoration: none;
color: inherit;
min-width: 0;
padding: 0.25rem 0.375rem;
margin-left: -0.375rem;
border-radius: var(--sl-radius-md);
}
.warp-topbar__home:hover { background: var(--scalar-background-2); }
.warp-topbar__home:focus-visible {
.warp-topbar__home:focus-visible,
.warp-topbar__docs:focus-visible {
outline: 2px solid var(--scalar-color-accent);
outline-offset: 2px;
}
Expand Down Expand Up @@ -169,6 +172,23 @@ const {
overflow: hidden;
text-overflow: ellipsis;
}
.warp-topbar__docs {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0 0.125rem;
text-decoration: none;
color: var(--scalar-color-3);
font-size: 0.8125rem;
font-weight: 500;
line-height: 1.25;
transition: color 0.15s ease;
white-space: nowrap;
border-radius: var(--sl-radius-xs);
}
.warp-topbar__docs:hover {
color: var(--scalar-color-1);
}
/* Right group wraps the nav and the theme select so the topbar's
`justify-content: space-between` keeps the home/crumb pinned left and
the whole right cluster pinned right. */
Expand Down
14 changes: 13 additions & 1 deletion src/pages/api.astro
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ const specJson = JSON.stringify(specObject);
body {
background-color: inherit;
}
.api-visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
</head>
<body>
Expand All @@ -179,6 +190,7 @@ const specJson = JSON.stringify(specObject);
})();
</script>
<WarpTopbar crumb="API Reference" />
<h1 class="api-visually-hidden">Warp & Oz HTTP API reference</h1>
<script
is:inline
id="api-reference"
Expand Down Expand Up @@ -339,7 +351,7 @@ const specJson = JSON.stringify(specObject);
}
})();
</script>
<script is:inline src="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.52.2" integrity="sha384-lQKcRGSJRRAHGfUX6ajRHvcWdCi8lgITkVTftrhsN5rVkWmsjVaW0encVdMEhGhi" crossorigin="anonymous"></script>
<script is:inline src="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.57.1" crossorigin="anonymous"></script>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] [SECURITY] Removing SRI from this third-party CDN script makes the page trust any bytes jsDelivr serves for this package version; update the integrity hash for @scalar/api-reference@1.57.1 or self-host/pin the bundle instead.

<FeedbackButtons
question="Was this helpful?"
class="api-feedback-widget"
Expand Down
Loading