diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e9105d..5556444 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,6 +116,10 @@ jobs: - run: pnpm install --frozen-lockfile --filter @headless/web - run: pnpm --filter @headless/web lint - run: pnpm --filter @headless/web build + - name: Install Chromium for responsive checks + run: pnpm --filter @headless/web exec playwright install --with-deps chromium + - name: Responsive command directory + run: pnpm --filter @headless/web test:browser linux-e2e: name: Linux E2E (Docker) diff --git a/apps/web/app/docs/commands/page.tsx b/apps/web/app/docs/commands/page.tsx index 11e5c3f..585d7a6 100644 --- a/apps/web/app/docs/commands/page.tsx +++ b/apps/web/app/docs/commands/page.tsx @@ -1,6 +1,5 @@ -import { CommandBlock } from "@/components/docs-copy-controls"; +import { CommandDirectory } from "@/components/command-directory"; import { DocsShell } from "@/components/docs-shell"; -import { plainText } from "@/lib/markdown"; import { loadProductDocsContent } from "@/lib/repository-content.mjs"; import { docsMetadata } from "@/lib/site-metadata"; @@ -19,19 +18,7 @@ export default function CommandsPage() { title={<>The complete agent surface.} lede="These usage lines come from the generated command reference, which protocol tests keep in sync with the CLI help output. Unknown parameters fail closed." > - {commands.groups.map((group, index) => ( -
-

- {String(index + 1).padStart(2, "0")} / {group.title} -

-

{group.title}

-

{plainText(group.description)}

- {group.usage} -
- ))} + ); } diff --git a/apps/web/app/docs/page.tsx b/apps/web/app/docs/page.tsx index 7a1fd37..0d13b29 100644 --- a/apps/web/app/docs/page.tsx +++ b/apps/web/app/docs/page.tsx @@ -16,6 +16,14 @@ export default function DocsPage() { return ( @@ -31,8 +39,8 @@ export default function DocsPage() {

01 / First run

Start a session.

- Start the host, create a session, then visit the app. The session - stays isolated until you close it. + Start the host, create a session, then visit the app.{" "} + {plainText(documentation.sessionModel)}

{documentation.firstRunCommands}

{plainText(documentation.startupPresentation)}

diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 4ab0d21..37db6f6 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -1248,6 +1248,19 @@ h3, width: min(1180px, calc(100% - 48px)); margin: 0 auto; } +.skip-link { + position: absolute; + left: 16px; + top: 12px; + z-index: 20; + padding: 8px 12px; + background: var(--amber); + color: var(--ink-on-amber); + transform: translateY(-150%); +} +.skip-link:focus { + transform: none; +} .docs-nav { height: 84px; display: flex; @@ -1286,14 +1299,58 @@ h3, font-family: var(--font-mono), monospace; font-size: 12px; } +.docs-nav-group, +.docs-toc { + display: flex; + flex-direction: column; + gap: 11px; +} +.docs-nav-group + .docs-nav-group, +.docs-toc { + margin-top: 30px; +} +.docs-toc-inline { + margin: 0 0 42px; +} .docs-sidebar p { margin: 0 0 6px; color: var(--amber-ink); font-size: 9px; letter-spacing: 0.12em; + text-transform: uppercase; } -.docs-sidebar p:not(:first-child) { - margin-top: 30px; +.command-filter { + display: grid; + gap: 8px; + margin: 0 0 42px; +} +.command-filter label { + font-family: var(--font-mono), monospace; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--amber-ink); +} +.command-filter input { + width: 100%; + min-height: 42px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--panel); + color: var(--ink); + font-family: var(--font-mono), monospace; + font-size: 13px; +} +.command-filter input:focus { + outline: 2px solid var(--amber); + outline-offset: 2px; +} +.command-filter p { + margin: 0; + color: var(--muted); + font-family: var(--font-mono), monospace; + font-size: 11px; } .docs-sidebar a:hover { color: var(--ink); @@ -1474,6 +1531,8 @@ h3, margin-top: 27px; } .docs-content .command-block pre { + max-width: 100%; + overflow-x: auto; margin: 0; padding: 24px 74px 24px 24px; border: 1px solid rgba(255, 176, 32, 0.3); @@ -1934,9 +1993,13 @@ h3, padding-bottom: 12px; overflow-x: auto; } - .docs-sidebar p { + .docs-sidebar p, + .docs-sidebar .docs-toc { display: none; } + .docs-nav-group { + display: contents; + } .docs-sidebar a { flex: 0 0 auto; padding: 7px 9px; diff --git a/apps/web/app/sitemap.ts b/apps/web/app/sitemap.ts index 2584fb0..ad2ac98 100644 --- a/apps/web/app/sitemap.ts +++ b/apps/web/app/sitemap.ts @@ -5,7 +5,6 @@ import type { MetadataRoute } from "next"; export default function sitemap(): MetadataRoute.Sitemap { const routes = [ "/", - "/docs", "/docs/markdown", ...PRODUCT_DOC_ROUTES.map(({ href }) => href), ]; diff --git a/apps/web/components/command-directory.tsx b/apps/web/components/command-directory.tsx new file mode 100644 index 0000000..2b7d8cb --- /dev/null +++ b/apps/web/components/command-directory.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { CommandBlock } from "@/components/docs-copy-controls"; +import { plainText } from "@/lib/markdown"; +import { useState } from "react"; + +type CommandGroup = { + title: string; + id: string; + description: string; + usage: string; +}; + +export function CommandDirectory({ groups }: { groups: CommandGroup[] }) { + const [query, setQuery] = useState(""); + const normalized = query.trim().toLowerCase(); + const visible = normalized + ? groups.filter((group) => + `${group.title} ${group.description} ${group.usage}` + .toLowerCase() + .includes(normalized), + ) + : groups; + + const countLabel = + visible.length === 0 + ? `No commands match “${query}”.` + : `${visible.length} of ${groups.length} groups`; + + return ( + <> +
+ + setQuery(event.target.value)} + placeholder="visit, inspect, credentials…" + autoComplete="off" + spellCheck={false} + /> +

+ {countLabel} +

+
+ {visible.length > 0 ? ( + + ) : null} + {visible.map((group, index) => ( +
+

+ {String(index + 1).padStart(2, "0")} / {group.title} +

+

{group.title}

+

{plainText(group.description)}

+ {group.usage} +
+ ))} + + ); +} diff --git a/apps/web/components/docs-shell.tsx b/apps/web/components/docs-shell.tsx index 05d1741..d010175 100644 --- a/apps/web/components/docs-shell.tsx +++ b/apps/web/components/docs-shell.tsx @@ -1,7 +1,10 @@ import { HeadlessMark } from "@/components/headless-mark"; import { LinkGlyph } from "@/components/link-glyph"; import { ThemeToggle } from "@/components/theme-toggle"; -import { PRODUCT_DOC_ROUTES } from "@/lib/repository-content.mjs"; +import { + PRODUCT_DOC_CATEGORIES, + PRODUCT_DOC_ROUTES, +} from "@/lib/repository-content.mjs"; import Link from "next/link"; export function DocumentationTable({ @@ -42,6 +45,7 @@ export function DocsShell({ lede, children, headingAction, + sections, }: { activePath: string; kicker: string; @@ -49,9 +53,13 @@ export function DocsShell({ lede: string; children: React.ReactNode; headingAction?: React.ReactNode; + sections?: Array<{ id: string; label: string }>; }) { return (
+ + Skip to content +