Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/compose-vike-themes-layouts-shell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@gemstack/ai-autopilot': minor
---

Compose vike-themes / vike-layouts for styling and the app shell instead of hand-rolling CSS + nav

After auth, data, and the CRUD/admin UI, the remaining big hand-rolled surface is styling and the app shell: an agent writes its own CSS design system, dark-mode toggle, and layout/nav chrome on every build, and that fresh CSS is the root of the loop's over-polish churn. The new `vike-shell-composer` persona (wired into `vikeExtensionPersonas`) teaches the agent to declare a brand with `defineTheme` and `extends: [themesExt]` (flash-free system dark mode, a picker, and a CSS-variable contract to style against) instead of hand-writing a color system, and to pick a shell with `vike-layouts` (`layout: 'centered' | 'topbar' | 'sidebar'` plus `logo` / cumulative `nav` slots) instead of a hand-written topbar/sidebar. It also notes the one-line `vike-toolbar` install that gives the theme/locale controls a home. No runtime change; the agent stays a black box. Part of #186. Closes #192.
27 changes: 25 additions & 2 deletions packages/ai-autopilot/src/personas/library.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
vikeCrudComposer,
vikeDataModeler,
vikeExtensionPersonas,
vikeShellComposer,
} from './library.js'

describe('vike extension personas', () => {
Expand Down Expand Up @@ -57,9 +58,31 @@ describe('vike extension personas', () => {
assert.match(text, /ejectView\(view, \{ framework \}\)/)
})

it('vikeExtensionPersonas composes data + auth + crud (no Prisma, no hand-rolled auth/UI)', () => {
it('vikeShellComposer teaches composing vike-themes/vike-layouts, not hand-rolled CSS + shell', () => {
assert.equal(vikeShellComposer.name, 'vike-shell-composer')
const text = personaInstructions(vikeShellComposer)
// Styling comes from vike-themes (a theme + CSS vars), not a hand-written design system.
assert.match(text, /defineTheme/)
assert.match(text, /vike-themes\/react/)
assert.match(text, /appearance/)
assert.match(text, /var\(--primary\)/)
// The app shell comes from vike-layouts, not a hand-written topbar/sidebar.
assert.match(text, /vike-layouts\/react/)
assert.match(text, /'centered'/)
assert.match(text, /do NOT write your own\s*\n?\s*color system/i)
// The toolbar is an install-and-forget one-liner.
assert.match(text, /vike-toolbar\/react/)
})

it('vikeExtensionPersonas composes data + auth + crud + shell (no Prisma, no hand-rolled auth/UI/CSS)', () => {
const names = vikeExtensionPersonas.map(p => p.name)
assert.deepEqual(names, ['vike-data-modeler', 'vike-auth-composer', 'vike-crud-composer', 'ui-intent-designer'])
assert.deepEqual(names, [
'vike-data-modeler',
'vike-auth-composer',
'vike-crud-composer',
'vike-shell-composer',
'ui-intent-designer',
])
assert.ok(vikeExtensionPersonas.includes(uiIntentDesigner))
})
})
72 changes: 68 additions & 4 deletions packages/ai-autopilot/src/personas/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,67 @@ Customization ladder — start generated, refine only where reality demands it:
it last, not by hand-rolling screens from the start.`,
})

/**
* Composes `vike-themes` (styling) + `vike-layouts` (app shell) instead of
* hand-rolling a CSS design system and a layout/nav shell. Styling is the most
* over-polished AI surface — freshly-written CSS is infinitely pickable, which is
* the root of the loop's over-polish churn — and the app shell is re-invented on
* every build. Both are declarations here. Opt-in, in-workspace only (the packages
* resolve inside the vike-data workspace) — see `vikeExtensionPersonas`.
*/
export const vikeShellComposer: Persona = definePersona({
name: 'vike-shell-composer',
role: 'Composes vike-themes + vike-layouts for styling and the app shell, not hand-rolled CSS and nav',
appliesTo: ['vike-themes', 'vike-layouts', 'vike-toolbar'],
systemPrompt: `You compose vike-themes for styling and vike-layouts for the app shell, instead
of hand-writing a CSS design system, a dark-mode toggle, and a layout/nav shell.
Freshly-generated CSS is the most over-polished surface there is (the review loop
always finds something to pick at), and the shell is re-invented on every build.
Both are a declaration here, and both render through the same vike-blocks system
the crud screens use.

Styling = vike-themes, NOT hand-written CSS:
- Define a brand and extend the theming core; a single \`primary\` expands into the
full color ramp, and you get flash-free system dark mode plus a theme picker:
\`\`\`js
import themesExt from 'vike-themes/react'
import { defineTheme } from 'vike-themes'
const acme = defineTheme({
name: 'acme', radius: '4px',
light: { bg: '#fffdf7', text: '#2a2016', primary: '#3b82f6' },
dark: { bg: '#1a140d', text: '#f3ead9', primary: '#e0772a' },
})
// +config.js: export default { extends: [themesExt], appearance: 'system', theme: 'acme', themes: [acme] }
\`\`\`
- \`appearance\` is \`'system'\` (follows the OS, no flash) | \`'light'\` | \`'dark'\`.
\`themes\` is cumulative — built-ins and theme packages compose in.
- Style AGAINST the theme's CSS variables (\`var(--bg)\`, \`var(--text)\`,
\`var(--primary)\`, etc.); do NOT hard-code colors, and do NOT write your own
color system or dark-mode switch. That is what the theme owns.

App shell = vike-layouts, NOT a hand-written layout/nav:
- Extend the layouts core, pick a shell, and fill its slots:
\`\`\`js
import layoutsExt from 'vike-layouts/react'
// +config.js: export default {
// extends: [layoutsExt], layout: 'topbar', logo: '◆ Acme',
// nav: [{ label: 'Home', href: '/' }, { label: 'Admin', href: '/admin' }],
// }
\`\`\`
- \`layout\` is \`'centered'\` (public pages) | \`'topbar'\` | \`'sidebar'\`, and a page
can override the app default. Slots (\`logo\`, the cumulative \`nav\`, footer, user
menu) are config; a shell renders only the slots it declares. Since the shells
are vike-blocks \`layout\` variants, this is the same block system the crud
screens render through. Do NOT hand-write a topbar/sidebar or a nav component.

Toolbar (one line, not its own concern): \`extends: [toolbarExt]\` from
\`vike-toolbar/react\` gives a settings popover a home; installed extensions (the
theme picker, a locale switcher) populate it automatically. Install it and move on.

Reach for a bespoke component only for a genuinely custom region, and even then
style it with the theme's CSS variables so it tracks light/dark and the brand.`,
})

/**
* The framework-neutral personas shared by every preset — the data layer and the
* intent-based UI guardrail apply the same whether the app is on Vike or Next.
Expand All @@ -354,15 +415,18 @@ export const sharedPersonas: readonly Persona[] = Object.freeze([
/**
* The opt-in vike-extension stack: compose `vike-auth` for authentication, the
* universal-orm data layer for domain data (both ride one registered adapter), and
* `vike-crud` / `vike-admin` for the CRUD/admin UI derived from the schema — instead
* of hand-rolling auth, hand-installing an ORM, or hand-writing list/record/form
* screens. Swap this in for {@link sharedPersonas} when composing extensions (Vike
* only; the extensions currently resolve inside the vike-data workspace).
* `vike-crud` / `vike-admin` for the CRUD/admin UI derived from the schema, and
* `vike-themes` / `vike-layouts` for styling and the app shell — instead of
* hand-rolling auth, hand-installing an ORM, or hand-writing list/record/form
* screens, a CSS design system, or a layout/nav shell. Swap this in for
* {@link sharedPersonas} when composing extensions (Vike only; the extensions
* currently resolve inside the vike-data workspace).
*/
export const vikeExtensionPersonas: readonly Persona[] = Object.freeze([
vikeDataModeler,
vikeAuthComposer,
vikeCrudComposer,
vikeShellComposer,
uiIntentDesigner,
])

Expand Down
Loading