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 ( - + + + {selectedStep && ( + <> + {selectedStep.id}:{' '} + {selectedStep.title} + + )} + - {steps.map((target) => ( - - Step {target.id}: {target.title} + {steps.map((step) => ( + + {step.id}:{' '} + {step.title} ))} diff --git a/web/components/step-diff-selector.tsx b/web/components/step-diff-selector.tsx new file mode 100644 index 0000000..d8c7ccf --- /dev/null +++ b/web/components/step-diff-selector.tsx @@ -0,0 +1,21 @@ +'use client' + +import { useRouter } from 'next/navigation' +import { DiffSelector } from '@/components/diff-selector' +import type { Step } from '@/lib/steps' + +interface StepDiffSelectorProps { + currentStepId: string + steps: Step[] +} + +export function StepDiffSelector({ currentStepId, steps }: StepDiffSelectorProps) { + const router = useRouter() + + return ( + router.push(`/steps/${currentStepId}/diff/${targetId}`)} + /> + ) +}