본문으로 건너뛰기
cccskills
무료GitHub에 공개

crouton-architecture-contract

The crouton mental model for someone who has never seen this repo — what "layer" means (two different things), the two-halves rule, the three app.config registries, team-scoped multi-tenancy, manifest-driven field types, the numbered invariants with the incidents behind them, and the known-weak points. Use when orienting in this codebase for the first time, when a question is shaped like "how does crouton work / why is it built this way / where does X live", when deciding whether a change belongs in a package or a generated layer, when confused by an extends chain or by croutonApps/croutonCollections/croutonLayoutBlocks, or before proposing any structural change. Trigger phrases: "what is a crouton layer", "package vs generated layer", "how does multi-tenancy work here", "why is there a stub component", "where do field types come from", "what invariants must hold".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md15.6 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Crouton Architecture Contract

The load-bearing mental model of crouton: definitions, design decisions with their WHY, the invariants that must hold, and the honestly-stated weak points. This is theory + contract; the how-to lives in sibling skills.

When to use / when NOT to use

You wantGo to
The mental model, invariants, "why is it like this"this skill
Generate a collection (workflow, MCP tools, gates)crouton skill (.claude/skills/crouton.md)
Schema JSON format, field-type tables, artifact listsibling crouton-generation-reference
Layout engine theory (LayoutTree, viability, placer)sibling crouton-layout-reference; authoring a block → block-authoring skill
Symptom → root cause lookupsibling crouton-diagnostics-index
Cold-start env setup, dist/install trapssibling crouton-build-and-env
Which docs are stale vs canonical (full list)sibling crouton-docs-trust-map
Which gate fires on a changesibling crouton-change-control

Trust order when sources conflict: see sibling crouton-docs-trust-map §1 (it owns the ranked chain; code beats prose for runtime behaviour).

Glossary (repo jargon, first-contact)

TermMeaning here
croutonTwo referents: (1) the whole framework; (2) the CLI (packages/crouton-cli, bin crouton) that generates collections. (The umbrella Nuxt module that used to live in packages/crouton was removed in #2290 — apps extend @fyit/crouton-core + addons instead)
collectionThe unit crouton generates: one entity/table, defined by a schema JSON, generated into a self-contained layer dir. Always team-scoped
layerA Nuxt Layer (a Nuxt app consumed via extends). Two meanings in this repo — see §1
fieldsFileThe per-collection field-definition JSON (schemas/<name>.json) referenced by crouton.config.js
teamThe multi-tenancy unit = a better-auth organization. Every generated API lives under /api/teams/[id]/…
fixtureA throwaway generated app in fixtures/ booted by the Playwright harness in e2e/ to smoke boot/auth/CRUD (see fixtures/CLAUDE.md)
POCA real crouton app in pocs/ incubating a future package; safe-to-fail, no required gates
graduationThe deliberate spec-driven rebuild of a proven POC into real package(s) + app (/graduate skill, epics #916/#992)
gateA hold point where a human signs off before the expensive step (Schema #314 / UI #307 / Test #774 / code review). Resume signal is an lgtm/approve comment only (#572)
stageWhere code lives in the lifecycle, declared in harness.config.mjs (poc/app/package/unstaged). Resolve with node scripts/harness-stages.mjs <path>
manifestA package's crouton.manifest.ts — declares its field types, bundled-ness, reserved names, contributions (§5)

1. "Layer" means two different things

Newcomer trap #1. Both are Nuxt Layers technically, but they play different roles:

  1. Package layer — packages/ holds the crouton-* packages (count: re-verify block). Most are publishable Nuxt Layers consumed as source: packages/crouton-core/package.json has "main": "./nuxt.config.ts" (no build step for the layer itself; dist/ exists only for specific subpath exports — install traps: sibling crouton-build-and-env). Exceptions (check each package.json): crouton-devtools is a Nuxt module ("main": "./dist/module.mjs"), crouton-cli is the bin CLI, crouton-mcp is the MCP server, crouton-themes has no main at all (subpath-export theme layers only).
  2. Generated per-collection layer — inside a consuming app, layers/<layer>/collections/<collection>/ is itself a nested Nuxt layer with its own nuxt.config.ts. Real tree (fixtures/minimal/layers/main/collections/items/):
layers/main/collections/items/
├── app/components/{_Form.vue, List.vue}
├── app/composables/useMainItems.ts
├── server/api/teams/[id]/main-items/{index.get,index.post,[itemId].patch,[itemId].delete}.ts
├── server/database/{schema.ts, queries.ts}
├── types.ts  nuxt.config.ts  README.md

(Newer generations add tests, seed.json, and app-root aggregates — the full artifact list is crouton-generation-reference's job.)

The extends chain, in a real launched app (apps/velo/nuxt.config.ts):

modules: ['@fyit/crouton-devtools'],          // the only crouton entry in `modules` (optional)
extends: [
  '@fyit/crouton-core', '@fyit/crouton-i18n',
  '@fyit/crouton-assets', '@fyit/crouton-pages', '@fyit/crouton-bookings',
  '@fyit/crouton-email',
  './layers/bookings', './layers/pages',       // ← generated layers
  '@fyit/crouton-maps', './layers/crouton'
]

And @fyit/crouton-core itself extends ['@fyit/crouton-i18n', '@fyit/crouton-auth', '@fyit/crouton-admin'] (its nuxt.config.ts; "Order matters: i18n provides translation system that auth/admin consume") — so auth/admin/i18n arrive through core ("bundled"). Velo lists crouton-i18n explicitly anyway; see invariant 5 for why that pattern is dangerous with auth/admin.

extends is the only package switch. An app turns a crouton package on by listing it in extends (hand-listed, kept in sync by the CLI's syncFrameworkPackages); crouton.config.js features is generation input, not a switch (#2286). The former umbrella Nuxt module (packages/crouton, listed under modules, with an extends-builder helper) was removed in #2290 — a Nuxt module can't add layers at runtime, so it never did more than wire runtimeConfig and warn.

2. The two-halves rule (pointer)

Canonical: this section (it lived in the meta package's CLAUDE.md until #2290 removed it). In one line: a crouton feature = a package (UI, admin pages, nav, composables, i18n — via extends) + a generated layer (Drizzle schema, API endpoints, configs, types — via the CLI), and they need each other: package without layer → no tables/endpoints (404/500); layer without package → a generic "Collections" sidebar with no dedicated UI. When something "is installed but doesn't work", check which half is missing first. Changing (#2278): a package with ownsCollections: true in its manifest ships both halves itself (sales first — packages/crouton-sales/collections/*); the CLI refuses to generate those collections into an app. Bookings, pages and assets still follow the two-halves rule until their slices land.

3. The three registries (all app.config, defu-merged across layers)

RegistryWho writes itWho reads it
croutonCollectionsThe generator upserts one entry per collection into the app's app/app.config.tsCore's shared CRUD components (CroutonCollection, forms, lists) resolve a collection by registry key
croutonAppsEach addon package self-registers in its own app/app.config.ts (e.g. crouton-bookings)Sidebar/nav via useCroutonApps(), and presence detection via hasApp(id)
croutonLayoutBlocksEach package contributes placeable layout blocks (defaults in crouton-layout; bookings adds calendar blocks)The layout engine — sibling crouton-layout-reference

Stub-priority pattern — mechanics + code are owned by root CLAUDE.md § "Optional Cross-Package Components (Stub Pattern)". The WHY this skill adds: optional packages (editor, maps, assets, collab) may or may not be in the extends chain, and Vue has no clean "is this component available?" check (resolveComponent() warns unconditionally when absent; vueApp._context.components is private API). So core ships no-op stubs (packages/crouton-core/app/components/stubs/) at priority: -1; the real addon component at default priority silently wins when present — preventing warning spam + broken renders in apps without the addon. Behavior branching uses hasApp('assets'). Addons registering routes use a parentApp key (type in crouton-core/app/types/app.ts) so defu array-merging doesn't collide nav entries.

4. Team-scoped multi-tenancy

better-auth with the organization plugin; team = organization (canonical package: packages/crouton-auth). The resolver is resolveTeamAndCheckMembership(event) in packages/crouton-auth/server/utils/team.ts — mode-aware per its JSDoc (multi-tenant / single-tenant / personal), returns { team, user, membership }, throws 401/403/404. Every generated endpoint lives at /api/teams/[id]/<layer>-<plural>/ and starts:

import { resolveTeamAndCheckMembership } from '@fyit/crouton-auth/server/utils/team'
const { team } = await resolveTeamAndCheckMembership(event)
// every query takes team.id

Client side: useTeamContext() (packages/crouton-core/app/composables/useTeamContext.ts) reads the [team] route param. There is no opt-out — the generator hardcodes the auth import and passes team.id to every query (invariant 3). Core also wires a #crouton/team-auth nitro alias as a compat path; generated code uses the direct import.

5. Manifest-driven field types

Field types are not hardcoded in the CLI. Each package declares a crouton.manifest.ts (defineCroutonManifest from @fyit/crouton-core/shared/manifest); the CLI's manifest loader merges them into the type mapping. Core's manifest declares the base types (string, text, number, … — exact list in the file) with db/drizzle/zod/tsType/defaultValue/component, plus aliases (integer→number, datetime→date) that resolve to canonical before generators run — the #285 incident is why (datetime once leaked through: raw <UInput> + a text column). The manifest also owns autoGeneratedFields (id, teamId, owner, createdAt, … — never declare these), reserved field/collection names, bundled (read by the CLI's scaffold/doctor as "core already provides it"), and detects patterns for addon generator contributions. Full tables: sibling crouton-generation-reference. Adding a field type = a manifest change, not a CLI patch.

6. Invariants (compressed to pointers — read the canonical source, don't re-derive)

#InvariantCanonical sourceIncident / why
1Dep direction crouton-layout → crouton-core, never reverse (shared layout types live in core so feature packages contribute blocks dep-free)packages/crouton-layout/CLAUDE.md ("HARD RULE")reverse = cycle
2Optional components via stub-priority, never resolveComponent()root CLAUDE.md § Critical Gotchaswhy: §3
3Every collection team-scoped, no opt-out (§4)the generated handlers themselvestenancy leaks; API tests go red if teamId dropped (#791)
4packages/ is edit-guarded (HARD GATE)root CLAUDE.md § Packages Boundary; hook gate-package-edits.sh; flow → crouton-change-controlevery consumer inherits the change
5Never re-extend bundled auth/admin alongside corepackages/crouton-cli/CLAUDE.mdduplicate layers → SSR $setup.t is not a function
6hub: { db: 'sqlite' }, never hub: { database: true }; NuxtHub = storage only, never deployroot CLAUDE.md § Critical Gotchasbreaks schema.entry resolution + local migrations
7Translatable fields required: false at the root column (real value in translations.{locale}.field; root is cache/fallback)packages/crouton-cli/CLAUDE.md ("Why this matters")NOT NULL root fails translation-only inserts
8Layout is data (a LayoutTree in team-scoped layout_configs), not generated .vuecrouton-layout/server/database/schema/layoutConfigs.ts (code)runtime-editable + diffable for the agent⇄human loop
9Field types / capabilities are manifest-declared (§5) — one source, two consumers (CLI, MCP)crouton.manifest.ts per packagehardcoded tables drift per consumer (#285)
10Core's ensure-hub-blob module loads before @nuxthub/core; don't reorder core's modulescomment in packages/crouton-core/nuxt.config.tsdb: 'sqlite'-only apps still get blob

(Install/postinstall/typecheck invariants → sibling crouton-build-and-env; deploy invariants → crouton-ci-and-deploy-map.)

7. Known-weak points (plainly)

Weak pointEvidenceStatus
Production-SSR 500 on collection pages — an internal data-fetch loses the auth cookie under nuxt preview, so the entire e2e harness runs against nuxt dev (source of its huge timeouts)e2e/CLAUDE.md; tracked as #246 (per the e2e-smoke skill)unresolved, load-bearing
packages/crouton-core/CLAUDE.md is partly stale — references dead names (nuxt-crouton-i18n, @crouton/auth)the file itselfdon't trust its import examples; full stale list → crouton-docs-trust-map
ISR routeRules contradiction — root CLAUDE.md recommends a wildcard /api/teams/*/... ISR rule that live code comments mark BROKENowner: crouton-diagnostics-index § "The ISR routeRules contradiction"don't add such wildcard rules
CLI mirrors block sizing contracts by hand — crouton-cli/lib/compose-layout.ts "keep in sync" comment (whose own target list has drifted; live default registry is in crouton-layout/app/app.config.ts)the file's commentsilent-drift hazard on minWidth/defaultSize changes
packages/crouton-layout/CLAUDE.md self-contradicts — marks server-side extraction pending while layout_configs schema + API exist on diskfiles existtrust the code

Provenance and maintenance

verified: 2026-07-03

Facts checked against the working tree (velo/core/crouton/auth/layout/cli configs + CLAUDE.mds, fixtures/minimal generated tree, e2e/CLAUDE.md). Issue numbers cited from discovery-report summaries — check the issue if load-bearing. Volatile facts live below, not in the prose:

ls packages | wc -l                                                             # package count (31 at verification)
grep -rn "modules: \[" apps/*/nuxt.config.ts fixtures/*/nuxt.config.ts pocs/*/nuxt.config.ts  # only '@fyit/crouton-devtools' may appear as a crouton entry (umbrella module gone, #2290)
grep -n "extends" apps/velo/nuxt.config.ts                                      # extends chain still hand-listed?
ls packages/crouton-core/app/components/stubs/                                  # stub list (7 at verification)
grep -n "priority" packages/crouton-core/nuxt.config.ts                         # stubs still -1?
grep -n "500 under production SSR" e2e/CLAUDE.md                                # #246 still unresolved?
grep -n "Keep in sync" packages/crouton-cli/lib/compose-layout.ts               # CLI sizing-mirror comment still there?
node scripts/harness-stages.mjs packages/crouton-core                           # packages still edit-guarded/test-first?

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

a11y

무료

Accessibility review for Vue surfaces — the code-cleaning analog of /code-review and /simplify, pointed at WCAG/ARIA. Reviews just your diff (or a package/file), rates findings by severity, and either comments inline on the PR (--comment) or applies the safe fixes for you (--fix). Steers the depth-aware `a11y` subagent. Use when asked to "check accessibility", "a11y this", "audit ARIA/keyboard", or run /a11y.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

FriendlyInternet/nuxt-crouton102026년 10월 21일 업데이트

ask-human

무료

Emit a blocking question the owner can read in ~10 seconds and answer in one reply — the scannable, recommendation-first handoff every agent posts when it hits a fork it can't own. Leads with the one decision + a recommendation, carries the 🤖 provenance header, doubles as the

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

FriendlyInternet/nuxt-crouton102026년 10월 21일 업데이트

audit

무료

Audit packages for documentation completeness, detect drift between code and docs, and maintain documentation quality across the monorepo. Use when checking package docs, running audits, or reviewing documentation health.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

FriendlyInternet/nuxt-crouton102026년 10월 21일 업데이트

Author a placeable layout block that looks right at ANY pane size. The one hard rule — size to the PANE with container queries (@container), never the viewport — plus list/form playbooks and the sizing contract (minWidth etc.) the viability metric reads. Use when adding/converting a croutonLayoutBlocks block, or when a block overflows/breaks in a narrow pane.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

FriendlyInternet/nuxt-crouton102026년 10월 21일 업데이트

When a bug or regression is reported, the FIRST step — before fixing — is to research how and when it was introduced (git archaeology), then record that finding on the tracking issue/PR. Use the moment a bug, error, broken build, or "this used to work" is reported, before writing a fix. Produces a first-bad-commit (or "not a code regression") note you paste onto the issue.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

FriendlyInternet/nuxt-crouton102026년 10월 21일 업데이트

commit

무료

Smart, granular git commits following monorepo conventions. Analyzes changes, filters to session-relevant files, groups by intent, and uses conventional commit format. Use when committing code changes.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

FriendlyInternet/nuxt-crouton102026년 10월 21일 업데이트

FriendlyInternet의 스킬 모두 보기

이 스킬의 문제 신고