Skip to content

Commit dfd4b82

Browse files
committed
fix(web): categorize docs nav and stop calling sessions isolated
Docs navigation is now an explicit category catalog instead of slicing the route list. The overview pulls the shared-profile contract from COMMANDS.md. The command reference is filterable from that same source.
1 parent caeb26c commit dfd4b82

9 files changed

Lines changed: 266 additions & 57 deletions

File tree

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { CommandBlock } from "@/components/docs-copy-controls";
1+
import { CommandDirectory } from "@/components/command-directory";
22
import { DocsShell } from "@/components/docs-shell";
3-
import { plainText } from "@/lib/markdown";
43
import { loadProductDocsContent } from "@/lib/repository-content.mjs";
54
import { docsMetadata } from "@/lib/site-metadata";
65

@@ -15,23 +14,15 @@ export default function CommandsPage() {
1514
return (
1615
<DocsShell
1716
activePath="/docs/commands"
17+
sections={commands.groups.map((group) => ({
18+
id: group.id,
19+
label: group.title,
20+
}))}
1821
kicker={`${commands.count} command forms`}
1922
title={<>The complete agent surface.</>}
2023
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."
2124
>
22-
{commands.groups.map((group, index) => (
23-
<section
24-
id={group.title.toLowerCase().replaceAll(" ", "-")}
25-
key={group.title}
26-
>
27-
<p className="docs-label">
28-
{String(index + 1).padStart(2, "0")} / {group.title}
29-
</p>
30-
<h2>{group.title}</h2>
31-
<p>{plainText(group.description)}</p>
32-
<CommandBlock>{group.usage}</CommandBlock>
33-
</section>
34-
))}
25+
<CommandDirectory groups={commands.groups} />
3526
</DocsShell>
3627
);
3728
}

apps/web/app/docs/page.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ export default function DocsPage() {
1616
return (
1717
<DocsShell
1818
activePath="/docs"
19+
sections={[
20+
{ id: "first-run", label: "First run" },
21+
{ id: "workflow", label: "QA workflow" },
22+
{ id: "commands", label: "Command groups" },
23+
{ id: "context", label: "Context pruning" },
24+
{ id: "scrollable", label: "Scrollable evidence" },
25+
{ id: "safety", label: "Safety" },
26+
]}
1927
kicker="Headless documentation"
2028
title={
2129
<>
@@ -31,8 +39,8 @@ export default function DocsPage() {
3139
<p className="docs-label">01 / First run</p>
3240
<h2>Start a session.</h2>
3341
<p>
34-
Start the host, create a session, then visit the app. The session
35-
stays isolated until you close it.
42+
Start the host, create a session, then visit the app.{" "}
43+
{plainText(documentation.sessionModel)}
3644
</p>
3745
<CommandBlock>{documentation.firstRunCommands}</CommandBlock>
3846
<p>{plainText(documentation.startupPresentation)}</p>

apps/web/app/globals.css

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,19 @@ h3,
12481248
width: min(1180px, calc(100% - 48px));
12491249
margin: 0 auto;
12501250
}
1251+
.skip-link {
1252+
position: absolute;
1253+
left: 16px;
1254+
top: 12px;
1255+
z-index: 20;
1256+
padding: 8px 12px;
1257+
background: var(--amber);
1258+
color: var(--ink-on-amber);
1259+
transform: translateY(-150%);
1260+
}
1261+
.skip-link:focus {
1262+
transform: none;
1263+
}
12511264
.docs-nav {
12521265
height: 84px;
12531266
display: flex;
@@ -1286,14 +1299,55 @@ h3,
12861299
font-family: var(--font-mono), monospace;
12871300
font-size: 12px;
12881301
}
1302+
.docs-nav-group,
1303+
.docs-toc {
1304+
display: flex;
1305+
flex-direction: column;
1306+
gap: 11px;
1307+
}
1308+
.docs-nav-group + .docs-nav-group,
1309+
.docs-toc {
1310+
margin-top: 30px;
1311+
}
12891312
.docs-sidebar p {
12901313
margin: 0 0 6px;
12911314
color: var(--amber-ink);
12921315
font-size: 9px;
12931316
letter-spacing: 0.12em;
1317+
text-transform: uppercase;
12941318
}
1295-
.docs-sidebar p:not(:first-child) {
1296-
margin-top: 30px;
1319+
.command-filter {
1320+
display: grid;
1321+
gap: 8px;
1322+
margin: 0 0 42px;
1323+
}
1324+
.command-filter label {
1325+
font-family: var(--font-mono), monospace;
1326+
font-size: 11px;
1327+
letter-spacing: 0.08em;
1328+
text-transform: uppercase;
1329+
color: var(--amber-ink);
1330+
}
1331+
.command-filter input {
1332+
width: 100%;
1333+
min-height: 42px;
1334+
padding: 0 12px;
1335+
border: 1px solid var(--line);
1336+
border-radius: var(--radius);
1337+
background: var(--panel);
1338+
color: var(--ink);
1339+
font-family: var(--font-mono), monospace;
1340+
font-size: 13px;
1341+
}
1342+
.command-filter input:focus {
1343+
outline: 2px solid var(--amber);
1344+
outline-offset: 2px;
1345+
}
1346+
.command-filter p {
1347+
margin: 0;
1348+
color: var(--muted);
1349+
font-family: var(--font-mono), monospace;
1350+
font-size: 11px;
12971351
}
12981352
.docs-sidebar a:hover {
12991353
color: var(--ink);
@@ -1934,9 +1988,13 @@ h3,
19341988
padding-bottom: 12px;
19351989
overflow-x: auto;
19361990
}
1937-
.docs-sidebar p {
1991+
.docs-sidebar p,
1992+
.docs-toc {
19381993
display: none;
19391994
}
1995+
.docs-nav-group {
1996+
display: contents;
1997+
}
19401998
.docs-sidebar a {
19411999
flex: 0 0 auto;
19422000
padding: 7px 9px;

apps/web/app/sitemap.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { MetadataRoute } from "next";
55
export default function sitemap(): MetadataRoute.Sitemap {
66
const routes = [
77
"/",
8-
"/docs",
98
"/docs/markdown",
109
...PRODUCT_DOC_ROUTES.map(({ href }) => href),
1110
];
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"use client";
2+
3+
import { CommandBlock } from "@/components/docs-copy-controls";
4+
import { plainText } from "@/lib/markdown";
5+
import { useMemo, useState } from "react";
6+
7+
type CommandGroup = {
8+
title: string;
9+
id: string;
10+
description: string;
11+
usage: string;
12+
};
13+
14+
export function CommandDirectory({ groups }: { groups: CommandGroup[] }) {
15+
const [query, setQuery] = useState("");
16+
const normalized = query.trim().toLowerCase();
17+
const visible = useMemo(() => {
18+
if (!normalized) return groups;
19+
return groups.filter((group) =>
20+
`${group.title} ${group.description} ${group.usage}`
21+
.toLowerCase()
22+
.includes(normalized),
23+
);
24+
}, [groups, normalized]);
25+
26+
return (
27+
<>
28+
<div className="command-filter">
29+
<label htmlFor="command-filter">Filter commands</label>
30+
<input
31+
id="command-filter"
32+
type="search"
33+
value={query}
34+
onChange={(event) => setQuery(event.target.value)}
35+
placeholder="visit, inspect, record…"
36+
autoComplete="off"
37+
spellCheck={false}
38+
/>
39+
<p>
40+
{visible.length} of {groups.length} groups
41+
</p>
42+
</div>
43+
{visible.length === 0 ? (
44+
<p role="status">No commands match “{query}”.</p>
45+
) : (
46+
visible.map((group, index) => (
47+
<section id={group.id} key={group.id}>
48+
<p className="docs-label">
49+
{String(index + 1).padStart(2, "0")} / {group.title}
50+
</p>
51+
<h2>{group.title}</h2>
52+
<p>{plainText(group.description)}</p>
53+
<CommandBlock>{group.usage}</CommandBlock>
54+
</section>
55+
))
56+
)}
57+
</>
58+
);
59+
}

apps/web/components/docs-shell.tsx

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { HeadlessMark } from "@/components/headless-mark";
22
import { LinkGlyph } from "@/components/link-glyph";
33
import { ThemeToggle } from "@/components/theme-toggle";
4-
import { PRODUCT_DOC_ROUTES } from "@/lib/repository-content.mjs";
4+
import {
5+
PRODUCT_DOC_CATEGORIES,
6+
PRODUCT_DOC_ROUTES,
7+
} from "@/lib/repository-content.mjs";
58
import Link from "next/link";
69

710
export function DocumentationTable({
@@ -42,16 +45,21 @@ export function DocsShell({
4245
lede,
4346
children,
4447
headingAction,
48+
sections,
4549
}: {
4650
activePath: string;
4751
kicker: string;
4852
title: React.ReactNode;
4953
lede: string;
5054
children: React.ReactNode;
5155
headingAction?: React.ReactNode;
56+
sections?: Array<{ id: string; label: string }>;
5257
}) {
5358
return (
5459
<main className="docs-shell">
60+
<a className="skip-link" href="#docs-content">
61+
Skip to content
62+
</a>
5563
<nav
5664
className="docs-nav docs-container"
5765
aria-label="Documentation navigation"
@@ -62,7 +70,10 @@ export function DocsShell({
6270
</Link>
6371
<div>
6472
<Link href="/">Overview</Link>
65-
<Link className="active" href="/docs">
73+
<Link
74+
className={activePath.startsWith("/docs") ? "active" : ""}
75+
href="/docs"
76+
>
6677
Docs
6778
</Link>
6879
<a
@@ -77,32 +88,36 @@ export function DocsShell({
7788

7889
<div className="docs-container docs-layout">
7990
<aside className="docs-sidebar" aria-label="Documentation sections">
80-
<p>DOCUMENTATION</p>
81-
<Link className={activePath === "/docs" ? "active" : ""} href="/docs">
82-
Overview
83-
</Link>
84-
{PRODUCT_DOC_ROUTES.slice(0, 3).map((route) => (
85-
<Link
86-
className={activePath === route.href ? "active" : ""}
87-
href={route.href}
88-
key={route.href}
89-
>
90-
{route.label}
91-
</Link>
92-
))}
93-
<p>TRUST &amp; SUPPORT</p>
94-
{PRODUCT_DOC_ROUTES.slice(3).map((route) => (
95-
<Link
96-
className={activePath === route.href ? "active" : ""}
97-
href={route.href}
98-
key={route.href}
99-
>
100-
{route.label}
101-
</Link>
91+
{PRODUCT_DOC_CATEGORIES.map((category) => (
92+
<div className="docs-nav-group" key={category.id}>
93+
<p>{category.label}</p>
94+
{PRODUCT_DOC_ROUTES.filter(
95+
(route) => route.category === category.id,
96+
).map((route) => (
97+
<Link
98+
className={activePath === route.href ? "active" : ""}
99+
href={route.href}
100+
key={route.href}
101+
aria-current={activePath === route.href ? "page" : undefined}
102+
>
103+
{route.label}
104+
</Link>
105+
))}
106+
</div>
102107
))}
108+
{sections && sections.length > 0 ? (
109+
<nav className="docs-toc" aria-label="On this page">
110+
<p>On this page</p>
111+
{sections.map((section) => (
112+
<a href={`#${section.id}`} key={section.id}>
113+
{section.label}
114+
</a>
115+
))}
116+
</nav>
117+
) : null}
103118
</aside>
104119

105-
<article className="docs-content">
120+
<article className="docs-content" id="docs-content" tabIndex={-1}>
106121
<div className="docs-heading-row">
107122
<div className="docs-kicker">
108123
<span className="status-dot" /> {kicker}

apps/web/lib/repository-content.d.mts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ export type BenchmarkContent = {
3535
export type DocumentationContent = {
3636
introduction: string;
3737
firstRunCommands: string;
38+
sessionModel: string;
3839
qaWorkflowCommands: string;
3940
startupPresentation: string;
4041
contextPruning: string;
4142
scrollableEvidence: string;
4243
commandGroups: Array<{
4344
title: string;
45+
id: string;
4446
commands: string;
4547
description: string;
4648
usage: string;
@@ -53,13 +55,19 @@ export type DocumentationContent = {
5355
export type MarkdownTable = { headers: string[]; rows: string[][] };
5456

5557
export type ProductDocsContent = {
56-
routes: Array<{ href: string; label: string }>;
58+
routes: Array<{ href: string; label: string; category: string }>;
5759
version: string;
5860
protocolVersion: string;
5961
install: Array<{ title: string; copy: string[]; commands: string[] }>;
6062
commands: {
6163
count: number;
6264
groups: DocumentationContent["commandGroups"];
65+
forms: Array<{
66+
group: string;
67+
groupId: string;
68+
form: string;
69+
description: string;
70+
}>;
6371
};
6472
mcp: { copy: string[]; commands: string[]; config: string };
6573
security: {
@@ -81,7 +89,12 @@ export type ProductDocsContent = {
8189
releases: Array<ProductDocsContent["release"]>;
8290
};
8391

84-
export const PRODUCT_DOC_ROUTES: Array<{ href: string; label: string }>;
92+
export const PRODUCT_DOC_CATEGORIES: Array<{ id: string; label: string }>;
93+
export const PRODUCT_DOC_ROUTES: Array<{
94+
href: string;
95+
label: string;
96+
category: string;
97+
}>;
8598

8699
export function loadBenchmarkContent(): BenchmarkContent;
87100
export function loadDocumentationContent(): DocumentationContent;

0 commit comments

Comments
 (0)