setIsOpen(false)}
/>
)}
{/* Sidebar */}
>
)
}
```
**lib/navigation.ts:**
```typescript
export const navigation: NavItem[] = [
{
title: "Getting Started",
children: [
{ title: "Introduction", href: "/docs/introduction" },
{ title: "Installation", href: "/docs/installation" },
{ title: "Quick Start", href: "/docs/quick-start" },
{ title: "Configuration", href: "/docs/configuration" },
],
},
{
title: "Guides",
children: [
{ title: "Voice Commands", href: "/docs/guides/voice-commands" },
{ title: "Speech Recognition", href: "/docs/guides/speech-recognition" },
{ title: "Text-to-Speech", href: "/docs/guides/text-to-speech" },
{ title: "Custom Intents", href: "/docs/guides/custom-intents", badge: "new" },
{ title: "Error Handling", href: "/docs/guides/error-handling" },
],
},
{
title: "API Reference",
children: [
{ title: "VoiceAssist", href: "/docs/api/voiceassist" },
{ title: "SpeechRecognition", href: "/docs/api/speech-recognition" },
{ title: "TextToSpeech", href: "/docs/api/text-to-speech" },
{ title: "IntentDetector", href: "/docs/api/intent-detector", badge: "beta" },
{ title: "Configuration", href: "/docs/api/configuration" },
],
},
{
title: "Examples",
children: [
{ title: "Basic Usage", href: "/docs/examples/basic" },
{ title: "React Integration", href: "/docs/examples/react" },
{ title: "Vue Integration", href: "/docs/examples/vue" },
{ title: "Next.js App", href: "/docs/examples/nextjs" },
],
},
];
```
---
### Feature 3.2: Breadcrumbs
**Priority:** P1 (High)
**Effort:** 1 day
**Dependencies:** None
#### Specification
Breadcrumb navigation showing the current page hierarchy with:
- Auto-generation from URL structure
- Custom labels from frontmatter
- Mobile-responsive design
- Structured data for SEO
- Home icon support
#### Implementation
**components/Breadcrumbs.tsx:**
```typescript
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { ChevronRight, Home } from 'lucide-react'
import { Fragment } from 'react'
interface BreadcrumbItem {
label: string
href: string
}
interface BreadcrumbsProps {
customItems?: BreadcrumbItem[]
}
export function Breadcrumbs({ customItems }: BreadcrumbsProps) {
const pathname = usePathname()
const items: BreadcrumbItem[] = customItems || generateBreadcrumbs(pathname)
if (items.length <= 1) {
return null
}
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: items.map((item, index) => ({
'@type': 'ListItem',
position: index + 1,
name: item.label,
item: `https://docs.voiceassist.io${item.href}`,
})),
}
return (
<>
>
)
}
function generateBreadcrumbs(pathname: string): BreadcrumbItem[] {
const segments = pathname.split('/').filter(Boolean)
const items: BreadcrumbItem[] = [
{ label: 'Home', href: '/' },
]
let currentPath = ''
for (const segment of segments) {
currentPath += `/${segment}`
// Convert kebab-case to Title Case
const label = segment
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
items.push({
label,
href: currentPath,
})
}
return items
}
```
**Usage in layout:**
```typescript
import { Breadcrumbs } from '@/components/Breadcrumbs'
export default function DocsLayout({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
---
### Feature 3.3: Previous/Next Links (DocsPager)
**Priority:** P1 (High)
**Effort:** 1 day
**Dependencies:** None
#### Specification
Previous/next page navigation at the bottom of documentation pages:
- Auto-generated from navigation structure
- Shows page titles and descriptions
- Keyboard navigation support (arrow keys)
- Mobile-friendly design
#### Implementation
**components/DocsPager.tsx:**
```typescript
'use client'
import Link from 'next/link'
import { ChevronLeft, ChevronRight } from 'lucide-react'
import { useEffect } from 'react'
import { navigation } from '@/lib/navigation'
interface Doc {
url: string
title: string
description?: string
}
interface DocsPagerProps {
doc: Doc
}
function flattenNavigation(items: NavItem[]): { title: string; href: string }[] {
const result: { title: string; href: string }[] = []
for (const item of items) {
if (item.href) {
result.push({ title: item.title, href: item.href })
}
if (item.children) {
result.push(...flattenNavigation(item.children))
}
}
return result
}
export function DocsPager({ doc }: DocsPagerProps) {
const allDocs = flattenNavigation(navigation)
const currentIndex = allDocs.findIndex((item) => item.href === doc.url)
const prev = currentIndex > 0 ? allDocs[currentIndex - 1] : null
const next = currentIndex < allDocs.length - 1 ? allDocs[currentIndex + 1] : null
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return
if (e.key === 'ArrowLeft' && prev) {
window.location.href = prev.href
} else if (e.key === 'ArrowRight' && next) {
window.location.href = next.href
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [prev, next])
if (!prev && !next) {
return null
}
return (
{prev ? (
) : (
)}
{next && (
)}
)
}
```
---
### Feature 3.4: Search with Shortcuts
**Priority:** P0 (Critical)
**Effort:** Covered in Feature 1.4
**Dependencies:** Algolia DocSearch
See Feature 1.4 for full implementation.
---
### Feature 3.5: Dark Mode Toggle
**Priority:** P1 (High)
**Effort:** 1 day
**Dependencies:** next-themes
#### Specification
Theme toggle with:
- System preference detection
- Smooth transitions
- Persistent preference storage
- Multiple themes (light, dark, system)
- Icon animations
#### Implementation
**app/providers.tsx:**
```typescript
'use client'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import { type ThemeProviderProps } from 'next-themes/dist/types'
export function Providers({ children, ...props }: ThemeProviderProps) {
return (
{children}
)
}
```
**components/ThemeToggle.tsx:**
```typescript
'use client'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
import { Moon, Sun, Monitor } from 'lucide-react'
export function ThemeToggle() {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return (
)
}
return (
)
}
```
**app/layout.tsx:**
```typescript
import { Providers } from './providers'
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
```
---
## Technical Implementation
### Next.js 14 Configuration
**next.config.js:**
```javascript
const { withContentlayer } = require("next-contentlayer");
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
images: {
domains: ["docs.voiceassist.io"],
formats: ["image/avif", "image/webp"],
},
async redirects() {
return [
{
source: "/docs",
destination: "/docs/introduction",
permanent: true,
},
];
},
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "X-XSS-Protection",
value: "1; mode=block",
},
],
},
];
},
};
module.exports = withContentlayer(nextConfig);
```
### TypeScript Configuration
**tsconfig.json:**
```json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"],
"contentlayer/generated": ["./.contentlayer/generated"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".contentlayer/generated"],
"exclude": ["node_modules"]
}
```
### Tailwind Configuration
**tailwind.config.ts:**
```typescript
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: "class",
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./content/**/*.{md,mdx}",
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
},
typography: (theme: any) => ({
DEFAULT: {
css: {
maxWidth: "none",
color: theme("colors.gray.700"),
a: {
color: theme("colors.blue.600"),
"&:hover": {
color: theme("colors.blue.800"),
},
},
"h1, h2, h3, h4": {
scrollMarginTop: "5rem",
},
},
},
dark: {
css: {
color: theme("colors.gray.300"),
a: {
color: theme("colors.blue.400"),
"&:hover": {
color: theme("colors.blue.300"),
},
},
"h1, h2, h3, h4": {
color: theme("colors.gray.100"),
},
},
},
}),
},
},
plugins: [require("@tailwindcss/typography")],
};
export default config;
```
---
## Content Structure
### Recommended Directory Organization
```
content/
├── docs/
│ ├── en/ # English content
│ │ ├── 01-getting-started/
│ │ │ ├── 01-introduction.mdx
│ │ │ ├── 02-installation.mdx
│ │ │ ├── 03-quick-start.mdx
│ │ │ └── 04-configuration.mdx
│ │ ├── 02-guides/
│ │ │ ├── 01-voice-commands.mdx
│ │ │ ├── 02-speech-recognition.mdx
│ │ │ └── 03-text-to-speech.mdx
│ │ ├── 03-api-reference/
│ │ │ ├── 01-voiceassist.mdx
│ │ │ ├── 02-speech-recognition.mdx
│ │ │ └── 03-text-to-speech.mdx
│ │ └── 04-examples/
│ │ ├── 01-basic.mdx
│ │ ├── 02-react.mdx
│ │ └── 03-nextjs.mdx
│ └── ar/ # Arabic content
│ └── ... (same structure)
├── blog/
│ ├── 2024-01-15-announcement.mdx
│ └── 2024-02-01-new-features.mdx
└── snippets/ # Reusable content snippets
├── installation-warning.mdx
└── api-key-setup.mdx
```
### Frontmatter Schema
```yaml
---
title: Page Title # Required
description: Page description # Required
category: getting-started # Required
order: 1 # Optional, for sorting
published: true # Default: true
tags: [setup, installation] # Optional
author: John Doe # Optional
lastModified: 2024-01-15 # Optional, auto-populated from git
version: "1.0.0" # Optional
locale: en # Default: en
translationKey: installation # Optional, links translations
---
```
---
## Deployment Strategy
### Vercel Deployment (Recommended)
**vercel.json:**
```json
{
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install",
"framework": "nextjs",
"regions": ["iad1"],
"github": {
"silent": true
},
"env": {
"NEXT_PUBLIC_ALGOLIA_APP_ID": "@algolia-app-id",
"NEXT_PUBLIC_ALGOLIA_SEARCH_KEY": "@algolia-search-key",
"NEXT_PUBLIC_ALGOLIA_INDEX_NAME": "voiceassist"
}
}
```
### GitHub Actions CI/CD
**.github/workflows/deploy.yml:**
```yaml
name: Deploy Documentation
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "18"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Build
run: npm run build
env:
NEXT_PUBLIC_ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
NEXT_PUBLIC_ALGOLIA_SEARCH_KEY: ${{ secrets.ALGOLIA_SEARCH_KEY }}
- name: Run Lighthouse CI
run: npm run lighthouse
if: github.event_name == 'pull_request'
```
### Performance Optimization
**next.config.js additions:**
```javascript
const nextConfig = {
// ... existing config ...
compiler: {
removeConsole: process.env.NODE_ENV === "production",
},
experimental: {
optimizeCss: true,
optimizePackageImports: ["lucide-react", "@codesandbox/sandpack-react"],
},
};
```
### Sitemap Generation
**app/sitemap.ts:**
```typescript
import { MetadataRoute } from "next";
import { allDocs } from "contentlayer/generated";
export default function sitemap(): MetadataRoute.Sitemap {
const docs = allDocs
.filter((doc) => doc.published)
.map((doc) => ({
url: `https://docs.voiceassist.io${doc.url}`,
lastModified: doc.lastModified || new Date(),
changeFrequency: "weekly" as const,
priority: doc.category === "getting-started" ? 1.0 : 0.8,
}));
return [
{
url: "https://docs.voiceassist.io",
lastModified: new Date(),
changeFrequency: "daily",
priority: 1.0,
},
...docs,
];
}
```
### Analytics Integration
**components/Analytics.tsx:**
```typescript
"use client";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
export function Analytics() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (typeof window !== "undefined" && window.gtag) {
window.gtag("config", "GA_MEASUREMENT_ID", {
page_path: pathname + searchParams.toString(),
});
}
}, [pathname, searchParams]);
return null;
}
```
---
## Summary
This comprehensive specification document outlines 15 features across three categories (Content Management, Interactive Elements, and Navigation) for the VoiceAssist Documentation Site. The implementation uses:
- **Next.js 14** with App Router for modern React patterns
- **Contentlayer** for type-safe MDX content management
- **Tailwind CSS** for utility-first styling
- **Algolia DocSearch** for powerful search
- **Sandpack** for interactive code playgrounds
- **Mermaid.js** for diagrams
Each feature includes:
- Priority and effort estimates
- Detailed specifications
- Complete implementation code
- Usage examples in MDX
- Integration with the overall architecture
The documentation site is designed to be:
- **Performant:** Sub-second page loads, optimized assets
- **Accessible:** WCAG 2.1 AA compliant
- **Scalable:** Supports 1000+ pages
- **Developer-friendly:** Type-safe, hot reload, clear architecture
- **SEO-optimized:** Semantic HTML, sitemaps, structured data
Total estimated effort: **28 days** for full implementation.
---
**Document Status:** Complete
**Total Word Count:** 8,500+ words
**Code Examples:** 30+ complete implementations
**Features Covered:** 15/15 (100%)
6:["slug","client-implementation/DOCS_SITE_FEATURE_SPECS","c"]
0:["X7oMT3VrOffzp0qvbeOas",[[["",{"children":["docs",{"children":[["slug","client-implementation/DOCS_SITE_FEATURE_SPECS","c"],{"children":["__PAGE__?{\"slug\":[\"client-implementation\",\"DOCS_SITE_FEATURE_SPECS\"]}",{}]}]}]},"$undefined","$undefined",true],["",{"children":["docs",{"children":[["slug","client-implementation/DOCS_SITE_FEATURE_SPECS","c"],{"children":["__PAGE__",{},[["$L1",["$","div",null,{"children":[["$","div",null,{"className":"mb-6 flex items-center justify-between gap-4","children":[["$","div",null,{"children":[["$","p",null,{"className":"text-sm text-gray-500 dark:text-gray-400","children":"Docs / Raw"}],["$","h1",null,{"className":"text-3xl font-bold text-gray-900 dark:text-white","children":"Docs Site Feature Specs"}],["$","p",null,{"className":"text-sm text-gray-600 dark:text-gray-400","children":["Sourced from"," ",["$","code",null,{"className":"font-mono text-xs","children":["docs/","client-implementation/DOCS_SITE_FEATURE_SPECS.md"]}]]}]]}],["$","a",null,{"href":"https://github.com/mohammednazmy/VoiceAssist/edit/main/docs/client-implementation/DOCS_SITE_FEATURE_SPECS.md","target":"_blank","rel":"noreferrer","className":"inline-flex items-center gap-2 rounded-md border border-gray-200 dark:border-gray-700 px-3 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:border-primary-500 dark:hover:border-primary-400 hover:text-primary-700 dark:hover:text-primary-300","children":"Edit on GitHub"}]]}],["$","div",null,{"className":"rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6","children":["$","$L2",null,{"content":"$3"}]}],["$","div",null,{"className":"mt-6 flex flex-wrap gap-2 text-sm","children":[["$","$L4",null,{"href":"/reference/all-docs","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"← All documentation"}],["$","$L4",null,{"href":"/","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"Home"}]]}]]}],null],null],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children","$6","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/7f586cdbbaa33ff7.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"__className_f367f3 h-full bg-white dark:bg-gray-900","children":[["$","a",null,{"href":"#main-content","className":"skip-to-content","children":"Skip to main content"}],["$","$L8",null,{"children":[["$","$L9",null,{}],["$","$La",null,{}],["$","main",null,{"id":"main-content","className":"lg:pl-64","role":"main","aria-label":"Documentation content","children":["$","$Lb",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]]}]]}]}]],null],null],["$Lc",null]]]]
c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"Docs Site Feature Specs | Docs | VoiceAssist Docs"}],["$","meta","3",{"name":"description","content":"**Document Version:** 1.0.0"}],["$","meta","4",{"name":"keywords","content":"VoiceAssist,documentation,medical AI,voice assistant,healthcare,HIPAA,API"}],["$","meta","5",{"name":"robots","content":"index, follow"}],["$","meta","6",{"name":"googlebot","content":"index, follow"}],["$","link","7",{"rel":"canonical","href":"https://assistdocs.asimo.io"}],["$","meta","8",{"property":"og:title","content":"VoiceAssist Documentation"}],["$","meta","9",{"property":"og:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","10",{"property":"og:url","content":"https://assistdocs.asimo.io"}],["$","meta","11",{"property":"og:site_name","content":"VoiceAssist Docs"}],["$","meta","12",{"property":"og:type","content":"website"}],["$","meta","13",{"name":"twitter:card","content":"summary"}],["$","meta","14",{"name":"twitter:title","content":"VoiceAssist Documentation"}],["$","meta","15",{"name":"twitter:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","16",{"name":"next-size-adjust"}]]
1:null