From 1b013e7e214040090b3bad5c19c6955e87c30747 Mon Sep 17 00:00:00 2001
From: Zane Chen <99708452+czl9707@users.noreply.github.com>
Date: Sat, 14 Mar 2026 22:06:52 -0400
Subject: [PATCH] Fix/ Step Comparing Step Restriction (#5)
* feat: add learning website with Next.js
Add interactive web interface for the Build Your Own OpenClaw tutorial:
- Next.js app with TypeScript and Tailwind CSS
- Theme provider with dark/light mode toggle
- Step card components with diff visualization
- Utilities for loading step metadata and file diffs
* chore: ignore worktrees directory
* feat(web): implement learning website with step navigation and diff viewer
- Landing page with hero section and steps overview grouped by phase
- Step detail pages with README rendering and syntax highlighting (Shiki)
- Diff comparison pages with side-by-side GitHub-style viewer
- Custom 404 Not Found page
- Components: ReadmeRenderer, CodeBlock, DiffViewer, DiffSelector
- Generates 175 static pages (18 steps + 153 diff combinations)
* feat(web): enhance UI with zane-portfolio theme and improved UX
- Update color scheme to match zane-portfolio style (warm beige light, pure black dark)
- Add scrollbar styling for light/dark modes
- Fix card border visibility in dark mode
- Add GitHub CTA section with clone command and copy button
- Add star on GitHub call-to-action
- Use favicons from zane-portfolio
- Improve diff viewer with scroll sync and collapsible files
- Add sticky header with file navigation dropdown
- Rewrite README links to GitHub and internal step pages
- Remove default create-next-app template files
* ci: add GitHub Pages deployment workflow
- Add workflow to build and deploy Next.js static site
- Configure custom domain (build-your-own-openclaw.kiyo-n-zane.com)
- Trigger on push to main branch
* fix config
* fix config
* fix config
* fix web navigation bug
---
web/app/steps/[id]/diff/[to]/page.tsx | 44 ++++++++-------------------
web/app/steps/[id]/page.tsx | 6 ++--
web/components/diff-page-selector.tsx | 39 ++++++++++++++++++++++++
web/components/diff-selector.tsx | 42 ++++++++++++++++---------
web/components/step-diff-selector.tsx | 21 +++++++++++++
5 files changed, 104 insertions(+), 48 deletions(-)
create mode 100644 web/components/diff-page-selector.tsx
create mode 100644 web/components/step-diff-selector.tsx
diff --git a/web/app/steps/[id]/diff/[to]/page.tsx b/web/app/steps/[id]/diff/[to]/page.tsx
index 04b47e9..38ce4d9 100644
--- a/web/app/steps/[id]/diff/[to]/page.tsx
+++ b/web/app/steps/[id]/diff/[to]/page.tsx
@@ -1,9 +1,10 @@
import { notFound } from 'next/navigation'
import Link from 'next/link'
import { getStep, getSteps } from '@/lib/steps'
-import { getChangedFiles, getUnchangedFiles, type FileDiff } from '@/lib/files'
+import { getChangedFiles, getUnchangedFiles } from '@/lib/files'
import { DiffViewer } from '@/components/diff-viewer'
import { FileNavDropdown } from '@/components/file-nav-dropdown'
+import { DiffPageSelector } from '@/components/diff-page-selector'
import {
Breadcrumb,
BreadcrumbList,
@@ -12,8 +13,6 @@ import {
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb'
-import { H2, Muted } from '@/components/ui/typography'
-import { PlusIcon, MinusIcon } from 'lucide-react'
interface DiffPageProps {
params: Promise<{
@@ -29,10 +28,10 @@ export async function generateStaticParams() {
// Generate all valid combinations where from < to
for (let i = 0; i < steps.length - 1; i++) {
for (let j = i + 1; j < steps.length; j++) {
- params.push({
- id: steps[i].id,
- to: steps[j].id,
- })
+ params.push({
+ id: steps[i].id,
+ to: steps[j].id,
+ })
}
}
@@ -53,21 +52,6 @@ export async function generateMetadata({ params }: DiffPageProps) {
}
}
-// Status type for changed files (excludes 'unchanged')
-type ChangedStatus = 'added' | 'removed' | 'modified'
-
-// Get status icon for file
-function getStatusIcon(status: ChangedStatus) {
- switch (status) {
- case 'added':
- return
- case 'removed':
- return
- case 'modified':
- return null
- }
-}
-
export default async function DiffPage({ params }: DiffPageProps) {
const { id, to } = await params
const fromStep = getStep(id)
@@ -112,15 +96,13 @@ export default async function DiffPage({ params }: DiffPageProps) {
{/* Sticky Page Header */}
-
-
-
- Step {fromStep.id} to Step {toStep.id}
-
-
- Comparing "{fromStep.title}" with "{toStep.title}"
-
-
+
+
parseInt(s.id) < parseInt(toStep.id))}
+ toSteps={getSteps().filter((s) => parseInt(s.id) > parseInt(fromStep.id))}
+ />
{changedFiles.length > 0 && (
({
diff --git a/web/app/steps/[id]/page.tsx b/web/app/steps/[id]/page.tsx
index e9f54b1..b20cc75 100644
--- a/web/app/steps/[id]/page.tsx
+++ b/web/app/steps/[id]/page.tsx
@@ -5,7 +5,7 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'
import { getStep, getSteps } from '@/lib/steps'
import { ReadmeRenderer } from '@/components/readme-renderer'
-import { DiffSelector } from '@/components/diff-selector'
+import { StepDiffSelector } from '@/components/step-diff-selector'
import {
Breadcrumb,
BreadcrumbItem,
@@ -60,7 +60,7 @@ export default async function StepPage({ params }: PageProps) {
const nextStep = currentIndex < steps.length - 1 ? steps[currentIndex + 1] : null
// Steps available for diff comparison (excluding current step)
- const diffTargets = steps.filter((s) => s.id !== step.id)
+ const diffTargets = steps.filter((s) => parseInt(s.id) > parseInt(step.id))
return (
@@ -94,7 +94,7 @@ export default async function StepPage({ params }: PageProps) {
{/* Diff action dropdown */}
-
+
{/* README Content */}
diff --git a/web/components/diff-page-selector.tsx b/web/components/diff-page-selector.tsx
new file mode 100644
index 0000000..49191eb
--- /dev/null
+++ b/web/components/diff-page-selector.tsx
@@ -0,0 +1,39 @@
+'use client'
+
+import { useRouter } from 'next/navigation'
+import { DiffSelector } from '@/components/diff-selector'
+import type { Step } from '@/lib/steps'
+
+interface DiffPageSelectorProps {
+ fromStep: Step
+ toStep: Step
+ fromSteps: Step[]
+ toSteps: Step[]
+}
+
+export function DiffPageSelector({
+ fromStep,
+ toStep,
+ fromSteps,
+ toSteps,
+}: DiffPageSelectorProps) {
+ const router = useRouter()
+
+ return (
+
+ router.push(`/steps/${newFromId}/diff/${toStep.id}`)}
+ />
+ →
+ router.push(`/steps/${fromStep.id}/diff/${newToId}`)}
+ />
+
+ )
+}
diff --git a/web/components/diff-selector.tsx b/web/components/diff-selector.tsx
index 2e9a186..beee384 100644
--- a/web/components/diff-selector.tsx
+++ b/web/components/diff-selector.tsx
@@ -1,6 +1,5 @@
'use client'
-import { useRouter } from 'next/navigation'
import {
Select,
SelectContent,
@@ -11,28 +10,43 @@ import {
import type { Step } from '@/lib/steps'
interface DiffSelectorProps {
- currentStepId: string
steps: Step[]
+ value?: string
+ placeholder?: string
+ onSelect: (stepId: string) => void
}
-export function DiffSelector({ currentStepId, steps }: DiffSelectorProps) {
- const router = useRouter()
-
- const handleValueChange = (value: string | null) => {
- if (value) {
- router.push(`/steps/${currentStepId}/diff/${value}`)
+export function DiffSelector({
+ steps,
+ value,
+ placeholder = 'Compare with...',
+ onSelect,
+}: DiffSelectorProps) {
+ const handleValueChange = (newValue: string | null) => {
+ if (newValue) {
+ onSelect(newValue)
}
}
+ const selectedStep = steps.find((s) => s.id === value)
+
return (
-