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
8c171c1
chore: scaffold builder app
johnleider Apr 4, 2026
ca6d25f
chore(builder): add tsconfig files matching playground pattern
johnleider Apr 4, 2026
e779602
chore(builder): add dependency graph generator
johnleider Apr 4, 2026
6648dcb
chore(builder): tighten vitest config include pattern
johnleider Apr 4, 2026
018c03b
feat(builder): add manifest generation and playground handoff
johnleider Apr 4, 2026
ffffd18
feat(builder): add landing page with mode selection
johnleider Apr 4, 2026
ba214a3
chore(builder): add feature catalog data layer
johnleider Apr 4, 2026
e8062ed
feat(builder): add builder store
johnleider Apr 4, 2026
ff95426
fix(builder): fix maturity.json type cast for null since values
johnleider Apr 4, 2026
b6448a3
feat(builder): add IntentCard and FeatureCard components
johnleider Apr 4, 2026
ead23e3
feat(builder): add guided mode with intent and category steps
johnleider Apr 4, 2026
4a0435d
feat(builder): add review page with playground handoff
johnleider Apr 4, 2026
7b853ba
feat(builder): add free pick mode
johnleider Apr 4, 2026
b61246b
feat(builder): add AI builder page with auth gate
johnleider Apr 4, 2026
d820762
chore(builder): fix lint issues
johnleider Apr 4, 2026
e0a335d
chore(builder): add knip workspace config
johnleider Apr 4, 2026
24c3156
fix(builder): replace non-existent mdiShoeprint with mdiCompass
johnleider Apr 4, 2026
1123681
fix(builder): use v0play.vuetifyjs.com for playground handoff
johnleider Apr 4, 2026
300596c
feat(builder): improve card component styling
johnleider Apr 4, 2026
df000d6
feat(builder): polish page layouts and visual hierarchy
johnleider Apr 4, 2026
2438d4f
feat(builder): generate rich multi-file scaffold from manifest
johnleider Apr 4, 2026
9a74b94
feat(builder): polish guided flow with icons, intros, and selection f…
johnleider Apr 4, 2026
e02fa8d
feat(builder): add AppBar with logo, theme toggle, and layout system
johnleider Apr 4, 2026
8d0a724
refactor(builder): use layout wrapper, remove redundant page wrappers
johnleider Apr 4, 2026
fb36e9d
feat(builder): enrich features with icons, descriptions, and code exa…
johnleider Apr 4, 2026
6db6500
chore(builder): add layouts to knip entry patterns
johnleider Apr 4, 2026
e743192
fix(builder): use router-view in layout instead of slot
johnleider Apr 4, 2026
8bb3f35
fix(builder): fix logo and theme icon ref unwrapping in template
johnleider Apr 4, 2026
938cae1
fix(builder): remove inline icon from feature cards
johnleider Apr 4, 2026
96a4ed2
feat(builder): add copyright footer
johnleider Apr 4, 2026
be70856
fix(builder): pin footer to bottom with flex layout
johnleider Apr 4, 2026
4c8b0c8
refactor(builder): centralize content width in layout
johnleider Apr 4, 2026
40298ed
fix(builder): allow toggling auto-included features
johnleider Apr 4, 2026
49df854
refactor(builder): use v0 composables for all state management
johnleider Apr 4, 2026
4d13109
refactor(builder): use features.selected() instead of raw Set check
johnleider Apr 4, 2026
bf112e8
fix(builder): initialize stepper synchronously to prevent render error
johnleider Apr 4, 2026
fe2fa44
fix(builder): guard stepper index with safe default
johnleider Apr 5, 2026
c2cf068
fix(builder): fix self-referencing stepIndex causing stack overflow
johnleider Apr 5, 2026
adfdf38
fix(builder): initialize stepper in store with steps at creation
johnleider Apr 5, 2026
34fb970
fix(builder): remove .value from Pinia-unwrapped stepper/intent refs
johnleider Apr 5, 2026
0accf5a
fix(builder): remove .value from stepIndex in template
johnleider Apr 5, 2026
781abc4
feat(builder): add breadcrumb navigation to AppBar
johnleider Apr 5, 2026
c3f9896
feat(builder): show dependency reasons and fix selection logic
johnleider Apr 5, 2026
1e7daab
feat(builder): improve feature card UX
johnleider Apr 5, 2026
0a0fe95
feat(builder): add all missing composables to feature catalog
johnleider Apr 5, 2026
c5d73c5
feat(builder): add rich review summary to guided wizard
johnleider Apr 5, 2026
f4f76ee
feat(builder): use category icons in step indicator and improve inten…
johnleider Apr 5, 2026
cc77cce
feat(builder): persist intent and feature selections with v0 useStorage
johnleider Apr 5, 2026
9909c7a
fix(builder): replace overflow-y-scroll with scrollbar-gutter stable
johnleider Apr 5, 2026
82be601
feat(builder): horizontal mode cards + fix scrollbar gutter
johnleider Apr 5, 2026
8d1ce46
feat(builder): widen layout to max-w-4xl, taller mode cards
johnleider Apr 5, 2026
ed7a888
feat(builder): taller mode cards with more padding
johnleider Apr 5, 2026
f32f0fa
feat(builder): bump mode card height to min-h-80, revert padding
johnleider Apr 5, 2026
05b3a9b
feat(builder): add question-based data model for guided flow
johnleider Apr 5, 2026
0cedd60
feat(builder): rebuild guided mode with yes/no question flow
johnleider Apr 5, 2026
9233ad7
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