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
18 changes: 13 additions & 5 deletions MD Instructions/00-START-HERE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,21 @@ finish and you can verify before moving on.

- **Stack:** Nuxt 4 (v4.5+, latest stable), Vue 3 `<script setup>`, TypeScript
strict, Pinia for state, Vitest + Vue Test Utils for tests.
- **UI framework:** Nuxt UI v4 (`@nuxt/ui`) for all editor chrome (inputs,
buttons, selects, modals, color pickers, etc.). Styling via Tailwind CSS v4
(ships with Nuxt UI). Use `app.config.ts` for theme customization.
- **UI framework:** Nuxt UI v4 (`@nuxt/ui`) for both the editor chrome (inputs,
buttons, selects, modals, color pickers, etc.) AND as the primary building
blocks in the visual builder (UPageHero, UPageSection, UButton, etc.). Styling
via Tailwind CSS v4 (ships with Nuxt UI). Use `app.config.ts` for theme
customization.
- **Building blocks:** The Component Registry wraps real Nuxt UI page-building
components (UPageHero, UPageSection, UPageColumns, UPageGrid, UPageCTA,
UPageFeature, UPageCard, UButton, UCard, USeparator) as the primary placeable
blocks. A small set of custom gap-filler blocks (RichText, Image, Spacer)
covers functionality Nuxt UI doesn't provide. The compiled output uses Nuxt UI
components directly — no custom component layer.
Comment on lines +66 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking (consistency): this new direction is not reflected in the two docs that are supposed to be the source of truth, so the doc set now contradicts itself.

This bullet (and the UI-framework bullet above) now say blocks are the primary Nuxt UI wrappers. But:

  • CLAUDE.md:20 — non-negotiable convention: "Blocks (app/components/blocks/) use Tailwind only — never Nuxt UI."
  • .kiro/steering/AI-MAP.md:119"Block components use Tailwind utilities (portable, no Nuxt UI dependency)."

Both still assert the opposite of this PR. An implementer following CLAUDE.md's "non-negotiable conventions" would build Tailwind-only blocks and directly violate these specs. The project's own Decisions Log requires these be kept in sync:

AI-MAP.md:148"When conventions in .kiro/steering/ change, CLAUDE.md must be updated in sync."

Since this is a design change (relates to #2), AI-MAP.md (line 119, and "exclusively" on line 120) and CLAUDE.md (line 20) should be updated in the same change. As merged, the docs disagree.

Fix this →

- **Layout model:** section/block **flow grid**, NOT absolute free-drag
positioning. This keeps generated Vue clean and responsive.
- **The editor is itself a Nuxt app.** The canvas renders the real components via
`<component :is>`, so the preview *is* production output.
- **The editor is itself a Nuxt app.** The canvas renders the real Nuxt UI
components via `<component :is>`, so the preview *is* production output.
- **Type everything.** The Registry and Page Document have shared TS types in
`/types`. Never duplicate a type; import it.
- **No `any`.** Prefer discriminated unions for node types.
Expand Down
52 changes: 30 additions & 22 deletions MD Instructions/02-component-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,37 @@ none of those need to hardcode component knowledge. Read `00-START-HERE.md` and
entry (missing label, unknown prop type, `acceptsChildren:false` but declares
default-slot children) should throw a clear error naming the entry.

3. **Starter component set.** Create real Nuxt components under
`/app/components/blocks/` AND their registry entries. Each component:
3. **Starter component set.** Create thin wrapper Nuxt components under
`/app/components/blocks/` AND their registry entries. The primary blocks wrap
real Nuxt UI components; a few custom gap-fillers handle what Nuxt UI lacks.

Each Nuxt UI wrapper component:
- Uses `<script setup lang="ts">` with `defineProps` typed to match its
registry `props`.
- Passes all registered props through to the actual Nuxt UI component.
- Renders cleanly with sensible defaults so it looks fine on an empty canvas.
- Uses Tailwind CSS v4 utility classes for styling. Keep components simple and
self-contained — no dependency on Nuxt UI (blocks should be portable for
the compiled output).
- Sets `compileAs` in the registry entry to the actual Nuxt UI tag name
(e.g., `compileAs: 'UPageHero'`).

Custom gap-filler components use Tailwind CSS v4 utility classes and have no
`compileAs` (they compile as their own component name).

Minimum set:
| type | category | key props | slots / children |
|------|----------|-----------|------------------|
| `Section` | layout | `padding(enum sm/md/lg)`, `bg(color)`, `maxWidth(enum full/wide/narrow)` | acceptsChildren |
| `Columns` | layout | `gap(enum sm/md/lg)` | acceptsChildren, `allowedChildren: ['Column']` |
| `Column` | layout | `span(number min:1 max:4 step:1)` | acceptsChildren, `allowedParents: ['Columns']` |
| `Hero` | content | `heading(string)`, `subheading(text)`, `align(enum left/center)`, `bg(color)` | slot: `actions` |
| `Heading` | content | `text(string)`, `level(enum h1..h4)`, `align(enum)` | none |
| `Text` | content | `body(text)`, `align(enum)` | none |
| `Button` | content | `label(string)`, `href(url)`, `variant(enum primary/secondary/ghost)` | none |
| `Image` | media | `src(image)`, `alt(string)`, `rounded(boolean)` | none |
| `Spacer` | layout | `size(enum sm/md/lg)` | none |
| type | compileAs | category | key props | children |
|------|-----------|----------|-----------|----------|
| `PageHero` | UPageHero | layout | `title(string)`, `description(text)`, `headline(string)`, `orientation(enum vertical/horizontal)` | none |
| `PageSection` | UPageSection | layout | `title(string)`, `description(text)`, `headline(string)`, `orientation(enum)`, `reverse(boolean)` | acceptsChildren |
| `PageColumns` | UPageColumns | layout | (none) | acceptsChildren |
| `PageGrid` | UPageGrid | layout | (none) | acceptsChildren |
| `PageCTA` | UPageCTA | content | `title(string)`, `description(text)`, `headline(string)` | none |
| `PageFeature` | UPageFeature | content | `title(string)`, `description(text)`, `icon(string)`, `orientation(enum)` | none |
| `PageCard` | UPageCard | content | `title(string)`, `description(text)`, `to(url)` | none |
| `Button` | UButton | content | `label(string)`, `to(url)`, `color(enum)`, `variant(enum)`, `size(enum)` | none |
| `Card` | UCard | content | (none) | acceptsChildren |
| `Separator` | USeparator | layout | `orientation(enum horizontal/vertical)`, `label(string)` | none |
| `RichText` | (custom) | content | `body(text)`, `align(enum)` | none |
| `Image` | (custom) | media | `src(image)`, `alt(string)`, `rounded(boolean)` | none |
| `Spacer` | (custom) | layout | `size(enum sm/md/lg)` | none |
Comment on lines +59 to +73

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency: the new starter set orphans two AI-MAP.md statements.

  1. The old Columns/Column block types are gone (replaced by PageColumns with plain acceptsChildren). But AI-MAP.md:127 still lists the convention "Columns block: only accepts Column children (enforced by registry + validation)", and the Decisions Log at AI-MAP.md:147 documents the allowedChildren: ['Column'] / allowedParents: ['Columns'] rule. Those now reference types this table no longer defines. Not relitigating the decision — flagging that the entries it depends on were removed, so either PageColumns should carry the constraint forward or the log/convention needs a sync note.

  2. This table now defines 13 blocks, but AI-MAP.md:61 still says the registry task delivers "8 starter blocks", and the same "8" wording likely lives in the registry test acceptance (registry.test.ts) an implementer will write.

Neither is in this diff, so as merged the map is stale against the spec.


4. **Registry purity split:**
- **`/registry/entries.ts`** — export all `RegistryEntry` data as pure data
Expand All @@ -83,15 +93,13 @@ none of those need to hardcode component knowledge. Read `00-START-HERE.md` and
## Acceptance criteria
- `npm run typecheck` passes.
- A unit test in `/tests/registry.test.ts` asserts: every `componentMap` key has a
matching `registry` entry and vice-versa; `defaultPropsFor('Hero')` returns the
declared defaults; zod validation throws on a deliberately broken entry.
matching `registry` entry and vice-versa; `defaultPropsFor('PageHero')` returns
the declared defaults; zod validation throws on a deliberately broken entry.
- Each block component can be imported and mounted in isolation without errors.
- **Purity check:** `/registry/entries.ts` has zero `.vue` imports; importing it
from a plain Node/tsx script does not throw.
- **Columns rule:** `Columns` declares `allowedChildren: ['Column']` and `Column`
declares `allowedParents: ['Columns']`. Both constraints are validated by
`validateAgainstRegistry` (Task 03) and enforced by drop-target guards
(Task 07). Tasks 07 and 09 rely on these invariants.
- **Nuxt UI blocks:** Every Nuxt UI wrapper block has a `compileAs` field starting
with `'U'` and renders the actual Nuxt UI component in the editor canvas.

## Out of scope
No canvas, no editor, no drag-drop. Components should render standalone but need
Expand Down
Loading