Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
756a3a5
chore: scaffold builder app
johnleider Apr 4, 2026
376dc10
chore(builder): add tsconfig files matching playground pattern
johnleider Apr 4, 2026
2e802fd
chore(builder): add dependency graph generator
johnleider Apr 4, 2026
866e33c
chore(builder): tighten vitest config include pattern
johnleider Apr 4, 2026
a99a7d5
feat(builder): add manifest generation and playground handoff
johnleider Apr 4, 2026
afc90db
feat(builder): add landing page with mode selection
johnleider Apr 4, 2026
173fd20
chore(builder): add feature catalog data layer
johnleider Apr 4, 2026
f1ad9d2
feat(builder): add builder store
johnleider Apr 4, 2026
b5fb8bd
fix(builder): fix maturity.json type cast for null since values
johnleider Apr 4, 2026
2e56640
feat(builder): add IntentCard and FeatureCard components
johnleider Apr 4, 2026
04a5818
feat(builder): add guided mode with intent and category steps
johnleider Apr 4, 2026
4af8def
feat(builder): add review page with playground handoff
johnleider Apr 4, 2026
6d43ec8
feat(builder): add free pick mode
johnleider Apr 4, 2026
e0d25a6
feat(builder): add AI builder page with auth gate
johnleider Apr 4, 2026
04300f2
chore(builder): fix lint issues
johnleider Apr 4, 2026
2016d7f
chore(builder): add knip workspace config
johnleider Apr 4, 2026
f706668
fix(builder): replace non-existent mdiShoeprint with mdiCompass
johnleider Apr 4, 2026
7e67fa2
fix(builder): use v0play.vuetifyjs.com for playground handoff
johnleider Apr 4, 2026
7504fec
feat(builder): improve card component styling
johnleider Apr 4, 2026
f71efb7
feat(builder): polish page layouts and visual hierarchy
johnleider Apr 4, 2026
ea4b9ec
feat(builder): generate rich multi-file scaffold from manifest
johnleider Apr 4, 2026
78d9d42
feat(builder): polish guided flow with icons, intros, and selection f…
johnleider Apr 4, 2026
0c83ebd
feat(builder): add AppBar with logo, theme toggle, and layout system
johnleider Apr 4, 2026
63343c1
refactor(builder): use layout wrapper, remove redundant page wrappers
johnleider Apr 4, 2026
8ac680e
feat(builder): enrich features with icons, descriptions, and code exa…
johnleider Apr 4, 2026
c6788ac
chore(builder): add layouts to knip entry patterns
johnleider Apr 4, 2026
e0c984b
fix(builder): use router-view in layout instead of slot
johnleider Apr 4, 2026
b7d9f6c
fix(builder): fix logo and theme icon ref unwrapping in template
johnleider Apr 4, 2026
2d10fbb
fix(builder): remove inline icon from feature cards
johnleider Apr 4, 2026
f1741c4
feat(builder): add copyright footer
johnleider Apr 4, 2026
e455036
fix(builder): pin footer to bottom with flex layout
johnleider Apr 4, 2026
a741f34
refactor(builder): centralize content width in layout
johnleider Apr 4, 2026
eac5439
fix(builder): allow toggling auto-included features
johnleider Apr 4, 2026
d402be9
refactor(builder): use v0 composables for all state management
johnleider Apr 4, 2026
d9ee015
refactor(builder): use features.selected() instead of raw Set check
johnleider Apr 4, 2026
d713079
fix(builder): initialize stepper synchronously to prevent render error
johnleider Apr 4, 2026
33106f6
fix(builder): guard stepper index with safe default
johnleider Apr 5, 2026
70e532b
fix(builder): fix self-referencing stepIndex causing stack overflow
johnleider Apr 5, 2026
50d15c2
fix(builder): initialize stepper in store with steps at creation
johnleider Apr 5, 2026
5123cdb
fix(builder): remove .value from Pinia-unwrapped stepper/intent refs
johnleider Apr 5, 2026
67a2eb2
fix(builder): remove .value from stepIndex in template
johnleider Apr 5, 2026
77ed6e5
feat(builder): add breadcrumb navigation to AppBar
johnleider Apr 5, 2026
751296f
feat(builder): show dependency reasons and fix selection logic
johnleider Apr 5, 2026
4e72af9
feat(builder): improve feature card UX
johnleider Apr 5, 2026
b0554c5
feat(builder): add all missing composables to feature catalog
johnleider Apr 5, 2026
7d1b6cf
feat(builder): add rich review summary to guided wizard
johnleider Apr 5, 2026
b643f50
feat(builder): use category icons in step indicator and improve inten…
johnleider Apr 5, 2026
249d799
feat(builder): persist intent and feature selections with v0 useStorage
johnleider Apr 5, 2026
46f9a8d
fix(builder): replace overflow-y-scroll with scrollbar-gutter stable
johnleider Apr 5, 2026
6bbcd51
feat(builder): horizontal mode cards + fix scrollbar gutter
johnleider Apr 5, 2026
1e9f751
feat(builder): widen layout to max-w-4xl, taller mode cards
johnleider Apr 5, 2026
659556d
feat(builder): taller mode cards with more padding
johnleider Apr 5, 2026
80493d5
feat(builder): bump mode card height to min-h-80, revert padding
johnleider Apr 5, 2026
c93bc70
feat(builder): add question-based data model for guided flow
johnleider Apr 5, 2026
074054a
feat(builder): rebuild guided mode with yes/no question flow
johnleider Apr 5, 2026
6758a3a
fix(builder): add Continue button after intent selection
johnleider Apr 5, 2026
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
70 changes: 70 additions & 0 deletions apps/builder/build/generate-dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = resolve(__dirname, '../../..')
const V0_SRC = resolve(ROOT, 'packages/0/src')

interface DependencyGraph {
composables: Record<string, string[]>
components: Record<string, string[]>
}

function extractV0Imports (filePath: string): string[] {
let content: string
try {
content = readFileSync(filePath, 'utf8')
} catch {
return []
}

const imports: string[] = []
const pattern = /from\s+['"]#v0\/(composables|components)\/(\w+)['"]/g
let match: RegExpExecArray | null

while ((match = pattern.exec(content)) !== null) {
imports.push(match[2])
}

return [...new Set(imports)]
}

function scanDirectory (dir: string): Record<string, string[]> {
const entries = readdirSync(dir)
const graph: Record<string, string[]> = {}

for (const entry of entries) {
const entryPath = resolve(dir, entry)
if (!statSync(entryPath).isDirectory()) continue

const indexPath = resolve(entryPath, 'index.ts')
const deps = extractV0Imports(indexPath)

// Also scan .vue files and non-index .ts files
try {
const files = readdirSync(entryPath)
for (const file of files) {
if (file.endsWith('.vue') || (file.endsWith('.ts') && file !== 'index.ts')) {
deps.push(...extractV0Imports(resolve(entryPath, file)))
}
}
} catch { /* empty */ }

graph[entry] = [...new Set(deps)].filter(d => d !== entry).toSorted()
}

return graph
}

const graph: DependencyGraph = {
composables: scanDirectory(resolve(V0_SRC, 'composables')),
components: scanDirectory(resolve(V0_SRC, 'components')),
}

const outPath = resolve(__dirname, '../src/data/dependencies.json')
writeFileSync(outPath, JSON.stringify(graph, null, 2) + '\n')

console.log(
`Generated dependency graph: ${Object.keys(graph.composables).length} composables, ${Object.keys(graph.components).length} components`,
)
13 changes: 13 additions & 0 deletions apps/builder/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<title>v0 Framework Builder</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
29 changes: 29 additions & 0 deletions apps/builder/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@vuetify-private/builder",
"version": "0.2.0",
"private": true,
"type": "module",
"scripts": {
"generate": "tsx build/generate-dependencies.ts",
"dev": "pnpm generate && vite",
"build": "pnpm generate && vite build",
"preview": "vite preview"
},
"dependencies": {
"@mdi/js": "catalog:",
"@vuetify/auth": "catalog:",
"@vuetify/v0": "workspace:*",
"fflate": "catalog:",
"pinia": "catalog:",
"vue": "catalog:"
},
"devDependencies": {
"tsx": "catalog:",
"unocss": "catalog:",
"unplugin-vue": "catalog:",
"unplugin-vue-components": "catalog:",
"vite": "catalog:",
"vite-plugin-vue-layouts-next": "catalog:",
"vue-router": "catalog:"
}
}
6 changes: 6 additions & 0 deletions apps/builder/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script setup lang="ts">
</script>

<template>
<router-view />
</template>
22 changes: 22 additions & 0 deletions apps/builder/src/components.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399

export {}

/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AppBar: typeof import('./components/app/AppBar.vue')['default']
AppFooter: typeof import('./components/app/AppFooter.vue')['default']
FeatureCard: typeof import('./components/FeatureCard.vue')['default']
IntentCard: typeof import('./components/IntentCard.vue')['default']
ModeCard: typeof import('./components/ModeCard.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}
106 changes: 106 additions & 0 deletions apps/builder/src/components/FeatureCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<script setup lang="ts">
// Utilities
import { toRef } from 'vue'

// Types
import type { Feature } from '@/data/types'

const {
feature,
active = false,
auto = false,
reason,
} = defineProps<{
feature: Feature
active?: boolean
auto?: boolean
reason?: string
}>()

const maturityClass = toRef(() => {
if (feature.maturity === 'stable') return 'bg-success/20 text-success'
if (feature.maturity === 'preview') return 'bg-info/20 text-info'
return 'bg-warning/20 text-warning'
})
</script>

<template>
<button
class="flex items-start gap-3 p-4 rounded-lg border text-left w-full"
:class="[
auto && !active
? 'border-dashed border-divider bg-surface/50 opacity-70 cursor-pointer transition-all hover:opacity-100'
: active
? 'border-primary bg-primary/5 ring-1 ring-primary/30 cursor-pointer transition-all hover:shadow-sm'
: 'border-divider bg-surface cursor-pointer transition-all hover:shadow-sm hover:border-primary',
]"
>
<!-- Checkbox indicator -->
<div class="w-6 h-6 flex items-center justify-center flex-shrink-0 mt-0.5">
<template v-if="auto && !active">
<svg class="w-5 h-5 text-on-surface-variant" fill="none" viewBox="0 0 20 20">
<circle
cx="10"
cy="10"
r="9"
stroke="currentColor"
stroke-dasharray="3 2"
stroke-width="1.5"
/>
<path d="M8 7l4 3-4 3" fill="currentColor" />
</svg>
</template>
<template v-else-if="active">
<svg class="w-5 h-5" viewBox="0 0 20 20">
<circle
class="text-primary"
cx="10"
cy="10"
fill="currentColor"
r="10"
/>
<path
class="text-on-primary"
d="M6 10l3 3 5-5"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</svg>
</template>
<template v-else>
<svg class="w-5 h-5 text-on-surface-variant/50" fill="none" viewBox="0 0 20 20">
<circle
cx="10"
cy="10"
r="9"
stroke="currentColor"
stroke-width="1.5"
/>
</svg>
</template>
</div>

<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<span class="font-semibold text-on-surface">{{ feature.name }}</span>
<span class="font-semibold text-xs px-2 py-0.5 rounded-full" :class="maturityClass">
{{ feature.maturity }}
</span>
<span v-if="auto && !active" class="text-xs text-on-surface-variant">
needed by {{ reason ?? 'another feature' }}
</span>
</div>

<p class="text-sm text-on-surface-variant">
{{ feature.description || feature.summary }}
</p>

<p v-if="feature.useCases.length > 0" class="text-xs text-on-surface-variant/70 mt-1">
Build: {{ feature.useCases.join(', ') }}
</p>
</div>
</button>
</template>
32 changes: 32 additions & 0 deletions apps/builder/src/components/IntentCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
const {
title,
description,
icon,
active = false,
} = defineProps<{
title: string
description: string
icon?: string
active?: boolean
}>()
</script>

<template>
<button
class="relative flex flex-col items-start p-5 rounded-lg border text-left transition-all cursor-pointer hover:shadow-md"
:class="active ? 'border-primary bg-primary/5 ring-2 ring-primary/30 shadow-md' : 'border-divider bg-surface hover:border-primary'"
>
<div
v-if="active"
class="absolute top-2 right-2 w-5 h-5 rounded-full bg-primary text-on-primary flex items-center justify-center text-xs"
>
&#10003;
</div>
<svg v-if="icon" class="w-8 h-8 mb-2" :class="active ? 'text-primary' : 'text-on-surface-variant'" viewBox="0 0 24 24">
<path :d="icon" fill="currentColor" />
</svg>
<h4 class="font-semibold text-on-surface">{{ title }}</h4>
<p class="text-sm text-on-surface-variant mt-1">{{ description }}</p>
</button>
</template>
47 changes: 47 additions & 0 deletions apps/builder/src/components/ModeCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script setup lang="ts">
const {
title,
description,
icon,
disabled = false,
locked = false,
recommended = false,
} = defineProps<{
title: string
description: string
icon: string
disabled?: boolean
locked?: boolean
recommended?: boolean
}>()
</script>

<template>
<button
class="flex flex-col items-start gap-3 p-6 rounded-lg border bg-surface text-left transition-all min-h-80"
:class="[
locked
? 'opacity-60 cursor-not-allowed border-divider'
: disabled
? 'opacity-50 cursor-not-allowed border-divider'
: recommended
? 'border-primary/30 ring-2 ring-primary/30 hover:shadow-lg hover:border-primary/50 cursor-pointer'
: 'border-divider hover:shadow-lg hover:border-primary/50 cursor-pointer',
]"
:disabled="disabled || locked"
>
<div class="flex items-center gap-3 w-full">
<svg class="w-6 h-6 text-primary" viewBox="0 0 24 24">
<path :d="icon" fill="currentColor" />
</svg>
<span v-if="recommended" class="text-xs text-primary font-semibold bg-primary/10 rounded px-2 py-0.5 ml-auto">
Recommended
</span>
<span v-if="locked" class="text-xs text-on-surface-variant border border-divider rounded px-2 py-0.5 ml-auto">
Vuetify One
</span>
</div>
<h3 class="text-lg font-semibold text-on-surface">{{ title }}</h3>
<p class="text-sm text-on-surface-variant">{{ description }}</p>
</button>
</template>
Loading
Loading