Skip to content

Commit 3686efa

Browse files
committed
feat: Add support for Python, Remix, Svelte, and Vue questions in the wizard
- Introduced new JSON files for Python, Remix, Svelte, and Vue questions with detailed options and explanations. - Enhanced the stack selection with new tags for better filtering and categorization. - Updated the agents and copilot instructions templates to include specific combinations for Vue, Nuxt, Svelte, Astro, Remix, and Python. - Implemented a new hook `useAnswerFilter` to enable filtering of answers based on user input. - Added tests for the new filtering functionality and stack guidance retrieval. - Updated the stack guidance logic to provide tailored guidance for each supported stack. - Enhanced the wizard question types to include an `enableFilter` property for dynamic filtering capabilities.
1 parent 6c540aa commit 3686efa

25 files changed

+1777
-51
lines changed

app/api/generate/[framework]/[fileName]/route.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from 'path'
44

55
import type { WizardResponses } from '@/types/wizard'
66
import { getTemplateConfig, type TemplateKey } from '@/lib/template-config'
7+
import { getStackGuidance } from '@/lib/stack-guidance'
78

89
function mapOutputFileToTemplateType(outputFile: string): string {
910
const mapping: Record<string, string> = {
@@ -111,8 +112,23 @@ export async function POST(
111112
replaceVariable('logging')
112113
replaceVariable('commitStyle')
113114
replaceVariable('prRules')
115+
const replaceStaticPlaceholder = (placeholderKey: string, value: string) => {
116+
const placeholder = `{{${placeholderKey}}}`
117+
118+
if (!generatedContent.includes(placeholder)) {
119+
return
120+
}
121+
122+
const replacement = isJsonTemplate ? escapeForJson(value) : value
123+
generatedContent = generatedContent.replace(placeholder, replacement)
124+
}
125+
114126
replaceVariable('outputFile')
115127

128+
const stackGuidanceSlug = responses.stackSelection || frameworkFromPath
129+
const stackGuidance = getStackGuidance(stackGuidanceSlug)
130+
replaceStaticPlaceholder('stackGuidance', stackGuidance)
131+
116132
return NextResponse.json({
117133
content: generatedContent,
118134
fileName: templateConfig.outputFileName,

app/new/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { track } from "@/lib/mixpanel"
1111
import type { DataQuestionSource, FileOutputConfig } from "@/types/wizard"
1212
import { Github } from "lucide-react"
1313
import Link from "next/link"
14+
import { useSearchParams } from "next/navigation"
1415

1516
import filesData from "@/data/files.json"
1617
import { buildFileOptionsFromQuestion } from "@/lib/wizard-utils"
@@ -20,10 +21,12 @@ const fileQuestion = fileQuestionSet[0] ?? null
2021
const fileOptionsFromData = buildFileOptionsFromQuestion(fileQuestion)
2122

2223
export default function NewInstructionsPage() {
24+
const searchParams = useSearchParams()
2325
const [showWizard, setShowWizard] = useState(false)
2426
const [selectedFileId, setSelectedFileId] = useState<string | null>(null)
2527

2628
const fileOptions = useMemo(() => fileOptionsFromData, [])
29+
const preferredStackId = searchParams.get("stack")?.toLowerCase() ?? null
2730

2831
const handleFileCtaClick = (file: FileOutputConfig) => {
2932
setSelectedFileId(file.id)
@@ -72,7 +75,11 @@ export default function NewInstructionsPage() {
7275
{/* Hero Section */}
7376
<main className={getHomeMainClasses(showWizard)}>
7477
{showWizard && selectedFileId ? (
75-
<InstructionsWizard selectedFileId={selectedFileId} onClose={handleWizardClose} />
78+
<InstructionsWizard
79+
selectedFileId={selectedFileId}
80+
onClose={handleWizardClose}
81+
initialStackId={preferredStackId}
82+
/>
7683
) : (
7784
<>
7885
<div className="space-y-6">

app/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export default function LandingPage() {
1212
<div className="relative z-10 flex min-h-screen flex-col">
1313
<header className="flex items-center justify-end px-6 py-6 lg:px-12 lg:py-8">
1414
<div className="flex items-center gap-3">
15+
<Button asChild variant="ghost" size="sm" className="hidden sm:inline-flex">
16+
<Link href="/stacks">Browse stacks</Link>
17+
</Button>
1518
<Button asChild variant="ghost" size="sm" className="hidden sm:inline-flex">
1619
<Link href="/new">Launch wizard</Link>
1720
</Button>

app/stacks/[stack]/page.tsx

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import Link from "next/link"
2+
import type { Metadata } from "next"
3+
import { notFound } from "next/navigation"
4+
5+
import stacksData from "@/data/stacks.json"
6+
import type { DataQuestionSource } from "@/types/wizard"
7+
8+
const stackQuestionSet = stacksData as DataQuestionSource[]
9+
const stackQuestion = stackQuestionSet[0]
10+
const stackAnswers = stackQuestion?.answers ?? []
11+
12+
const STACK_PAGE_DETAILS: Record<string, { title: string; description: string; highlights: string[]; docsNote?: string }> = {
13+
react: {
14+
title: "React instructions file",
15+
description:
16+
"Ship consistent React instructions that cover hooks usage, component structures, and testing expectations before you open the wizard.",
17+
highlights: [
18+
"Bundle instructions for hooks, components, and state tooling",
19+
"Document styling decisions across Tailwind or CSS Modules",
20+
"Share testing policies with Jest and React Testing Library",
21+
],
22+
},
23+
nextjs: {
24+
title: "Next.js instructions file",
25+
description: "Clarify App Router patterns, rendering modes, and data fetching strategies for your Next.js project.",
26+
highlights: [
27+
"Capture SSR, SSG, or ISR defaults for routes",
28+
"Explain data fetching via server actions or route handlers",
29+
"Align styling and component conventions across the monorepo",
30+
],
31+
},
32+
angular: {
33+
title: "Angular instructions file",
34+
description: "Outline Angular module structure, RxJS usage, and testing defaults for teams shipping with Angular.",
35+
highlights: [
36+
"Define standalone components vs. NgModule structure",
37+
"Document reactive forms, signals, and service patterns",
38+
"Capture Jest or Karma coverage expectations",
39+
],
40+
},
41+
vue: {
42+
title: "Vue instructions file",
43+
description: "Guide your team on Composition API, Pinia state, and the Vue testing stack before exporting guidance.",
44+
highlights: [
45+
"Document whether components use the Composition or Options API",
46+
"Agree on Pinia, Vuex, or lightweight store helpers",
47+
"Share styling choices from SFC scoped CSS to Tailwind",
48+
],
49+
},
50+
nuxt: {
51+
title: "Nuxt instructions file",
52+
description: "Capture Nuxt-specific rendering, data fetching, and deployment strategies to feed into AI assistants.",
53+
highlights: [
54+
"Set expectations for SSR, SSG, or hybrid rendering",
55+
"List how you use runtime config, server routes, and Nitro",
56+
"Clarify deployment adapters whether Vercel, Netlify, or custom",
57+
],
58+
},
59+
svelte: {
60+
title: "Svelte instructions file",
61+
description: "Align on SvelteKit tooling, store patterns, and styling so generated instructions match your stack.",
62+
highlights: [
63+
"Capture whether you build with SvelteKit or bare Vite",
64+
"Explain store usage and when to reach for external libs",
65+
"Define styling guidance across scoped CSS or Tailwind",
66+
],
67+
},
68+
astro: {
69+
title: "Astro instructions file",
70+
description: "Describe islands architecture, content collections, and deployment workflows for Astro projects.",
71+
highlights: [
72+
"Identify which integration (React, Vue, Svelte) powers islands",
73+
"Explain your rendering defaults and revalidation windows",
74+
"Document CMS or content-collection structure for writers",
75+
],
76+
},
77+
remix: {
78+
title: "Remix instructions file",
79+
description: "Share data loader patterns, runtime choices, and styling so AI agents stay true to your Remix app.",
80+
highlights: [
81+
"Capture whether loaders, resource routes, or client fetch power data",
82+
"Document hosting choices like Vercel, Fly.io, or Express",
83+
"Signal styling conventions across Tailwind or CSS Modules",
84+
],
85+
},
86+
python: {
87+
title: "Python instructions file",
88+
description: "Detail your Python framework, typing policy, and packaging so Copilot and agents stay in sync.",
89+
highlights: [
90+
"State whether FastAPI, Django, or Flask powers the API",
91+
"Explain typing expectations and lint tooling like Ruff",
92+
"Document package and testing workflows across Poetry or pytest",
93+
],
94+
docsNote: "Need a framework not listed? Pick Python, then document it in the first question.",
95+
},
96+
}
97+
98+
export function generateStaticParams() {
99+
return stackAnswers
100+
.filter((answer) => typeof answer.value === "string" && answer.value.length > 0)
101+
.map((answer) => ({ stack: answer.value }))
102+
}
103+
104+
export function generateMetadata({ params }: { params: { stack: string } }): Metadata {
105+
const slug = params.stack.toLowerCase()
106+
const stackEntry = stackAnswers.find((answer) => answer.value === slug)
107+
108+
if (!stackEntry) {
109+
return {
110+
title: "DevContext instructions",
111+
description: "Generate clear AI assistant instructions tailored to your framework.",
112+
}
113+
}
114+
115+
const details = STACK_PAGE_DETAILS[slug]
116+
const title = details ? `${details.title} | DevContext` : `${stackEntry.label} instructions file | DevContext`
117+
const description = details?.description ?? `Create an instructions file for ${stackEntry.label} with DevContext.`
118+
119+
return {
120+
title,
121+
description,
122+
alternates: {
123+
canonical: `https://devcontext.xyz/stacks/${slug}`,
124+
},
125+
}
126+
}
127+
128+
export default function StackLandingPage({ params }: { params: { stack: string } }) {
129+
const slug = params.stack.toLowerCase()
130+
const stackEntry = stackAnswers.find((answer) => answer.value === slug)
131+
132+
if (!stackEntry) {
133+
notFound()
134+
}
135+
136+
const details = STACK_PAGE_DETAILS[slug]
137+
const highlights = details?.highlights ?? []
138+
const pageTitle = details?.title ?? `${stackEntry.label} instructions file`
139+
const description = details?.description ?? `Start a ${stackEntry.label} instructions wizard with DevContext.`
140+
const targetUrl = `/new?stack=${slug}`
141+
142+
return (
143+
<main className="mx-auto flex min-h-screen max-w-3xl flex-col gap-10 px-6 py-16 text-foreground">
144+
<header className="space-y-4">
145+
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Stack presets</p>
146+
<h1 className="text-4xl font-semibold tracking-tight md:text-5xl">{pageTitle}</h1>
147+
<p className="text-base text-muted-foreground md:text-lg">{description}</p>
148+
{stackEntry.docs ? (
149+
<p className="text-sm text-muted-foreground">
150+
Source docs: {" "}
151+
<a href={stackEntry.docs} target="_blank" rel="noreferrer" className="underline-offset-4 hover:underline">
152+
{stackEntry.label} documentation
153+
</a>
154+
</p>
155+
) : null}
156+
{details?.docsNote ? (
157+
<p className="text-sm text-muted-foreground">{details.docsNote}</p>
158+
) : null}
159+
</header>
160+
161+
{highlights.length > 0 ? (
162+
<section className="space-y-3">
163+
<h2 className="text-2xl font-semibold tracking-tight">What this preset covers</h2>
164+
<ul className="list-disc space-y-2 pl-6 text-sm text-muted-foreground">
165+
{highlights.map((bullet) => (
166+
<li key={bullet}>{bullet}</li>
167+
))}
168+
</ul>
169+
</section>
170+
) : null}
171+
172+
<div className="flex flex-col gap-4 rounded-2xl border border-border/70 bg-card/95 p-6 shadow-sm">
173+
<h2 className="text-xl font-semibold">Ready to build your instructions?</h2>
174+
<p className="text-sm text-muted-foreground">
175+
Launch the DevContext wizard with {stackEntry.label} pre-selected. You can review every question, accept defaults, and export a
176+
stack-aware instructions file when each section is complete.
177+
</p>
178+
<div>
179+
<Link
180+
href={targetUrl}
181+
className="inline-flex items-center justify-center rounded-lg border border-border/80 bg-primary px-5 py-2 text-sm font-semibold text-primary-foreground shadow-sm transition hover:translate-y-[1px] hover:bg-primary/90"
182+
>
183+
Start the {stackEntry.label} wizard
184+
</Link>
185+
</div>
186+
</div>
187+
</main>
188+
)
189+
}

app/stacks/page.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Link from "next/link"
2+
import type { Metadata } from "next"
3+
4+
import stacksData from "@/data/stacks.json"
5+
import type { DataQuestionSource } from "@/types/wizard"
6+
7+
const stackQuestionSet = stacksData as DataQuestionSource[]
8+
const stackQuestion = stackQuestionSet[0]
9+
const stackAnswers = stackQuestion?.answers ?? []
10+
11+
export const metadata: Metadata = {
12+
title: "Choose Your Stack | DevContext",
13+
description: "Explore framework-specific instructions flows for React, Vue, Svelte, Python, and more.",
14+
}
15+
16+
export default function StacksIndexPage() {
17+
return (
18+
<main className="mx-auto flex min-h-screen max-w-4xl flex-col gap-10 px-6 py-16 text-foreground">
19+
<header className="space-y-4 text-center">
20+
<h1 className="text-4xl font-semibold tracking-tight md:text-5xl">Framework instructions presets</h1>
21+
<p className="mx-auto max-w-2xl text-base text-muted-foreground md:text-lg">
22+
Jump straight into the DevContext wizard with copy tailored to your stack. Each page outlines what we cover and links directly
23+
into the guided flow.
24+
</p>
25+
</header>
26+
27+
<section className="grid gap-6 md:grid-cols-2">
28+
{stackAnswers.map((answer) => {
29+
const href = answer.value ? `/stacks/${answer.value}` : "/new"
30+
return (
31+
<article
32+
key={answer.value}
33+
className="flex flex-col gap-4 rounded-2xl border border-border/70 bg-card/95 p-6 shadow-sm transition hover:-translate-y-1 hover:shadow-lg"
34+
>
35+
<div className="space-y-2">
36+
<h2 className="text-2xl font-semibold tracking-tight">{answer.label}</h2>
37+
{answer.docs ? (
38+
<p className="text-sm text-muted-foreground">
39+
<a href={answer.docs} target="_blank" rel="noreferrer" className="underline-offset-4 hover:underline">
40+
Official documentation
41+
</a>
42+
</p>
43+
) : null}
44+
</div>
45+
<div className="mt-auto">
46+
<Link
47+
href={href}
48+
className="inline-flex items-center justify-center rounded-lg border border-border/80 bg-background/80 px-4 py-2 text-sm font-semibold transition hover:border-primary/40 hover:text-primary"
49+
>
50+
View {answer.label} instructions
51+
</Link>
52+
</div>
53+
</article>
54+
)
55+
})}
56+
</section>
57+
</main>
58+
)
59+
}

0 commit comments

Comments
 (0)