diff --git a/agents.md b/agents.md
index da9a7bd..1295ecc 100644
--- a/agents.md
+++ b/agents.md
@@ -40,3 +40,97 @@
- When you identify reusable effect logic, wrap it in a custom hook under `hooks/` (e.g., `use-window-click-dismiss.ts`) and consume that hook rather than repeating `useEffect` blocks.
+
+
+---
+
+## Prompt for Codex Agent
+
+You are the **DevContext Assistant**, helping developers generate AI-ready instruction files (`copilot-instructions.md`, `agents.md`, `cursor-rules.md`) for their projects.
+
+Your role is to **analyze input (manual answers or GitHub repo scans)**, infer project conventions, and generate high-quality context/config files tailored to the project.
+
+---
+
+### Core Capabilities
+
+1. **Modes of Use**
+ - **New Project (Wizard)**
+ - Ask the user step-by-step questions (IDE, framework, language, tooling, file structure, naming conventions, testing approach, etc.).
+ - Allow skipping questions and using defaults to generate a boilerplate instructions file.
+ - **Existing Project (GitHub Repo)**
+ - Accept a GitHub repo URL or `owner/repo`.
+ - Fetch metadata (via GitHub API or local scan): languages, frameworks, configs, testing tools, structure.
+ - Pre-fill wizard answers based on detected information.
+ - Ask only about missing/ambiguous details.
+ - Provide smart suggestions (e.g., “Detected ESLint but no Prettier — do you want me to add Prettier rules?”).
+
+2. **Repo Scanning Rules**
+ - Detect frameworks/languages:
+ - `package.json` → React, Next.js, Angular, etc.
+ - `requirements.txt`, `pyproject.toml` → Django, FastAPI, Flask, etc.
+ - `pom.xml`, `build.gradle` → Java/Spring.
+ - Detect tooling/config:
+ - ESLint, Prettier, Babel, Webpack, Vite, Dockerfile, TSConfig.
+ - Detect testing:
+ - Jest, Vitest, Cypress, Playwright, Mocha.
+ - Detect structure:
+ - Presence of `src/`, `tests/`, `components/`, etc.
+ - Summarize findings in a clear JSON object.
+
+3. **Output**
+ - Generate one or more instruction/config files depending on user choice:
+ - `copilot-instructions.md`
+ - `agents.md`
+ - `cursor-rules.md`
+ - File must include:
+ - Environment (IDE, framework, language, tooling).
+ - Project priorities.
+ - Code style (naming, structure, comments, testing).
+ - AI-related guidelines (how Copilot, Cursor, or agents should behave).
+ - Provide options:
+ - Preview file in UI.
+ - Copy to clipboard.
+ - Download file.
+
+---
+
+### UX Rules
+
+- Always make it clear what value you bring:
+ - For wizard: *“Guided setup for AI coding guidelines.”*
+ - For repo scan: *“Auto-detect stack and generate context-aware instructions.”*
+- Use smart defaults, but let user override.
+- Keep the flow simple: *Landing → Choose (New or Existing Project) → Wizard (manual or prefilled) → Generate File.*
+- At the end, provide both the file output and short explanation: *“This file was generated based on your repo + your preferences.”*
+
+---
+
+### SEO / Positioning Notes
+
+- Emphasize you are not just a “file generator” but a **repo-aware, AI coding guidelines assistant**.
+- Highlight keywords: *AI coding guidelines, Copilot instructions, Cursor rules, GitHub repo analyzer, IDE setup automation, developer onboarding docs.*
+
+---
+
+### Example Workflow (Existing Project)
+
+1. User inputs repo URL → agent scans repo.
+2. Agent outputs summary JSON:
+ ```json
+ {
+ "language": "TypeScript",
+ "frameworks": ["Next.js", "React"],
+ "tooling": ["ESLint", "Tailwind"],
+ "testing": [],
+ "structure": { "src": true, "tests": true }
+ }
+ ```
+3. Agent asks:
+ *“We detected Next.js + React + ESLint. Do you want to add Prettier rules? Do you plan to add Jest or another testing framework?”*
+4. Agent generates `copilot-instructions.md` with these conventions baked in.
+5. User downloads or copies the file.
+
+---
+
+👉 Use this as your guiding instruction: **always detect what you can, ask only when needed, and generate a repo-aware instructions file that saves the user time.**
diff --git a/app/layout.tsx b/app/layout.tsx
index afe22c6..2795b10 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -11,16 +11,15 @@ const geistSans = Geist({
subsets: ["latin"],
});
-
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
-const siteUrl = "https://devcontext.com";
-const siteTitle = "DevContext – AI Coding Guidelines & Context Generator";
+const siteUrl = "https://devcontext.xyz";
+const siteTitle = "DevContext – AI Coding Guidelines & Repo Analyzer";
const siteDescription =
- "DevContext helps developers generate AI config files like Copilot instructions, Cursor rules, and prompts — consistent, fast, IDE-ready.";
+ "DevContext helps developers generate AI config files like Copilot instructions, Cursor rules, and agents.md. Start fresh with a guided wizard or analyze your GitHub repo to auto-detect stack, frameworks, and best practices — consistent, fast, and IDE-ready.";
const ogImage = `${siteUrl}/og-image.png`;
@@ -46,14 +45,22 @@ const structuredData = {
featureList: [
"Guided wizard for AI coding instructions",
"Prebuilt templates for Copilot, Cursor, and IDE agents",
- "Context-aware questions with best-practice examples",
+ "Scan a GitHub repo to auto-detect stack, frameworks, and tooling",
+ "Context-aware questions with best-practice suggestions",
+ "Instant boilerplate generation with smart defaults",
],
keywords: [
+ "AI coding guidelines",
+ "Copilot instructions generator",
+ "Cursor rules builder",
+ "IDE setup automation",
+ "Developer onboarding docs",
"generate Copilot instructions file",
"generate agents file",
- "generate AI instructions",
"generate Cursor rules",
- "AI development workflows",
+ "AI repo analyzer",
+ "GitHub repo scanner for Copilot",
+ "repo-aware coding guidelines",
],
};
@@ -79,6 +86,10 @@ export const metadata: Metadata = {
"Copilot instructions",
"Cursor rules",
"agents md",
+ "GitHub repo analyzer",
+ "generate coding standards from GitHub repo",
+ "repo-aware AI coding guidelines",
+ "auto-detect framework coding rules",
],
authors: [{ name: "DevContext" }],
creator: "DevContext",
@@ -89,8 +100,7 @@ export const metadata: Metadata = {
},
openGraph: {
title: siteTitle,
- description:
- "Generate AI config files like Copilot instructions, Cursor rules, and prompts. Consistent, fast, and IDE-ready.",
+ description: siteDescription,
url: siteUrl,
siteName: "DevContext",
images: [
@@ -107,8 +117,7 @@ export const metadata: Metadata = {
twitter: {
card: "summary_large_image",
title: siteTitle,
- description:
- "Generate AI config files like Copilot instructions, Cursor rules, and prompts. Consistent, fast, and IDE-ready.",
+ description: siteDescription,
images: [ogImage],
site: "@devcontext",
},
@@ -141,20 +150,21 @@ export const metadata: Metadata = {
},
};
-
-
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
-
return (
-
}>
-
-
- )
-}
-
-function NewInstructionsPageContent() {
- const searchParams = useSearchParams()
- const [showWizard, setShowWizard] = useState(false)
- const [selectedFileId, setSelectedFileId] = useState(null)
-
- const fileOptions = useMemo(() => fileOptionsFromData, [])
- const preferredStackId = searchParams.get("stack")?.toLowerCase() ?? null
-
- const handleFileCtaClick = (file: FileOutputConfig) => {
- setSelectedFileId(file.id)
- setShowWizard(true)
- track(ANALYTICS_EVENTS.CREATE_INSTRUCTIONS_FILE, {
- fileId: file.id,
- fileLabel: file.label,
- })
- }
-
- const handleWizardClose = () => {
- setShowWizard(false)
- setSelectedFileId(null)
- }
-
- return (
-
-
-
- {/* Top utility bar */}
-
- {!showWizard ? (
- <>
-
- DevContext
-
-
-
-
- >
- ) : (
-
-
-
- )}
-
-
- {/* Hero Section */}
-
- {showWizard && selectedFileId ? (
-
- ) : (
- <>
-
-
-
Start a new instructions project
-
- Choose the file preset that matches what you need. The wizard will open with targeted questions and save progress as you go.
-
-
-
- - Pick a preset to load stack, architecture, and workflow prompts.
- - Answer or skip questions — you can revisit any step before exporting.
- - Download the generated file once every section shows as complete.
-
-
-
-
- {/* File type CTAs */}
-
-
- {fileOptions.map((file) => {
- return (
-
- )
- })}
-
-
-
- >
- )}
-
-
-
- )
+import type { Metadata } from "next"
+import { redirect } from "next/navigation"
+
+const title = "Launch the DevContext Wizard"
+const description =
+ "Start a guided flow to assemble AI-ready coding instruction files. Pick your stack, customize conventions, and export Copilot, Cursor, or agents guidelines in minutes."
+
+export const metadata: Metadata = {
+ title,
+ description,
+ alternates: {
+ canonical: "/new",
+ },
+ openGraph: {
+ title,
+ description,
+ url: "/new",
+ type: "website",
+ siteName: "DevContext",
+ images: [
+ {
+ url: "/og-image.png",
+ width: 1200,
+ height: 630,
+ alt: "DevContext wizard interface preview",
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ title,
+ description,
+ images: ["/og-image.png"],
+ },
}
-function LoadingFallback() {
- return (
-
- Loading wizard…
-
- )
+export default function NewPage() {
+ redirect(`/new/stack`)
}
diff --git a/app/new/stack/[[...stackSegments]]/page.tsx b/app/new/stack/[[...stackSegments]]/page.tsx
new file mode 100644
index 0000000..8b55e90
--- /dev/null
+++ b/app/new/stack/[[...stackSegments]]/page.tsx
@@ -0,0 +1,153 @@
+import Link from "next/link"
+import { notFound } from "next/navigation"
+import type { Metadata } from "next"
+
+import stacksData from "@/data/stacks.json"
+import type { DataQuestionSource } from "@/types/wizard"
+import { AnimatedBackground } from "@/components/AnimatedBackground"
+import { Button } from "@/components/ui/button"
+import { Github } from "lucide-react"
+import { getHomeMainClasses } from "@/lib/utils"
+import { StackWizardClient } from "../stack-wizard-client"
+import { StackSummaryPage } from "../stack-summary-page"
+
+const stackQuestion = (stacksData as DataQuestionSource[])[0]
+const stackAnswers = stackQuestion?.answers ?? []
+
+type PageParams = {
+ stackSegments?: string[]
+}
+
+type MetadataProps = {
+ params: Promise
+}
+
+type PageProps = {
+ params: PageParams
+}
+
+export async function generateMetadata({ params }: MetadataProps): Promise {
+ const resolvedParams = await params
+ const { stackSegments } = resolvedParams
+ const segments = Array.isArray(stackSegments) ? stackSegments : []
+ const stackId = segments.length > 0 ? segments[0] : null
+ let mode: "default" | "user" | null = null
+
+ if (segments.length > 1) {
+ if (segments.length === 2 && segments[1] === "summary") {
+ mode = "default"
+ } else if (
+ segments.length === 3 &&
+ segments[2] === "summary" &&
+ (segments[1] === "default" || segments[1] === "user")
+ ) {
+ mode = segments[1] as "default" | "user"
+ }
+ }
+
+ const stackLabel = stackAnswers.find((answer) => answer.value === stackId)?.label
+
+ const title = stackLabel
+ ? `${stackLabel} · DevContext Wizard`
+ : "DevContext Wizard"
+ let description = "Choose your stack and build AI-ready coding instructions."
+
+ if (stackLabel) {
+ if (mode === "user") {
+ description = `Review your saved answers for ${stackLabel} and generate tailored context files.`
+ } else {
+ description = `Review recommended defaults for ${stackLabel} and share the summary instantly.`
+ }
+ }
+
+ const canonicalPath = `/new/stack${segments.length > 0 ? `/${segments.join("/")}` : ""}`
+ const ogImage = "/og-image.png"
+ const imageAlt = stackLabel ? `${stackLabel} DevContext wizard preview` : "DevContext wizard interface preview"
+
+ return {
+ title,
+ description,
+ alternates: {
+ canonical: canonicalPath,
+ },
+ openGraph: {
+ title,
+ description,
+ url: canonicalPath,
+ type: "website",
+ siteName: "DevContext",
+ images: [
+ {
+ url: ogImage,
+ width: 1200,
+ height: 630,
+ alt: imageAlt,
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ title,
+ description,
+ images: [ogImage],
+ },
+ }
+}
+
+export default function StackRoutePage({ params }: PageProps) {
+ const { stackSegments } = params
+ let stackIdFromRoute: string | null = null
+ let summaryMode: "default" | "user" | null = null
+
+ if (Array.isArray(stackSegments) && stackSegments.length > 0) {
+ const potentialStackId = stackSegments[0]
+ const stackMatch = stackAnswers.find((answer) => answer.value === potentialStackId)
+
+ if (!stackMatch) {
+ notFound()
+ }
+
+ stackIdFromRoute = potentialStackId
+
+ if (stackSegments.length > 1) {
+ if (stackSegments.length === 2 && stackSegments[1] === "summary") {
+ summaryMode = "default"
+ } else if (
+ stackSegments.length === 3 &&
+ stackSegments[2] === "summary" &&
+ (stackSegments[1] === "default" || stackSegments[1] === "user")
+ ) {
+ summaryMode = stackSegments[1] as "default" | "user"
+ } else {
+ notFound()
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+ DevContext
+
+
+
+
+
+
+
+ {summaryMode ? (
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/app/new/stack/stack-summary-page.tsx b/app/new/stack/stack-summary-page.tsx
new file mode 100644
index 0000000..803c63f
--- /dev/null
+++ b/app/new/stack/stack-summary-page.tsx
@@ -0,0 +1,401 @@
+"use client"
+
+import { useCallback, useEffect, useMemo, useState } from "react"
+import Link from "next/link"
+
+import { Button } from "@/components/ui/button"
+import FinalOutputView from "@/components/final-output-view"
+import { generateInstructions } from "@/lib/instructions-api"
+import { buildCompletionSummary } from "@/lib/wizard-summary"
+import { serializeWizardResponses } from "@/lib/wizard-response"
+import {
+ STACK_QUESTION_ID,
+ stackQuestion,
+ getFileOptions,
+ getFileSummaryQuestion,
+} from "@/lib/wizard-config"
+import { loadWizardState, persistWizardState } from "@/lib/wizard-storage"
+import { buildDefaultSummaryData, buildStepsForStack } from "@/lib/wizard-summary-data"
+import type { FileOutputConfig, Responses, WizardQuestion, WizardAnswer, WizardStep } from "@/types/wizard"
+import type { GeneratedFileResult } from "@/types/output"
+import { WizardEditAnswerDialog } from "@/components/wizard-edit-answer-dialog"
+
+const fileOptions = getFileOptions()
+const fileSummaryQuestion = getFileSummaryQuestion()
+type StackSummaryPageProps = {
+ stackId: string | null
+ mode: "default" | "user"
+}
+
+export function StackSummaryPage({ stackId, mode }: StackSummaryPageProps) {
+ const [wizardSteps, setWizardSteps] = useState(null)
+ const [responses, setResponses] = useState(null)
+ const [autoFilledMap, setAutoFilledMap] = useState>({})
+ const [stackLabel, setStackLabel] = useState(null)
+ const [autoFillNotice, setAutoFillNotice] = useState(null)
+ const [isLoading, setIsLoading] = useState(true)
+ const [errorMessage, setErrorMessage] = useState(null)
+ const [generatedFile, setGeneratedFile] = useState(null)
+ const [isGeneratingMap, setIsGeneratingMap] = useState>({})
+ const [editingQuestionId, setEditingQuestionId] = useState(null)
+
+ useEffect(() => {
+ if (!stackId) {
+ setErrorMessage("Select a stack to review your summary.")
+ setIsLoading(false)
+ return
+ }
+
+ let isActive = true
+
+ const loadSummaryData = async () => {
+ setIsLoading(true)
+ setErrorMessage(null)
+ try {
+ if (mode === "default") {
+ const { steps, responses: defaultResponses, autoFilledMap: defaultsMap, stackLabel: label } =
+ await buildDefaultSummaryData(stackId)
+
+ if (!isActive) {
+ return
+ }
+
+ setWizardSteps(steps)
+ setResponses(defaultResponses)
+ setAutoFilledMap(defaultsMap)
+ setStackLabel(label)
+ setAutoFillNotice("We applied the recommended defaults for you. Tweak any section before generating.")
+
+ persistWizardState({
+ stackId,
+ stackLabel: label,
+ responses: defaultResponses,
+ autoFilledMap: defaultsMap,
+ updatedAt: Date.now(),
+ })
+ } else {
+ const { steps, stackLabel: computedLabel } = await buildStepsForStack(stackId)
+
+ if (!isActive) {
+ return
+ }
+
+ const storedState = loadWizardState(stackId)
+
+ if (!storedState) {
+ setWizardSteps(steps)
+ setResponses(null)
+ setAutoFilledMap({})
+ setStackLabel(computedLabel)
+ setAutoFillNotice(null)
+ setErrorMessage("We couldn't find saved answers for this stack. Complete the wizard to generate your own summary.")
+ return
+ }
+
+ const normalizedResponses: Responses = {
+ ...storedState.responses,
+ [STACK_QUESTION_ID]: stackId,
+ }
+
+ setWizardSteps(steps)
+ setResponses(normalizedResponses)
+ setAutoFilledMap(storedState.autoFilledMap ?? {})
+ setStackLabel(storedState.stackLabel ?? computedLabel)
+ setAutoFillNotice(null)
+ }
+ } catch (error) {
+ console.error(`Unable to prepare summary for stack "${stackId}"`, error)
+ if (isActive) {
+ setErrorMessage("We couldn't load the summary for this stack. Try selecting it again from the wizard.")
+ }
+ } finally {
+ if (isActive) {
+ setIsLoading(false)
+ }
+ }
+ }
+
+ void loadSummaryData()
+
+ return () => {
+ isActive = false
+ }
+ }, [stackId, mode])
+
+ const summaryEntries = useMemo(() => {
+ if (!wizardSteps || !responses) {
+ return []
+ }
+
+ return buildCompletionSummary(
+ fileSummaryQuestion,
+ null,
+ null,
+ wizardSteps,
+ responses,
+ autoFilledMap,
+ false
+ )
+ }, [wizardSteps, responses, autoFilledMap])
+
+ const handleGenerate = useCallback(
+ async (fileOption: FileOutputConfig) => {
+ if (!wizardSteps || !responses || !stackId) {
+ return
+ }
+
+ setIsGeneratingMap((prev) => ({ ...prev, [fileOption.id]: true }))
+ setGeneratedFile(null)
+
+ try {
+ const payload = serializeWizardResponses(wizardSteps, responses, fileOption.id)
+
+ const result = await generateInstructions({
+ stackSegment: stackId,
+ outputFileId: fileOption.id,
+ responses: payload,
+ fileFormat: fileOption.format,
+ })
+
+ if (result) {
+ setGeneratedFile(result)
+ }
+ } catch (error) {
+ console.error("Error generating instructions", error)
+ setErrorMessage("We hit a snag generating that file. Please try again.")
+ } finally {
+ setIsGeneratingMap((prev) => ({ ...prev, [fileOption.id]: false }))
+ }
+ },
+ [wizardSteps, responses, stackId]
+ )
+
+ const summaryHeader = stackLabel ??
+ (stackQuestion?.answers.find((answer) => answer.value === stackId)?.label ?? stackId ?? "Your stack")
+
+ const questionLookup = useMemo(() => {
+ const map: Record = {}
+ wizardSteps?.forEach((step) => {
+ step.questions.forEach((question) => {
+ map[question.id] = question
+ })
+ })
+ return map
+ }, [wizardSteps])
+
+ const handleEditClick = (questionId: string) => {
+ setEditingQuestionId(questionId)
+ }
+
+ const handleCloseEdit = () => {
+ setEditingQuestionId(null)
+ }
+
+ const applyAnswerUpdate = useCallback(
+ (question: WizardQuestion, answer: WizardAnswer) => {
+ const currentResponses: Responses = responses ? { ...responses } : {}
+ const currentAutoMap = { ...autoFilledMap }
+ const prevValue = currentResponses[question.id]
+ let nextValue: Responses[keyof Responses] | undefined
+
+ if (question.allowMultiple) {
+ const prevArray = Array.isArray(prevValue) ? prevValue : []
+ if (prevArray.includes(answer.value)) {
+ const filtered = prevArray.filter((item) => item !== answer.value)
+ nextValue = filtered.length > 0 ? filtered : undefined
+ } else {
+ nextValue = [...prevArray, answer.value]
+ }
+ } else {
+ nextValue = prevValue === answer.value ? undefined : answer.value
+ }
+
+ if (nextValue === undefined || (Array.isArray(nextValue) && nextValue.length === 0)) {
+ delete currentResponses[question.id]
+ } else {
+ currentResponses[question.id] = nextValue
+ }
+
+ delete currentAutoMap[question.id]
+ setResponses(currentResponses)
+ setAutoFilledMap(currentAutoMap)
+ setAutoFillNotice(null)
+
+ if (stackId) {
+ persistWizardState({
+ stackId,
+ stackLabel: stackLabel ?? summaryHeader,
+ responses: currentResponses,
+ autoFilledMap: currentAutoMap,
+ updatedAt: Date.now(),
+ })
+ }
+
+ if (!question.allowMultiple) {
+ handleCloseEdit()
+ }
+ },
+ [responses, autoFilledMap, stackId, stackLabel, summaryHeader]
+ )
+
+ if (isLoading) {
+ return (
+
+ Preparing your summary…
+
+ )
+ }
+
+ if (errorMessage) {
+ return (
+
+
{errorMessage}
+
+
+ )
+ }
+
+ if (!responses || !wizardSteps || !stackId) {
+ return null
+ }
+
+ return (
+
+
+
+
+
Generate context files
+
+ Pick the output format you need. Each option uses the summary on this page.
+
+
+
+ {fileOptions.map((file) => {
+ const isGenerating = Boolean(isGeneratingMap[file.id])
+ return (
+
+ )
+ })}
+
+
+
+
+
+
+
+
{summaryHeader} instructions overview
+
+ Share this page to sync on conventions, or jump back into the wizard to fine-tune answers.
+
+
+
+
+
+
+ {autoFillNotice ? (
+
+ {autoFillNotice}
+
+ ) : null}
+
+
+
+
+
Selections in this summary
+
+ Reopen any question from the wizard to change these selections.
+
+
+
+ {summaryEntries.map((entry) => (
+
+
+ {entry.hasSelection ? (
+
+ ) : (
+
+ No selection
+ {!entry.isReadOnlyOnSummary && stackId ? (
+
+ ) : null}
+
+ )}
+
+ ))}
+
+
+
+ {editingQuestionId ? (
+ (() => {
+ const editingQuestion = questionLookup[editingQuestionId]
+ if (!editingQuestion) {
+ return null
+ }
+ const currentValue = responses ? responses[editingQuestion.id] : undefined
+ return (
+
applyAnswerUpdate(editingQuestion, answer)}
+ onClose={handleCloseEdit}
+ />
+ )
+ })()
+ ) : null}
+
+ {generatedFile ? (
+ setGeneratedFile(null)}
+ />
+ ) : null}
+
+ )
+}
diff --git a/app/new/stack/stack-wizard-client.tsx b/app/new/stack/stack-wizard-client.tsx
new file mode 100644
index 0000000..995e085
--- /dev/null
+++ b/app/new/stack/stack-wizard-client.tsx
@@ -0,0 +1,59 @@
+"use client"
+
+import { useRouter } from "next/navigation"
+
+import { InstructionsWizard } from "@/components/instructions-wizard"
+
+const buildStackPath = (stackId?: string | null, view?: "summary" | "default" | "user") => {
+ if (!stackId) {
+ return "/new/stack"
+ }
+
+ if (view === "summary") {
+ return `/new/stack/${stackId}/summary`
+ }
+
+ if (view === "default") {
+ return `/new/stack/${stackId}/default/summary`
+ }
+
+ if (view === "user") {
+ return `/new/stack/${stackId}/user/summary`
+ }
+
+ return `/new/stack/${stackId}`
+}
+
+type StackWizardClientProps = {
+ stackIdFromRoute: string | null
+}
+
+export function StackWizardClient({ stackIdFromRoute }: StackWizardClientProps) {
+ const router = useRouter()
+ const initialStackId = stackIdFromRoute
+
+ const handleStackSelected = (stackId: string) => {
+ router.push(buildStackPath(stackId))
+ }
+
+ const handleStackCleared = () => {
+ router.push(buildStackPath())
+ }
+
+ const handleWizardComplete = (stackId: string | null) => {
+ if (!stackId) {
+ return
+ }
+
+ router.push(buildStackPath(stackId, "user"))
+ }
+
+ return (
+
+ )
+}
diff --git a/app/page.tsx b/app/page.tsx
index b467336..5f43227 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -10,7 +10,10 @@ export default function LandingPage() {
-
+
+
+ DevContext
+