Skip to content

chore(builder): complete framework-creation flow, current with v0 master - #241

Open
johnleider wants to merge 113 commits into
masterfrom
feat/builder
Open

chore(builder): complete framework-creation flow, current with v0 master#241
johnleider wants to merge 113 commits into
masterfrom
feat/builder

Conversation

@johnleider

@johnleider johnleider commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

Builds apps/builder/ into a complete car-configurator wizard for assembling a v0-based Vue app from selectable plugins and components — now current with v0 master and able to produce a working framework start to finish.

The flow: landing → select plugins (15, incl. new ReducedMotion + Tooltip) → per-plugin config screens with a live preview aside (every configurable plugin has a dedicated preview module) → component picker (maturity-aware: draft components are visibly gated) → review build sheet → outputs: a zip starter that installs and builds green, and a playground URL carrying the full plugin configuration.

What's in this update

  • Merged 429 commits of master drift and realigned every config screen with current v0 option shapes (permissions tuples, locale without the non-public adapter, rules alias names, top-level theme foreground).
  • Codegen correctness: adapter imports from their real subpaths, third-party adapters emit wiring instructions instead of uncompilable zero-arg constructions, real rule predicates for known aliases (compile-time coverage via Record<KnownAlias, string>), explicit plugin install order with storage auto-included when anything persists, undefined config values survive, draft components/composables filtered from emission.
  • Generated starter actually verified: pnpm install && pnpm run build green, styled via an emitted uno.config mapping theme tokens to var(--v0-*) with fallbacks, demos rewritten on v0 compounds (zero native form controls).
  • Playground parity: payload emits src/main.ts so the user's configuration installs in the sandbox; framework.ts binds through a namespace (vue-repl cannot resolve re-exported bindings).
  • Live preview modules for all configurable plugins (notifications, logger, storage, stack, features, permissions, date, rules, tooltip, reduced-motion + the original four).
  • A11y: cards are Toggle.Root with real pressed state, every field labeled, CTAs announce their visible text, SVG indicators only.
  • Design pass: one visual language (graphite + teal, real elevation steps, mono for identifiers), landing hero built from the actual generated main.ts, segmented wizard progress, build-sheet review page, dark mode persisted, AA contrast verified.
  • Tests: apps/builder wired into the vitest workspace — 153 tests including drift guards (every config screen must have a factory emission; every known alias must have a predicate).

Verification

Three full Playwright walkthroughs (desktop/tablet/mobile, light/dark): complete flow, zero console errors, zip built and served, playground rendered with configured theme, state persistence across reload.

Known limitations

  • NumberField commits on blur/Enter (v0 has no eager-commit option), so number fields update the preview on commit rather than per keystroke.
  • The playground supplies its own uno.config.ts, so a deliberately partial palette can render unstyled borders there (zip output has fallbacks).

johnleider added 30 commits May 20, 2026 12:58
Bootstrap apps/builder workspace package modeled on apps/playground but
without the playground-specific deps (vue-repl, shiki, markdown). Same
tsconfig + vite config patterns so the oxc transformer resolves tsconfig
cleanly.
Carried forward from the deleted feature/framework-builder branch via
docs/builder-seed/:
- data/features.ts: curated catalog with name, summary, useCases, tags,
  icon over the generated dep graph
- data/dependencies.json: generated by build/generate-dependencies.ts
  on every `pnpm generate` (runs before vite via the dev script)
- data/questions.ts: 11 plugins across 5 categories (appearance, i18n,
  infrastructure, access, utilities) - the only surface upfront config
  needs to ask about
- engine/resolve.ts: pure function that walks the dep graph and reports
  { selected, autoIncluded, reasons, warnings }
- engine/manifest.ts: encodes selection into a fflate-compressed hash
  for handoff to apps/playground
- main.ts wires Pinia + router + v0 plugins (hydration, breakpoints,
  storage, theme) with light/dark color tokens
- pages/index.vue lands users with a one-paragraph explainer and a
  single CTA to /wizard
- pages/wizard.vue runs the wizard in two phases via a plain
  shallowRef<'plugins' | 'review'>:
  - plugins phase renders the questions.ts taxonomy as a category-
    grouped toggle grid
  - review phase shows selected + auto-included dependencies + any
    warnings, pulled from the engine/resolve output
- stores/builder.ts holds the selection Set and exposes resolved as a
  toRef derived from the dep graph

Deliberately skipped from the previous branch: IntentCard + multi-
intent flow (only component-library exists), mode cards, the stepper
integration, breadcrumb sync, Free Pick and AI pages.
Add generateMainTs() and generatePluginCalls() to manifest.ts. The
generator iterates a FACTORY map covering all 13 plugins and emits
`app.use(createXPlugin(config))` calls. When the stored config is
empty/absent, the call is no-arg. The useDate special-case always
emits `{ adapter: new V0DateAdapter() }` since the plugin throws
when the adapter is missing.
New engine/zip.ts exports generateZip(manifest) and downloadZip(manifest)
producing a downloadable starter project. The archive includes
package.json, vite.config.ts, tsconfig.json, uno.config.ts, index.html,
README.md, .gitignore, App.vue, src/pages/index.vue, and main.ts
generated by manifest.ts so per-plugin config is threaded through.
Replace the Wave 1 placeholder with the real review screen. Shows
selected plugins with customized/defaults/(no config) status, selected
components, auto-included deps, and any resolver warnings. Two action
buttons: "Open in Playground" (encodes a FrameworkManifest via
toPlaygroundUrl) and "Download starter (.zip)" (calls downloadZip).
Footer offers Reset all and Back.
Date / Logger / Notifications / Features plugins save the adapter as a
string in their config (e.g. 'V0DateAdapter', 'KnockNotificationsAdapter').
The generator was JSON-stringifying the whole config object, producing
broken TypeScript like `createDatePlugin({ adapter: "V0DateAdapter" })`
where the plugin expects a class INSTANCE.

The existing useDate sentinel trick also didn't work because the spread
order overwrote the placeholder.

Introduce a per-plugin ADAPTER_HANDLERS table that:
- Maps each adapter string to a raw TS expression (e.g. `new V0DateAdapter()`)
- Returns the v0 class names that need to be added to the import list
- Returns `null` to omit the adapter field entirely (e.g. 'none' for
  Notifications / Features)

Imports are now collected per-plugin and merged into the @vuetify/v0
import list so adapter classes always travel alongside their factory.

useDate always emits an adapter (it's required); 'custom' emits a
TODO-flagged `new CustomDateAdapter()` for the user to fill in.
- F1 (must): customized() now resolves plugin meta via the exported
  getPluginById helper and short-circuits to "stock" for plugins with
  hasConfig: false (useHydration was stuck "customized" forever since
  it has no defaults.ts to compare against)
- (c) free: saved === undefined -> isUndefined(saved)
- F3 (low): index.vue's local meta() now delegates to getPluginById
  instead of reimplementing the PLUGINS.find lookup
- F2 (must): chip rows carry a visually hidden stock/customized state
  text alongside the color-only chip-on/chip-quiet styling
- F4 (low): manifest panel gets role="region" + an aria-label naming it
- F5 (low): source switches from toRef to computed, since generateMainTs
  is expensive and was re-running on every read
…lock, visible dots

- Gate the MiniFrame mock app AND the customized-chip row on
  route === '/builder' (step 1 only); config routes that fall back to
  PreviewSummary (e.g. /builder/hydration) keep both, unchanged.
- On step 1, the manifest panel flex-fills all remaining sticky-aside
  height instead of sizing to content, with the code block absorbing the
  fill and scrolling internally — no dead space under a fixed-height cap.
  Peek is dropped on step 1 (fights the fill layout): the block always
  fills and scrolls there. Threaded a route+breakpoint `fill` flag through
  AppShell -> PreviewPane -> PreviewSummary (each computed locally, per
  the existing per-component convention) so this only engages on the
  desktop sticky aside; the mobile bottom sheet has no flex ancestor with
  a definite height to fill into, so it keeps the capped (24rem) + peek
  block instead.
- Add an opt-in fill/maxHeight mode to GnDocsExampleCode so the content
  area scrolls internally instead of growing unbounded; the peek toggle
  stays outside the scroll region so it never scrolls out of view.
- Restructure the manifest panel so GnDotGrid only layers the body
  (invite line + code), not the "src/main.ts ... N lines" header bar.
- Make the dot grid actually read as a background: the code block's own
  surface (var(--v0-pre, var(--v0-surface))) is fully opaque, which hid
  the dots behind it in any populated state. Override --v0-pre to a
  translucent color-mix from the builder app (packages/genesis untouched),
  raise GnDotGrid's coverage to 30 (from the default 15) and turn on its
  `lines` prop so the pattern reads as an obvious blueprint grid rather
  than a handful of faint dots — visible at a glance in both empty and
  populated states, light and dark.
- Add shiki, @shikijs/langs, @shikijs/themes (catalog:) and wire up
  useHighlighter.ts / useHighlightCode.ts / constants/shiki.ts, ported
  from apps/docs with the vue lang swapped for typescript and the
  docs-only API transformer + idle-callback path stripped (builder wants
  eager highlighting, not the idle-prefetch used for docs example panes).
  `prehighlight` stays unexported here — nothing outside the module calls
  it, unlike docs' multi-file example prewarming.
- PreviewSummary.vue drives GnDocsExampleCode's default slot with
  useHighlightCode(source), falling back to a plain <pre> until the
  highlighter loads. No packages/genesis changes — the slot seam already
  existed.
- Global .shiki override in App.vue: shiki (defaultColor: false, dual
  themes) only emits --shiki-light/--shiki-dark as custom properties on
  each token span — nothing consumes them as `color` without a rule, so
  every fallback attempt at reusing apps/docs' override rendered
  monochrome text. apps/docs' version also assumes a `.dark` ancestor
  class; this app only sets [data-theme] on <html> (createThemePlugin,
  target: 'html'), so the added rule keys off that instead. Background is
  forced transparent (not shiki's own light/dark bg) so the manifest's
  translucent --v0-pre surface — and GnDotGrid behind it — still shows
  through the code.
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

⚠️ No changeset found

This PR changes packages/* source but has no changeset, so it won't be in the next release's version bump or notes. Add one:

pnpm changeset

Pick the affected package(s) — @vuetify/v0 carries @vuetify/paper automatically; @paper/genesis (and other @paper/* design systems) version separately — a bump type, and a short summary, then commit the generated .changeset/*.md. Docs-only, chore, or CI PRs can ignore this.

johnleider and others added 22 commits August 3, 2026 15:43
Removes the top "← Back" button and "STEP N · X" eyebrow across the wizard
pages, replacing them with a chevron-left icon button next to the primary
action and a linear progress hairline, both in the fixed bottom bar. Frees
up vertical space so the h1 is the first element on each step.
# Conflicts:
#	vitest.config.ts

Co-authored-by: John Leider <johnleider@users.noreply.github.com>
The fixed StepBar at the bottom (4rem + 1px) could intercept clicks or
cause scroll-to targets to land under it. Adding scroll-padding-bottom:
6rem to html ensures in-page anchors and router scroll behavior position
targets above the fixed bar.

Co-authored-by: John Leider <johnleider@users.noreply.github.com>
…ed logic

statusFor() previously considered any config object with keys as 'customized',
but this was incorrect — a config saved with default values should show as
'defaults'. Now uses the same comparison logic as PreviewSummary.vue:
1. Check if config exists (undefined → defaults)
2. Compare against actual plugin defaults from defaults.ts modules
3. Only mark as 'customized' when JSON differs from defaults

Co-authored-by: John Leider <johnleider@users.noreply.github.com>
…romotion

DataGrid and DataTable were promoted from draft to preview in v1.2.0,
so they're now selectable and should be exported. Update the DRAFT test
array to match the current maturity.json state.
- review.vue: flip negated condition to positive comparison
- manifest.test.ts: remove unnecessary 's' flag from regex
- PreviewRules.vue: use non-capturing group for numeric pattern
@johnleider
johnleider marked this pull request as ready for review September 5, 2026 05:22
@johnleider johnleider added this to the v1.2.x milestone Sep 5, 2026
@johnleider johnleider added the enhancement New feature or request label Sep 5, 2026
@johnleider johnleider self-assigned this Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants