2:I[7012,["4765","static/chunks/4765-f5afdf8061f456f3.js","9856","static/chunks/9856-3b185291364d9bef.js","6687","static/chunks/app/docs/%5B...slug%5D/page-e07536548216bee4.js"],"MarkdownRenderer"] 4:I[9856,["4765","static/chunks/4765-f5afdf8061f456f3.js","9856","static/chunks/9856-3b185291364d9bef.js","6687","static/chunks/app/docs/%5B...slug%5D/page-e07536548216bee4.js"],""] 5:I[4126,[],""] 7:I[9630,[],""] 8:I[4278,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"HeadingProvider"] 9:I[1476,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"Header"] a:I[3167,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"Sidebar"] b:I[7409,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"PageFrame"] 3:T1696e, # VoiceAssist Documentation Site - Feature Specifications **Document Version:** 1.0.0 **Last Updated:** 2025-11-21 **Status:** Planning Phase --- ## Table of Contents 1. [Overview](#overview) 2. [Content Management Features](#content-management-features) 3. [Interactive Elements Features](#interactive-elements-features) 4. [Navigation Features](#navigation-features) 5. [Technical Implementation](#technical-implementation) 6. [Content Structure](#content-structure) 7. [Deployment Strategy](#deployment-strategy) --- ## Overview ### Technology Stack The VoiceAssist Documentation Site is built on a modern, performant stack optimized for content-rich documentation: **Core Framework:** - **Next.js 14** with App Router for enhanced performance and React Server Components - **React 18** with concurrent features - **TypeScript** for type safety **Content Management:** - **MDX** for rich, interactive documentation - **Contentlayer** for type-safe content transformation - **Gray-matter** for frontmatter parsing **Styling:** - **Tailwind CSS 3.4+** for utility-first styling - **CSS Variables** for theming - **Tailwind Typography** for prose styling **Search:** - **Algolia DocSearch** for instant, typo-tolerant search - **Keyboard shortcuts** (Cmd+K / Ctrl+K) **Syntax Highlighting:** - **Shiki** for beautiful code highlighting - **Multiple themes** support (light/dark) **Diagrams:** - **Mermaid.js** for flowcharts, sequence diagrams, and more **Deployment:** - **Vercel** for zero-config deployments - **Cloudflare CDN** as alternative ### Architecture Goals 1. **Performance First:** Sub-second page loads, optimized images, streaming SSR 2. **Developer Experience:** Hot reload, type safety, clear component architecture 3. **Content Flexibility:** Easy authoring in MDX with rich components 4. **Accessibility:** WCAG 2.1 AA compliance, keyboard navigation 5. **SEO Optimized:** Semantic HTML, meta tags, sitemap generation 6. **Scalability:** Support 1000+ pages without performance degradation ### Project Structure ``` voiceassist-docs/ ├── app/ # Next.js 14 App Router │ ├── layout.tsx # Root layout │ ├── page.tsx # Homepage │ ├── docs/ # Documentation routes │ │ ├── layout.tsx # Docs layout with sidebar │ │ └── [...slug]/ # Dynamic doc pages │ │ └── page.tsx │ ├── api/ # API routes │ │ └── search/ │ └── globals.css # Global styles ├── components/ # React components │ ├── MDXComponents.tsx # MDX component mapping │ ├── Navigation/ # Navigation components │ ├── Interactive/ # Interactive elements │ └── Layout/ # Layout components ├── content/ # MDX content │ ├── docs/ │ │ ├── getting-started/ │ │ ├── api-reference/ │ │ └── guides/ │ └── blog/ ├── contentlayer.config.ts # Contentlayer configuration ├── lib/ # Utility functions ├── public/ # Static assets └── styles/ # Additional styles ``` --- ## Content Management Features ### Feature 1.1: MDX-based Content System **Priority:** P0 (Critical) **Effort:** 3 days **Dependencies:** Next.js 14, Contentlayer #### Specification A robust MDX-based content system that allows technical writers to create rich documentation with React components embedded in Markdown. The system must support: - Standard Markdown syntax (GFM) - React component imports and usage - Frontmatter metadata - Code blocks with syntax highlighting - Automatic slug generation - Type-safe content queries #### Implementation **contentlayer.config.ts:** ```typescript import { defineDocumentType, makeSource } from "contentlayer/source-files"; import rehypeAutolinkHeadings from "rehype-autolink-headings"; import rehypePrettyCode from "rehype-pretty-code"; import rehypeSlug from "rehype-slug"; import remarkGfm from "remark-gfm"; export const Doc = defineDocumentType(() => ({ name: "Doc", filePathPattern: `docs/**/*.mdx`, contentType: "mdx", fields: { title: { type: "string", required: true, }, description: { type: "string", required: true, }, category: { type: "enum", options: ["getting-started", "api-reference", "guides", "examples"], required: true, }, order: { type: "number", required: false, }, published: { type: "boolean", default: true, }, tags: { type: "list", of: { type: "string" }, }, author: { type: "string", required: false, }, lastModified: { type: "date", required: false, }, version: { type: "string", required: false, }, }, computedFields: { slug: { type: "string", resolve: (doc) => doc._raw.flattenedPath.replace(/^docs\//, ""), }, slugAsParams: { type: "string", resolve: (doc) => doc._raw.flattenedPath.split("/").slice(1).join("/"), }, url: { type: "string", resolve: (doc) => `/docs/${doc._raw.flattenedPath.replace(/^docs\//, "")}`, }, headings: { type: "json", resolve: async (doc) => { const headingsRegex = /^(#{1,6})\s+(.+)$/gm; const headings: { level: number; text: string; slug: string }[] = []; let match; while ((match = headingsRegex.exec(doc.body.raw)) !== null) { const level = match[1].length; const text = match[2]; const slug = text .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/(^-|-$)/g, ""); headings.push({ level, text, slug }); } return headings; }, }, }, })); export default makeSource({ contentDirPath: "./content", documentTypes: [Doc], mdx: { remarkPlugins: [remarkGfm], rehypePlugins: [ rehypeSlug, [ rehypePrettyCode, { theme: { dark: "github-dark-dimmed", light: "github-light", }, keepBackground: false, }, ], [ rehypeAutolinkHeadings, { behavior: "wrap", properties: { className: ["anchor"], }, }, ], ], }, }); ``` **components/MDXComponents.tsx:** ```typescript import Image from 'next/image' import Link from 'next/link' import { Callout } from './Callout' import { CodeBlock } from './CodeBlock' import { CodePlayground } from './CodePlayground' import { Tabs, Tab } from './Tabs' import { Steps } from './Steps' const components = { // HTML elements h1: ({ children, ...props }: any) => (

{children}

), h2: ({ children, ...props }: any) => (

{children}

), h3: ({ children, ...props }: any) => (

{children}

), h4: ({ children, ...props }: any) => (

{children}

), p: ({ children, ...props }: any) => (

{children}

), a: ({ href, children, ...props }: any) => { const isExternal = href?.startsWith('http') const Component = isExternal ? 'a' : Link return ( {children} ) }, ul: ({ children, ...props }: any) => ( ), ol: ({ children, ...props }: any) => (
    {children}
), li: ({ children, ...props }: any) => (
  • {children}
  • ), code: ({ children, className, ...props }: any) => { // Inline code if (!className) { return ( {children} ) } // Block code (handled by rehype-pretty-code) return {children} }, pre: ({ children, ...props }: any) => (
          {children}
        
    ), blockquote: ({ children, ...props }: any) => (
    {children}
    ), table: ({ children, ...props }: any) => (
    {children}
    ), thead: ({ children, ...props }: any) => ( {children} ), th: ({ children, ...props }: any) => ( {children} ), td: ({ children, ...props }: any) => ( {children} ), // Custom components Image: (props: any) => , Callout, CodeBlock, CodePlayground, Tabs, Tab, Steps, } export default components ``` **app/docs/[...slug]/page.tsx:** ```typescript import { notFound } from 'next/navigation' import { allDocs } from 'contentlayer/generated' import { Mdx } from '@/components/mdx-components' import { TableOfContents } from '@/components/toc' import { DocsPager } from '@/components/pager' import { Metadata } from 'next' interface DocPageProps { params: { slug: string[] } } async function getDocFromParams(params: DocPageProps['params']) { const slug = params.slug?.join('/') || '' const doc = allDocs.find((doc) => doc.slugAsParams === slug) if (!doc) { null } return doc } export async function generateMetadata({ params, }: DocPageProps): Promise { const doc = await getDocFromParams(params) if (!doc) { return {} } return { title: doc.title, description: doc.description, openGraph: { title: doc.title, description: doc.description, type: 'article', url: doc.url, }, twitter: { card: 'summary_large_image', title: doc.title, description: doc.description, }, } } export async function generateStaticParams(): Promise { return allDocs.map((doc) => ({ slug: doc.slugAsParams.split('/'), })) } export default async function DocPage({ params }: DocPageProps) { const doc = await getDocFromParams(params) if (!doc || !doc.published) { notFound() } return (

    {doc.title}

    {doc.description}

    {doc.version && (
    Version: {doc.version}
    )}
    ) } ``` **Example MDX Content (content/docs/getting-started/installation.mdx):** ````mdx --- title: Installation description: Get started with VoiceAssist in minutes category: getting-started order: 1 published: true tags: [setup, installation, quickstart] version: "1.0.0" --- import { Callout } from "@/components/Callout"; import { Steps } from "@/components/Steps"; import { Tabs, Tab } from "@/components/Tabs"; # Installation Get VoiceAssist up and running in your project in just a few minutes. Before you begin, make sure you have Node.js 18 or later installed. ## Prerequisites Ensure you have the following installed: - Node.js 18.0.0 or later - npm, yarn, or pnpm - A modern browser (Chrome, Firefox, Safari, or Edge) ## Installation Steps ### Install the package Choose your preferred package manager: ```bash npm install voiceassist ``` ```bash yarn add voiceassist ``` ```bash pnpm add voiceassist ``` ### Configure your API key Create a `.env.local` file in your project root: ```env VOICEASSIST_API_KEY=your_api_key_here VOICEASSIST_REGION=us-east-1 ``` ```` Never commit your API key to version control. Add `.env.local` to your `.gitignore` file. ### Initialize the client Create a new file `lib/voiceassist.ts`: ```typescript import { VoiceAssist } from "voiceassist"; export const voiceAssist = new VoiceAssist({ apiKey: process.env.VOICEASSIST_API_KEY!, region: process.env.VOICEASSIST_REGION, options: { language: "en-US", enableLogging: process.env.NODE_ENV === "development", }, }); ``` ### Verify installation Run the following command to verify your setup: ```bash npm run voiceassist:verify ``` ## Next Steps Now that you have VoiceAssist installed, you can: - Create your first voice command (see voice mode guides) - Explore the API reference (see /ai/api) - Check out examples (see usage documentation) Installation complete! You're ready to build amazing voice experiences. ``` --- ### Feature 1.2: Version Control Integration **Priority:** P1 (High) **Effort:** 2 days **Dependencies:** Git, GitHub/GitLab #### Specification Seamless version control integration that tracks content changes, enables collaboration, and maintains documentation history. Features include: - Automatic last-modified timestamps - Git-based edit history - "Edit on GitHub" links - Contributor attribution - Content versioning for different product versions #### Implementation **lib/git-utils.ts:** ```typescript import { execSync } from "child_process"; import path from "path"; export interface GitFileInfo { lastModified: Date; author: string; authorEmail: string; commitHash: string; contributors: Array<{ name: string; email: string; commits: number; }>; } export function getGitFileInfo(filePath: string): GitFileInfo | null { try { const absolutePath = path.join(process.cwd(), filePath); // Get last commit info const lastCommit = execSync(`git log -1 --format="%ai|%an|%ae|%H" -- "${absolutePath}"`, { encoding: "utf-8", }).trim(); if (!lastCommit) return null; const [dateStr, author, authorEmail, commitHash] = lastCommit.split("|"); // Get all contributors const contributorsOutput = execSync(`git log --format="%an|%ae" -- "${absolutePath}" | sort | uniq -c | sort -rn`, { encoding: "utf-8", }).trim(); const contributors = contributorsOutput .split("\n") .filter(Boolean) .map((line) => { const match = line.trim().match(/^\s*(\d+)\s+(.+)\|(.+)$/); if (!match) return null; return { name: match[2], email: match[3], commits: parseInt(match[1], 10), }; }) .filter(Boolean) as GitFileInfo["contributors"]; return { lastModified: new Date(dateStr), author, authorEmail, commitHash, contributors, }; } catch (error) { console.error("Error getting git info:", error); return null; } } export function getEditUrl(filePath: string, repo: string): string { const branch = "main"; return `https://github.com/${repo}/edit/${branch}/${filePath}`; } ``` **components/DocFooter.tsx:** ```typescript 'use client' import { useState, useEffect } from 'react' import Link from 'next/link' import { GitBranch, Clock, Users, Github } from 'lucide-react' interface DocFooterProps { filePath: string repository: string lastModified?: Date contributors?: Array<{ name: string email: string commits: number }> } export function DocFooter({ filePath, repository, lastModified, contributors = [], }: DocFooterProps) { const [mounted, setMounted] = useState(false) useEffect(() => { setMounted(true) }, []) const editUrl = `https://github.com/${repository}/edit/main/${filePath}` const historyUrl = `https://github.com/${repository}/commits/main/${filePath}` const topContributors = contributors.slice(0, 3) return (
    {/* Last Modified */}

    Last Updated

    {mounted && lastModified ? ( ) : ( Recently )}
    {/* Contributors */} {topContributors.length > 0 && (

    Contributors

    {topContributors.map((contributor) => ( {contributor.name} ))} {contributors.length > 3 && ( +{contributors.length - 3} more )}
    )}
    {/* Action Links */}
    Edit this page View history
    ) } ``` **Enhanced app/docs/[...slug]/page.tsx:** ```typescript import { DocFooter } from '@/components/DocFooter' import { getGitFileInfo } from '@/lib/git-utils' export default async function DocPage({ params }: DocPageProps) { const doc = await getDocFromParams(params) if (!doc || !doc.published) { notFound() } // Get git info const gitInfo = getGitFileInfo(doc._raw.sourceFilePath) return (
    {/* ... existing content ... */}
    {/* ... sidebar ... */}
    ) } ``` --- ### Feature 1.3: Multi-language Support **Priority:** P2 (Medium) **Effort:** 4 days **Dependencies:** next-intl or similar #### Specification Comprehensive internationalization support for documentation in multiple languages. Features include: - Language switcher in navigation - Translated content with language-specific URLs - Fallback to English for missing translations - RTL support for Arabic and Hebrew - Language-specific search indices #### Implementation **i18n/config.ts:** ```typescript export const locales = ["en", "ar", "es", "fr", "de", "zh"] as const; export type Locale = (typeof locales)[number]; export const defaultLocale: Locale = "en"; export const localeNames: Record = { en: "English", ar: "العربية", es: "Español", fr: "Français", de: "Deutsch", zh: "中文", }; export const localeDirections: Record = { en: "ltr", ar: "rtl", es: "ltr", fr: "ltr", de: "ltr", zh: "ltr", }; ``` **Enhanced contentlayer.config.ts:** ```typescript export const Doc = defineDocumentType(() => ({ name: "Doc", filePathPattern: `docs/**/*.mdx`, contentType: "mdx", fields: { // ... existing fields ... locale: { type: "enum", options: ["en", "ar", "es", "fr", "de", "zh"], default: "en", required: true, }, translationKey: { type: "string", required: false, description: "Key to link translations together", }, }, computedFields: { // ... existing computed fields ... localizedPath: { type: "string", resolve: (doc) => { const path = doc._raw.flattenedPath.replace(/^docs\//, ""); return doc.locale === "en" ? path : `${doc.locale}/${path}`; }, }, }, })); ``` **Content Structure:** ``` content/ ├── docs/ │ ├── en/ │ │ ├── getting-started/ │ │ │ └── installation.mdx │ │ └── api-reference/ │ ├── ar/ │ │ ├── getting-started/ │ │ │ └── installation.mdx │ │ └── api-reference/ │ └── es/ │ ├── getting-started/ │ └── api-reference/ ``` **components/LanguageSwitcher.tsx:** ```typescript 'use client' import { useState } from 'react' import { useRouter, usePathname } from 'next/navigation' import { Globe } from 'lucide-react' import { locales, localeNames, type Locale } from '@/i18n/config' export function LanguageSwitcher({ currentLocale }: { currentLocale: Locale }) { const [isOpen, setIsOpen] = useState(false) const router = useRouter() const pathname = usePathname() const switchLocale = (newLocale: Locale) => { const segments = pathname.split('/').filter(Boolean) // Remove current locale if present if (locales.includes(segments[0] as Locale)) { segments.shift() } // Add new locale if not default const newPath = newLocale === 'en' ? `/${segments.join('/')}` : `/${newLocale}/${segments.join('/')}` router.push(newPath) setIsOpen(false) } return (
    {isOpen && ( <>
    setIsOpen(false)} />
    {locales.map((locale) => ( ))}
    )}
    ) } ``` **app/[locale]/docs/[...slug]/page.tsx:** ```typescript import { notFound } from 'next/navigation' import { allDocs } from 'contentlayer/generated' import { locales, defaultLocale, type Locale } from '@/i18n/config' interface LocalizedDocPageProps { params: { locale: Locale slug: string[] } } async function getLocalizedDoc(locale: Locale, slug: string[]) { const slugStr = slug.join('/') // Try to find doc in requested locale let doc = allDocs.find( (d) => d.locale === locale && d.slugAsParams === slugStr ) // Fallback to English if not found if (!doc && locale !== defaultLocale) { doc = allDocs.find( (d) => d.locale === defaultLocale && d.slugAsParams === slugStr ) } return doc } export async function generateStaticParams() { const params: LocalizedDocPageProps['params'][] = [] for (const locale of locales) { const localeDocs = allDocs.filter((doc) => doc.locale === locale) for (const doc of localeDocs) { params.push({ locale, slug: doc.slugAsParams.split('/'), }) } } return params } export default async function LocalizedDocPage({ params, }: LocalizedDocPageProps) { const doc = await getLocalizedDoc(params.locale, params.slug) if (!doc || !doc.published) { notFound() } // Find available translations const translations = doc.translationKey ? allDocs.filter((d) => d.translationKey === doc.translationKey && d.locale !== doc.locale) : [] return (
    {translations.length > 0 && (

    This page is also available in:{' '} {translations.map((t, i) => ( {localeNames[t.locale as Locale]} {i < translations.length - 1 && ', '} ))}

    )} {/* ... rest of content ... */}
    ) } ``` --- ### Feature 1.4: Content Search (Algolia DocSearch) **Priority:** P0 (Critical) **Effort:** 2 days **Dependencies:** Algolia DocSearch #### Specification Fast, typo-tolerant search powered by Algolia DocSearch. Features include: - Instant search results as you type - Keyboard shortcuts (Cmd/Ctrl + K) - Categorized results (sections, pages, headings) - Search highlighting - Recent searches - Mobile-responsive search modal #### Implementation **components/Search.tsx:** ```typescript 'use client' import { useEffect, useRef, useCallback } from 'react' import { DocSearch } from '@docsearch/react' import '@docsearch/css' interface SearchProps { appId?: string apiKey?: string indexName?: string } export function Search({ appId = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID, apiKey = process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY, indexName = process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME, }: SearchProps) { const searchButtonRef = useRef(null) const onOpen = useCallback(() => { if (searchButtonRef.current) { searchButtonRef.current.click() } }, []) useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault() onOpen() } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [onOpen]) if (!appId || !apiKey || !indexName) { return null } return ( ) } export function SearchTrigger() { return ( ) } ``` **styles/docsearch.css (Customizations):** ```css :root { --docsearch-primary-color: rgb(59, 130, 246); --docsearch-text-color: rgb(31, 41, 55); --docsearch-spacing: 12px; --docsearch-icon-stroke-width: 1.4; --docsearch-highlight-color: var(--docsearch-primary-color); --docsearch-muted-color: rgb(107, 114, 128); --docsearch-container-background: rgba(0, 0, 0, 0.6); --docsearch-modal-background: rgb(255, 255, 255); --docsearch-searchbox-background: rgb(249, 250, 251); --docsearch-searchbox-focus-background: rgb(255, 255, 255); --docsearch-hit-color: rgb(31, 41, 55); --docsearch-hit-active-color: rgb(255, 255, 255); --docsearch-hit-background: rgb(255, 255, 255); --docsearch-hit-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); --docsearch-key-gradient: linear-gradient(-225deg, rgb(249, 250, 251), rgb(243, 244, 246)); --docsearch-key-shadow: inset 0 -2px 0 0 rgb(209, 213, 219), inset 0 0 1px 1px rgb(255, 255, 255), 0 1px 2px 1px rgba(0, 0, 0, 0.1); --docsearch-footer-background: rgb(249, 250, 251); } .dark { --docsearch-text-color: rgb(243, 244, 246); --docsearch-container-background: rgba(0, 0, 0, 0.8); --docsearch-modal-background: rgb(31, 41, 55); --docsearch-modal-shadow: inset 1px 1px 0 0 rgb(55, 65, 81), 0 3px 8px 0 rgba(0, 0, 0, 0.66); --docsearch-searchbox-background: rgb(17, 24, 39); --docsearch-searchbox-focus-background: rgb(0, 0, 0); --docsearch-hit-color: rgb(243, 244, 246); --docsearch-hit-shadow: none; --docsearch-hit-background: rgb(55, 65, 81); --docsearch-key-gradient: linear-gradient(-26.5deg, rgb(55, 65, 81), rgb(31, 41, 55)); --docsearch-key-shadow: inset 0 -2px 0 0 rgb(17, 24, 39), inset 0 0 1px 1px rgb(75, 85, 99), 0 2px 2px 0 rgba(0, 0, 0, 0.3); --docsearch-footer-background: rgb(17, 24, 39); --docsearch-footer-shadow: inset 0 1px 0 0 rgb(55, 65, 81), 0 -4px 8px 0 rgba(0, 0, 0, 0.2); --docsearch-muted-color: rgb(156, 163, 175); } .DocSearch-Button { margin: 0; border-radius: 0.5rem; } .DocSearch-Button-Keys { min-width: auto; padding: 0; } .DocSearch-Hit-icon { display: flex; align-items: center; } .DocSearch-Hit[aria-selected="true"] { background: var(--docsearch-primary-color); } .DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-title, .DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-path { color: var(--docsearch-hit-active-color); } ``` **Algolia Configuration (docsearch.json):** ```json { "index_name": "voiceassist", "start_urls": [ { "url": "https://docs.voiceassist.io/", "selectors_key": "homepage" }, { "url": "https://docs.voiceassist.io/docs/", "selectors_key": "docs" } ], "sitemap_urls": ["https://docs.voiceassist.io/sitemap.xml"], "selectors": { "homepage": { "lvl0": ".hero h1", "lvl1": ".features h2", "lvl2": ".features h3", "text": ".features p" }, "docs": { "lvl0": { "selector": ".sidebar .active", "default_value": "Documentation" }, "lvl1": "article h1", "lvl2": "article h2", "lvl3": "article h3", "lvl4": "article h4", "lvl5": "article h5", "text": "article p, article li" } }, "selectors_exclude": [".hash-link", ".table-of-contents", ".DocSearch-Button", "footer"], "custom_settings": { "attributesForFaceting": ["language", "version", "category"], "attributesToRetrieve": ["hierarchy", "content", "anchor", "url", "url_without_anchor", "type"], "attributesToHighlight": ["hierarchy", "content"], "attributesToSnippet": ["content:20"], "highlightPreTag": "", "highlightPostTag": "", "minWordSizefor1Typo": 3, "minWordSizefor2Typos": 7, "hitsPerPage": 20, "maxValuesPerFacet": 100 } } ``` --- ### Feature 1.5: Table of Contents Generation **Priority:** P1 (High) **Effort:** 1 day **Dependencies:** None #### Specification Automatic table of contents generation from document headings with: - Smooth scroll navigation - Active heading highlighting - Nested hierarchy support (H2-H4) - Collapsible sections - Sticky positioning #### Implementation **components/TableOfContents.tsx:** ```typescript 'use client' import { useEffect, useState, useCallback } from 'react' import { cn } from '@/lib/utils' interface Heading { level: number text: string slug: string } interface TableOfContentsProps { headings: Heading[] } export function TableOfContents({ headings }: TableOfContentsProps) { const [activeId, setActiveId] = useState('') const onClick = useCallback((e: React.MouseEvent, slug: string) => { e.preventDefault() const element = document.getElementById(slug) if (element) { const offset = 80 // Account for sticky header const elementPosition = element.getBoundingClientRect().top const offsetPosition = elementPosition + window.pageYOffset - offset window.scrollTo({ top: offsetPosition, behavior: 'smooth', }) // Update URL without jumping window.history.pushState(null, '', `#${slug}`) } }, []) useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { setActiveId(entry.target.id) } }) }, { rootMargin: '-100px 0px -80% 0px', } ) headings.forEach(({ slug }) => { const element = document.getElementById(slug) if (element) { observer.observe(element) } }) return () => observer.disconnect() }, [headings]) if (!headings || headings.length === 0) { return null } // Filter to only show H2 and H3 headings const visibleHeadings = headings.filter((h) => h.level >= 2 && h.level <= 3) if (visibleHeadings.length === 0) { return null } return ( ) } ``` **Enhanced contentlayer.config.ts (Heading extraction):** ```typescript export const Doc = defineDocumentType(() => ({ // ... existing fields ... computedFields: { // ... existing computed fields ... headings: { type: "json", resolve: async (doc) => { const headingsRegex = /^(#{1,6})\s+(.+)$/gm; const headings: { level: number; text: string; slug: string }[] = []; let match; while ((match = headingsRegex.exec(doc.body.raw)) !== null) { const level = match[1].length; const text = match[2] .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // Remove markdown links .replace(/`([^`]+)`/g, "$1") // Remove code formatting .replace(/<[^>]+>/g, "") // Remove HTML tags .trim(); const slug = text .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/(^-|-$)/g, ""); headings.push({ level, text, slug }); } return headings; }, }, }, })); ``` **lib/utils.ts (cn helper):** ```typescript import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } ``` --- ## Interactive Elements Features ### Feature 2.1: Code Playgrounds **Priority:** P1 (High) **Effort:** 5 days **Dependencies:** Sandpack or CodeSandbox #### Specification Interactive code playgrounds embedded in documentation that allow users to: - Edit code in real-time - See live previews - Switch between files in multi-file examples - Reset to original state - Copy code snippets - Open in CodeSandbox/StackBlitz #### Implementation **components/CodePlayground.tsx:** ```typescript 'use client' import { Sandpack, SandpackProps } from '@codesandbox/sandpack-react' import { useState } from 'react' import { Copy, RotateCcw, ExternalLink } from 'lucide-react' interface CodePlaygroundProps { files: Record template?: 'react' | 'react-ts' | 'vanilla' | 'vanilla-ts' dependencies?: Record options?: { showLineNumbers?: boolean showTabs?: boolean showConsole?: boolean editorHeight?: number } } export function CodePlayground({ files, template = 'react-ts', dependencies = {}, options = {}, }: CodePlaygroundProps) { const [key, setKey] = useState(0) const [copied, setCopied] = useState(false) const { showLineNumbers = true, showTabs = true, showConsole = false, editorHeight = 400, } = options const handleReset = () => { setKey((prev) => prev + 1) } const handleCopy = () => { const mainFile = files['/App.tsx'] || files['/index.js'] || Object.values(files)[0] navigator.clipboard.writeText(mainFile) setCopied(true) setTimeout(() => setCopied(false), 2000) } const handleOpenInCodeSandbox = () => { // Implementation would create a CodeSandbox URL const parameters = { files, template, } const url = `https://codesandbox.io/api/v1/sandboxes/define?parameters=${ btoa(JSON.stringify(parameters)) }` window.open(url, '_blank') } return (
    {/* Toolbar */}
    Live Example
    {/* Sandpack */} {copied && (
    Copied!
    )}
    ) } ``` **Usage in MDX:** ```mdx --- title: Voice Recognition Example --- import { CodePlayground } from "@/components/CodePlayground"; # Voice Recognition Example Here's a live example of implementing voice recognition with VoiceAssist: { setIsListening(true) const voice = new VoiceAssist({ onTranscript: (text) => setTranscript(text), }) await voice.start() } const handleStop = () => { setIsListening(false) } return (

    Voice Recognition Demo

    {transcript && (
    Transcript: {transcript}
    )}
    ) }`, '/styles.css': `body { font-family: system-ui, sans-serif; margin: 0; padding: 0; } button { padding: 12px 24px; font-size: 16px; background: #0070f3; color: white; border: none; border-radius: 6px; cursor: pointer; } button:hover { background: #0051cc; }`, }} template="react-ts" dependencies={{ 'voiceassist': '^1.0.0', }} options={{ showLineNumbers: true, showTabs: true, editorHeight: 450, }} /> Try editing the code above to customize the behavior! ``` --- ### Feature 2.2: Interactive Examples **Priority:** P1 (High) **Effort:** 3 days **Dependencies:** None #### Specification Interactive, self-contained examples that demonstrate specific features without requiring a full code playground. These are lighter weight and ideal for showing API behavior, configuration options, or UI components. #### Implementation **components/InteractiveExample.tsx:** ```typescript 'use client' import { useState, useEffect } from 'react' import { Play, Pause, RotateCcw } from 'lucide-react' interface InteractiveExampleProps { title: string description?: string children: React.ReactNode code?: string controls?: boolean autoPlay?: boolean } export function InteractiveExample({ title, description, children, code, controls = true, autoPlay = false, }: InteractiveExampleProps) { const [isPlaying, setIsPlaying] = useState(autoPlay) const [showCode, setShowCode] = useState(false) return (
    {/* Header */}

    {title}

    {description && (

    {description}

    )}
    {/* Preview */}
    {children}
    {/* Controls */} {(controls || code) && (
    {controls && (
    )} {code && ( )}
    )} {/* Code */} {showCode && code && (
                {code}
              
    )}
    ) } ``` **components/VoiceWaveform.tsx (Example Interactive Component):** ```typescript 'use client' import { useEffect, useRef, useState } from 'react' interface VoiceWaveformProps { isActive?: boolean color?: string bars?: number } export function VoiceWaveform({ isActive = true, color = 'rgb(59, 130, 246)', bars = 40, }: VoiceWaveformProps) { const canvasRef = useRef(null) const animationRef = useRef() const [heights, setHeights] = useState([]) useEffect(() => { // Initialize bar heights setHeights(Array(bars).fill(0).map(() => Math.random() * 0.3 + 0.1)) }, [bars]) useEffect(() => { const canvas = canvasRef.current if (!canvas) return const ctx = canvas.getContext('2d') if (!ctx) return const dpr = window.devicePixelRatio || 1 const rect = canvas.getBoundingClientRect() canvas.width = rect.width * dpr canvas.height = rect.height * dpr ctx.scale(dpr, dpr) const barWidth = rect.width / bars const maxHeight = rect.height const animate = () => { ctx.clearRect(0, 0, rect.width, rect.height) heights.forEach((height, i) => { const x = i * barWidth const barHeight = height * maxHeight ctx.fillStyle = color ctx.fillRect( x + barWidth * 0.2, (maxHeight - barHeight) / 2, barWidth * 0.6, barHeight ) }) // Update heights if (isActive) { setHeights((prev) => prev.map((h) => { const delta = (Math.random() - 0.5) * 0.1 return Math.max(0.1, Math.min(1, h + delta)) }) ) } animationRef.current = requestAnimationFrame(animate) } animate() return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current) } } }, [isActive, color, bars, heights]) return ( ) } ``` **Usage in MDX:** ```mdx import { InteractiveExample } from "@/components/InteractiveExample"; import { VoiceWaveform } from "@/components/VoiceWaveform"; ## Voice Waveform Visualization ) }`} > ``` --- ### Feature 2.3: Video Tutorials **Priority:** P2 (Medium) **Effort:** 2 days **Dependencies:** None #### Specification Embedded video tutorials with enhanced player controls and features: - Custom video player with chapters - Playback speed control - Timestamp links - Transcript display - Video thumbnails with lazy loading - YouTube/Vimeo embed support #### Implementation **components/VideoPlayer.tsx:** ```typescript 'use client' import { useState, useRef, useEffect } from 'react' import { Play, Pause, Volume2, VolumeX, Maximize, Settings } from 'lucide-react' interface Chapter { time: number title: string } interface VideoPlayerProps { src?: string youtubeId?: string vimeoId?: string poster?: string title: string duration?: string chapters?: Chapter[] transcript?: string } export function VideoPlayer({ src, youtubeId, vimeoId, poster, title, duration, chapters = [], transcript, }: VideoPlayerProps) { const [isPlaying, setIsPlaying] = useState(false) const [showTranscript, setShowTranscript] = useState(false) const [currentTime, setCurrentTime] = useState(0) const videoRef = useRef(null) const embedUrl = youtubeId ? `https://www.youtube.com/embed/${youtubeId}` : vimeoId ? `https://player.vimeo.com/video/${vimeoId}` : null const handleChapterClick = (time: number) => { if (videoRef.current) { videoRef.current.currentTime = time setCurrentTime(time) } } const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60) const secs = Math.floor(seconds % 60) return `${mins}:${secs.toString().padStart(2, '0')}` } if (embedUrl) { return (