Skip to content

Docs homepage redesign #1718

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Repository Overview
This is the Optimism Documentation website repository that powers docs.optimism.io - the official technical documentation for the Optimism Collective, covering the OP Stack, Superchain, and interoperability features.

## Tech Stack
- **Framework**: Next.js 14.2.21 with React 18.2.0
- **Documentation Engine**: Nextra 2.13.2 (docs theme)
- **Language**: TypeScript
- **Package Manager**: pnpm (required - do not use npm or yarn)
- **Content Format**: MDX (Markdown with React components)
- **Deployment**: Netlify

## Essential Commands

### Development
```bash
pnpm dev # Start development server at localhost:3001
pnpm build # Create production build
```
Comment on lines +20 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect dev server port

The script "dev" in package.json is next dev without a -p flag, which defaults to port 3000, not 3001. Update the docs or the script for consistency.

Apply one of:

- pnpm dev          # Start development server at localhost:3001
+ pnpm dev          # Start development server at http://localhost:3000

or change package.json script:

- "dev": "next dev",
+ "dev": "next dev -p 3001",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pnpm dev # Start development server at localhost:3001
pnpm build # Create production build
```
pnpm dev # Start development server at http://localhost:3000
pnpm build # Create production build
🤖 Prompt for AI Agents
In CLAUDE.md around lines 20 to 22, the docs state "pnpm dev" starts the dev
server at localhost:3001 but the package.json script uses "next dev" which
defaults to port 3000; fix by making them consistent: either update CLAUDE.md to
say the dev server runs on localhost:3000, or modify the package.json "dev"
script to "next dev -p 3001" (or equivalent environment config) so the script
actually starts on 3001.


### Quality Checks (Run before committing)
```bash
pnpm lint # Run all linting (ESLint + spellcheck + breadcrumbs + redirects + metadata)
pnpm fix # Auto-fix all fixable issues (runs on pre-push automatically)
```

### Individual Linting Commands
```bash
pnpm spellcheck:lint # Check spelling
pnpm spellcheck:fix # Add words to dictionary (words.txt)
pnpm lint:eslint # Run ESLint
pnpm lint:breadcrumbs # Validate breadcrumb structure
pnpm lint:redirects # Check redirect configuration
pnpm lint:metadata # Validate page metadata
```
Comment on lines +31 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Listed per-tool lint scripts don’t exist

The docs advertise pnpm lint:eslint, lint:breadcrumbs, lint:redirects, lint:metadata, but package.json doesn’t define them. Either document the actual commands or add script aliases.

Option A (fix docs):

- pnpm lint:eslint         # Run ESLint
- pnpm lint:breadcrumbs    # Validate breadcrumb structure
- pnpm lint:redirects      # Check redirect configuration
- pnpm lint:metadata       # Validate page metadata
+ pnpm lint                # ESLint + spellcheck + breadcrumbs + redirects + metadata
+ pnpm check-breadcrumbs  # Validate breadcrumb structure
+ pnpm check-redirects    # Check redirect configuration
+ pnpm validate-metadata  # Validate page metadata

Option B (add scripts to package.json):

   "scripts": {
+    "lint:eslint": "eslint . --ext mdx",
+    "lint:breadcrumbs": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/breadcrumbs.ts",
+    "lint:redirects": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/redirects.ts",
+    "lint:metadata": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/metadata-validator.ts",

Check which path you prefer and keep docs aligned:


🏁 Script executed:

#!/bin/bash
# Show available scripts
cat package.json | jq '.scripts'

Length of output: 1376


Sync CLI docs with package.json scripts

The commands listed in CLAUDE.md (lines 31–38) reference scripts that don’t exist in package.json. Choose one of the following to restore alignment:

Option A – Update CLAUDE.md:

--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -31,7 +31,11 @@
 pnpm spellcheck:lint     # Check spelling
 pnpm spellcheck:fix      # Add words to dictionary (words.txt)
- pnpm lint:eslint         # Run ESLint
- pnpm lint:breadcrumbs    # Validate breadcrumb structure
- pnpm lint:redirects      # Check redirect configuration
- pnpm lint:metadata       # Validate page metadata
+ pnpm lint                # Run ESLint, spellcheck, breadcrumbs, redirects, metadata, link-checker
+ pnpm check-breadcrumbs   # Validate breadcrumb structure
+ pnpm check-redirects     # Check redirect configuration
+ pnpm validate-metadata   # Validate page metadata
+ pnpm link-checker        # Check for broken links

Option B – Add missing aliases to package.json scripts:

--- a/package.json
+++ b/package.json
@@   "scripts": {
+    "lint:eslint": "eslint . --ext mdx --max-warnings 0",
+    "lint:breadcrumbs": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/breadcrumbs.ts",
+    "lint:redirects": "NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/redirects.ts",
+    "lint:metadata": "CHANGED_FILES=$(git diff --name-only --cached) NODE_NO_WARNINGS=1 node --loader ts-node/esm utils/metadata-validator.ts",

Let me know which approach you prefer so the docs and scripts stay in sync.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```bash
pnpm spellcheck:lint # Check spelling
pnpm spellcheck:fix # Add words to dictionary (words.txt)
pnpm lint:eslint # Run ESLint
pnpm lint:breadcrumbs # Validate breadcrumb structure
pnpm lint:redirects # Check redirect configuration
pnpm lint:metadata # Validate page metadata
```
pnpm spellcheck:lint # Check spelling
pnpm spellcheck:fix # Add words to dictionary (words.txt)
pnpm lint # Run ESLint, spellcheck, breadcrumbs, redirects, metadata, link-checker
pnpm check-breadcrumbs # Validate breadcrumb structure
pnpm check-redirects # Check redirect configuration
pnpm validate-metadata # Validate page metadata
pnpm link-checker # Check for broken links


## Architecture & Structure

### Content Organization
```
pages/ # All documentation content (MDX files)
├── app-developers/ # Application developer guides
├── operators/ # Node & chain operator documentation
├── stack/ # OP Stack protocol documentation
├── superchain/ # Superchain network documentation
├── interop/ # Interoperability documentation
└── connect/ # Contributing guides and resources
```

Comment on lines +43 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add a language to the fenced code block (markdownlint MD040)

Specify a language (e.g., text) for the directory tree block to satisfy linters and improve readability.

-```
+```text
 pages/                    # All documentation content (MDX files)
 ├── app-developers/      # Application developer guides
 ├── operators/           # Node & chain operator documentation
 ├── stack/              # OP Stack protocol documentation
 ├── superchain/         # Superchain network documentation
 ├── interop/           # Interoperability documentation
 └── connect/           # Contributing guides and resources

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

43-43: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In CLAUDE.md around lines 43 to 52, the fenced code block showing the directory
tree is missing a language specifier which triggers markdownlint MD040; update
the opening fence to include a language (e.g., change totext) so the
block becomes a labeled code block, ensuring the opening and closing backticks
remain matched and no other content is altered.


</details>

<!-- fingerprinting:phantom:poseidon:chinchilla -->

<!-- This is an auto-generated comment by CodeRabbit -->

### Key Directories
- `components/`: Reusable React components for documentation
- `public/`: Static assets, images, and tutorial files
- `utils/`: Utility scripts for linting, validation, and build processes
- `providers/`: React context providers for global state

## Important Patterns

### MDX Page Structure
All documentation pages use MDX format with frontmatter metadata:
```mdx
---
title: Page Title
lang: en-US
description: Page description for SEO
---

import { ComponentName } from '@/components/ComponentName'

# Content here...
```

### Component Imports
Use the configured path alias for component imports:
```typescript
import { ComponentName } from '@/components/ComponentName'
```

### Adding New Documentation
1. Create MDX file in appropriate `pages/` subdirectory
2. Include required frontmatter (title, lang, description)
3. Run `pnpm lint` to validate metadata and content
4. Use existing components from `components/` directory when possible

### Spell Checking
- Custom dictionary maintained in `words.txt`
- Add technical terms using `pnpm spellcheck:fix`
- Spell checking runs automatically in the lint pipeline

## Git Workflow
- **Pre-push hook**: Automatically runs `pnpm fix` via Husky
- **Auto-commit**: Fixes are automatically committed if changes are made
- **No pre-commit hooks**: Only validation on push

## Special Features
- **Kapa.ai Widget**: AI assistant integrated for documentation queries
- **Algolia Search**: Full-text search across documentation
- **Feelback**: User feedback collection system
- **Growth Book**: A/B testing framework for feature experiments

## Common Tasks

### Adding a New Page
1. Create `.mdx` file in appropriate `pages/` directory
2. Add frontmatter with title, lang, and description
3. Write content using Markdown and import React components as needed
4. Run `pnpm dev` to preview
5. Run `pnpm lint` before committing

### Updating Components
- Components are in `components/` directory
- Follow existing patterns and TypeScript types
- Test component changes across multiple pages that use them

### Working with Images
- Place images in `public/img/` directory
- Reference using `/img/filename.ext` in MDX files
- Optimize images before adding to repository

## Notes
- The repository uses automated quality checks - always run `pnpm lint` before pushing
- Netlify handles deployment automatically on merge to main
- TypeScript is configured with relaxed strict mode - follow existing patterns
- MDX allows mixing Markdown with React components - leverage this for interactive content
21 changes: 17 additions & 4 deletions components/AskAIButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { RiSparkling2Fill } from '@remixicon/react';
import { useFeature } from '@growthbook/growthbook-react';
import { useEffect, useState } from 'react';

const AskAIButton = () => {
interface AskAIButtonProps {
fullWidth?: boolean;
large?: boolean;
id?: string;
}

const AskAIButton = ({ fullWidth = false, large = false, id = 'custom-ask-ai-button' }: AskAIButtonProps) => {
const [mounted, setMounted] = useState(false);
const enableDocsAIWidget = useFeature('enable_docs_ai_widget').on;

Expand All @@ -19,10 +25,17 @@ const AskAIButton = () => {
return null;
}

const baseClasses = 'nx-flex nx-gap-2 nx-items-center nx-rounded-lg nx-font-semibold nx-justify-center';
const sizeClasses = large
? 'nx-py-3 nx-px-6 nx-text-base'
: 'nx-py-1.5 nx-px-3 nx-text-sm';
const widthClasses = fullWidth ? 'nx-w-full' : '';
const iconSize = large ? 16 : 14;

return (
<button
id='custom-ask-ai-button'
className='nx-flex nx-gap-2 nx-items-center nx-py-1.5 nx-px-3 nx-rounded-lg nx-text-sm nx-font-semibold'
id={id}
className={`${baseClasses} ${sizeClasses} ${widthClasses}`}
style={{
backgroundColor: '#FF0420',
color: 'white',
Expand All @@ -32,7 +45,7 @@ const AskAIButton = () => {
}}
>
<span>Ask AI</span>
<RiSparkling2Fill size={14} />
<RiSparkling2Fill size={iconSize} />
</button>
);
};
Expand Down
273 changes: 273 additions & 0 deletions components/CustomHeader/index.tsx

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions components/LoadingBar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router';

const LoadingBar = () => {
const [loading, setLoading] = useState(false);
const [progress, setProgress] = useState(0);
const router = useRouter();

useEffect(() => {
let progressTimer: NodeJS.Timeout;
let finishTimer: NodeJS.Timeout;

Comment on lines +10 to +12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Timer types may be incompatible in the browser

Using NodeJS.Timeout can cause TS type errors in browser contexts. Prefer ReturnType for cross-env compatibility.

-    let progressTimer: NodeJS.Timeout;
-    let finishTimer: NodeJS.Timeout;
+    let progressTimer: ReturnType<typeof setTimeout> | null = null;
+    let finishTimer: ReturnType<typeof setTimeout> | null = null;
🤖 Prompt for AI Agents
In components/LoadingBar/index.tsx around lines 10 to 12, the timers are typed
as NodeJS.Timeout which can cause TypeScript incompatibilities in browser
builds; change the type to ReturnType<typeof setTimeout> (or use number for
setTimeout in DOM libs) for cross-environment compatibility, e.g., update both
progressTimer and finishTimer declarations to use ReturnType<typeof setTimeout>
and ensure any clearTimeout calls accept that type.

const handleStart = () => {
setLoading(true);
setProgress(0);

progressTimer = setTimeout(() => {
setProgress(70);
}, 200);
};

const handleComplete = () => {
setProgress(100);
finishTimer = setTimeout(() => {
setLoading(false);
setProgress(0);
}, 300);
};

Comment on lines +22 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Race condition: clear the pending progress timer on completion

If a route completes before the 200ms timer fires, progress can jump back to 70% after completion. Clear the progress timer in handleComplete to avoid flicker.

-    const handleComplete = () => {
-      setProgress(100);
-      finishTimer = setTimeout(() => {
-        setLoading(false);
-        setProgress(0);
-      }, 300);
-    };
+    const handleComplete = () => {
+      if (progressTimer) {
+        clearTimeout(progressTimer);
+        progressTimer = null;
+      }
+      setProgress(100);
+      finishTimer = setTimeout(() => {
+        setLoading(false);
+        setProgress(0);
+        finishTimer = null;
+      }, 300);
+    };

Also consider cancelling any pending finishTimer when a new navigation starts:

     const handleStart = () => {
       setLoading(true);
       setProgress(0);
-      
+      if (finishTimer) {
+        clearTimeout(finishTimer);
+        finishTimer = null;
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleComplete = () => {
setProgress(100);
finishTimer = setTimeout(() => {
setLoading(false);
setProgress(0);
}, 300);
};
const handleComplete = () => {
if (progressTimer) {
clearTimeout(progressTimer);
progressTimer = null;
}
setProgress(100);
finishTimer = setTimeout(() => {
setLoading(false);
setProgress(0);
finishTimer = null;
}, 300);
};
const handleStart = () => {
setLoading(true);
setProgress(0);
if (finishTimer) {
clearTimeout(finishTimer);
finishTimer = null;
}
// …rest of your start logic (e.g. setting progressTimer)
};
🤖 Prompt for AI Agents
In components/LoadingBar/index.tsx around lines 22-29, handleComplete sets a
finishTimer but does not clear any existing timers, which can let a previously
scheduled timeout fire after completion and reset progress (causing flicker);
modify handleComplete to clear the pending finishTimer (if set) before setting a
new one, and also clear/cancel any existing finishTimer when a new navigation
starts so no stale timeout can run later. Ensure finishTimer is stored in a
scope that both the navigation-start handler and handleComplete can access and
that you check for a non-null timer before calling clearTimeout, then null it
after clearing.

router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);

return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
clearTimeout(progressTimer);
clearTimeout(finishTimer);
};
}, [router]);

if (!loading) return null;

return (
<div className="nx-fixed nx-top-0 nx-left-0 nx-right-0 nx-z-50 nx-h-1 nx-bg-transparent">
<div
className="nx-h-full nx-bg-primary-500 nx-transition-all nx-duration-300 nx-ease-out"
style={{ width: `${progress}%` }}
/>
</div>
);
};

export default LoadingBar;
Loading