Skip to content

Commit 0ec1265

Browse files
committed
fix(docs): finish accessible controls and cached content validation
1 parent 5981f72 commit 0ec1265

11 files changed

Lines changed: 65 additions & 37 deletions

File tree

‎apps/docs/components/docs/article.tsx‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { ContentInteractions } from '@/components/docs/content-interactions'
55
import { PageActions } from '@/components/docs/page-actions'
66
import { IntegrationIcon } from '@/components/ui/integration-icon'
77
import { getNavigation, getNeighbours } from '@/lib/content/catalog'
8-
import type { DocsPage, NavNode } from '@/lib/content/types'
8+
import { findNavigationGroup } from '@/lib/content/navigation'
9+
import type { DocsPage } from '@/lib/content/types'
910
import { DOCS_BASE_URL } from '@/lib/urls'
1011

1112
interface ArticleProps {
@@ -14,21 +15,9 @@ interface ArticleProps {
1415
aside?: ReactNode
1516
}
1617

17-
function findGroup(nodes: NavNode[], url: string, label = ''): string | undefined {
18-
let current = label
19-
for (const node of nodes) {
20-
if (node.kind === 'label') current = node.title
21-
if (node.url === url) return current
22-
if (node.children) {
23-
const found = findGroup(node.children, url, node.title)
24-
if (found !== undefined) return found
25-
}
26-
}
27-
}
28-
2918
export function Article({ page, children, aside }: ArticleProps) {
3019
const group =
31-
findGroup(getNavigation(page.section), page.url) ||
20+
findNavigationGroup(getNavigation(page.section), page.url) ||
3221
{ docs: 'Documentation', api: 'API reference', cli: 'CLI', mcp: 'MCP' }[page.section]
3322
const { previous, next } = getNeighbours(page)
3423
const source = page.operation

‎apps/docs/components/docs/page-actions.tsx‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,19 @@ export function PageActions({ url }: PageActionsProps) {
8787
</Dropdown.Root>
8888
<span
8989
role='status'
90-
className='absolute top-full right-0 z-10 whitespace-nowrap text-[var(--docs-muted)] text-docs-caption'
90+
className={
91+
showCopied
92+
? 'sr-only'
93+
: 'absolute top-full right-0 z-10 whitespace-nowrap text-[var(--docs-muted)] text-docs-caption'
94+
}
9195
>
9296
{status === 'error'
9397
? 'Could not copy. Try again or view Markdown.'
9498
: status === 'ready'
9599
? 'Select again to copy.'
96-
: ''}
100+
: showCopied
101+
? 'Copied to clipboard.'
102+
: ''}
97103
</span>
98104
</div>
99105
)

‎apps/docs/components/ui/tabs.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,7 @@ export function Tabs({
181181
.docs-root [data-docs-tab-panel][hidden] { display: block !important; }
182182
}
183183
.docs-root [data-docs-tabs-root] [role="tablist"],
184-
.docs-root [data-docs-tabs-root] select,
185-
.docs-root [data-docs-tabs-root] button { display: none; }
184+
.docs-root [data-docs-tabs-select] { display: none; }
186185
`}</style>
187186
</noscript>
188187
{items && (
@@ -216,6 +215,7 @@ export function TabsSelect({ items, label, className, ...props }: TabsSelectProp
216215
<Dropdown.Root modal={false}>
217216
<Dropdown.Trigger
218217
{...props}
218+
data-docs-tabs-select=''
219219
aria-label={`${label}: ${selected?.label ?? ''}`}
220220
className={cn(docsControlClass, 'min-w-0 max-w-full shrink px-2', className)}
221221
>

‎apps/docs/lib/content/markdown.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,13 @@ export function serializeMarkdown(tree: Root): string {
117117
switch (node.name) {
118118
case 'WorkflowPreview':
119119
return [paragraph('Workflow configuration'), json(props.workflow)]
120-
case 'BlockPreview':
121-
return [
122-
paragraph(`${String(props.type)} block configuration`),
123-
json(BLOCK_DISPLAY_WORKFLOWS[String(props.type)]),
124-
]
120+
case 'BlockPreview': {
121+
const type = props.type
122+
if (typeof type !== 'string' || !Object.hasOwn(BLOCK_DISPLAY_WORKFLOWS, type))
123+
throw new Error(`Unknown documentation block preview: ${String(type)}`)
124+
const workflow = BLOCK_DISPLAY_WORKFLOWS[type]
125+
return [paragraph(`${type} block configuration`), json(workflow)]
126+
}
125127
case 'OutputBundle':
126128
return [
127129
paragraph(`Output from ${String(props.blockName)}`),

‎apps/docs/lib/content/navigation.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,16 @@ export function flattenNavigation(nodes: NavNode[]): NavNode[] {
178178
walk(nodes)
179179
return flat
180180
}
181+
182+
/** Returns the same parent label used by page headers and search results. */
183+
export function findNavigationGroup(nodes: NavNode[], url: string, label = ''): string | undefined {
184+
let current = label
185+
for (const node of nodes) {
186+
if (node.kind === 'label') current = node.title
187+
if (node.url === url) return current
188+
if (node.children) {
189+
const found = findNavigationGroup(node.children, url, node.title)
190+
if (found !== undefined) return found
191+
}
192+
}
193+
}

‎apps/docs/lib/http/pages.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ export function listPages(input: z.input<typeof listPagesSchema>) {
5454
/** Catalog lookup is the entire read boundary: no caller-supplied URL is fetched. */
5555
export function readPage(path: string) {
5656
const parsed = pagePathSchema.parse(path)
57-
const page = getPage(parsed)
57+
const canonical = parsed === '/' ? parsed : parsed.replace(/\/$/, '')
58+
const page = getPage(canonical)
5859
if (!page || page.url === '/') return null
5960
return { ...pageMetadata(page), headings: page.headings, markdown: page.markdown }
6061
}

‎apps/docs/lib/search/service.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createLogger } from '@sim/logger'
22
import { compareStrings, truncateAtCodePoint } from '@sim/utils/string'
3-
import { getPage } from '@/lib/content/catalog'
3+
import { getNavigation, getPage } from '@/lib/content/catalog'
4+
import { findNavigationGroup } from '@/lib/content/navigation'
45
import type { DocsPage } from '@/lib/content/types'
56
import { generateSearchEmbedding } from '@/lib/embeddings'
67
import { getSearchIndex } from '@/lib/search/precomputed'
@@ -27,12 +28,18 @@ const SECTION_LABELS = { docs: 'Documentation', api: 'API reference', cli: 'CLI'
2728
let hybridRequests = 0
2829

2930
function resultFor(page: DocsPage, id: string, url: string, heading: string): SearchResult {
31+
const section = SECTION_LABELS[page.section]
32+
const group = findNavigationGroup(getNavigation(page.section), page.url)
3033
return {
3134
id,
3235
type: 'page',
3336
url,
3437
content: heading || page.title,
35-
breadcrumbs: [SECTION_LABELS[page.section], page.title],
38+
breadcrumbs: [
39+
section,
40+
...(group && group !== section && group !== page.title ? [group] : []),
41+
page.title,
42+
],
3643
}
3744
}
3845

‎apps/docs/migration/VERIFICATION.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Compared the introduction and Files pages with the live docs and their source co
6969

7070
All eight new framing scenarios failed on the previous renderer ([report](verification/images-before-fix.json)). They now pass at 390px and 1440px, checking loaded backgrounds, relative image geometry, the hero crop, and viewer backgrounds. The complete production browser suite passes all 58 scenarios; [all 2,996 corpus/asset checks pass](verification/image-corpus.json). See [desktop hero](verification/screenshots/image-hero-1440.png), [mobile hero](verification/screenshots/image-hero-390.png), [overview](verification/screenshots/image-overview-1440.png), and [detail](verification/screenshots/image-detail-1440.png).
7171

72-
The retained corpus report is the unmodified `docs-corpus.json` from [CI run 40987306209](https://github.com/simstudioai/sim/actions/runs/40987306209), artifact `12947558143`, at screenshot commit `703e2c1b0e50f908766e52091026777f40ed6145`. It started at `2026-10-30T01:44:31.862Z` and records 2,996 passing checks, zero failures. Its SHA-256 is `591694c2df9152307881fa7722b52cfa0ba9ada327d1d7a80f251091691742b6`. That workflow failed in another suite; this report establishes the corpus result only. It replaces a summary whose source report could not be verified.
72+
The retained corpus report contains the complete `docs-corpus.json` from [CI run 40987306209](https://github.com/simstudioai/sim/actions/runs/40987306209), artifact `12947558143`, at screenshot commit `703e2c1b0e50f908766e52091026777f40ed6145`. It started at `2026-10-30T01:44:31.862Z` and records 2,996 passing checks, zero failures. The raw artifact SHA-256 is `591694c2df9152307881fa7722b52cfa0ba9ada327d1d7a80f251091691742b6`; the committed copy has repository formatting with identical JSON values and SHA-256 `dbd979dd2eb1053db948a6b755bb83aedca61789efdc1ef74f753f8534ca51a9`. That workflow failed in another suite; this report establishes the corpus result only. It replaces a summary whose source report could not be verified.
7373

7474
Production build, root lint, all 26 workspace type checks, all 61 audits, the docs unit suite, and the block registry check passed. The root test command stopped on six timeouts across four script files; rerunning those files after the other checks completed passed all 78 tests. The latest root invocation did not reach the workspace suites.
7575

‎apps/docs/migration/verification/image-corpus.json‎

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎apps/docs/scripts/compile-content.ts‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@ async function readCache(): Promise<CompiledCache> {
180180
}
181181
}
182182

183+
function validateMedia(relative: string, mediaPaths: string[]) {
184+
for (const media of mediaPaths)
185+
if (
186+
media.startsWith('/') &&
187+
!existsSync(path.join(DOCS_ROOT, 'public', decodeURIComponent(media.split(/[?#]/)[0])))
188+
)
189+
throw new Error(`${relative}: missing media ${media}`)
190+
}
191+
183192
async function generate() {
184193
await mkdir(path.join(OUTPUT_ROOT, 'pages'), { recursive: true })
185194
const pipelineFiles = [
@@ -221,6 +230,7 @@ async function generate() {
221230
prior?.hash === sourceHash &&
222231
existsSync(javascriptPath)
223232
) {
233+
validateMedia(relative, prior.media)
224234
const { lastModified: _previousDate, ...content } = prior.page
225235
const date = dates.get(file)
226236
const page = { ...content, ...(date ? { lastModified: date } : {}) }
@@ -256,12 +266,7 @@ async function generate() {
256266
contentHash: hash(markdown),
257267
...(dates.get(file) ? { lastModified: dates.get(file) } : {}),
258268
}
259-
for (const media of result.media)
260-
if (
261-
media.startsWith('/') &&
262-
!existsSync(path.join(DOCS_ROOT, 'public', decodeURIComponent(media.split(/[?#]/)[0])))
263-
)
264-
throw new Error(`${relative}: missing media ${media}`)
269+
validateMedia(relative, result.media)
265270
await writeFile(javascriptPath, result.javascript)
266271
await writeFile(
267272
javascriptPath.replace(/\.js$/, '.d.ts'),

0 commit comments

Comments
 (0)