@vertz/create-vertz-app 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +7 -1
- package/dist/templates/index.d.ts +13 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +416 -3
- package/package.json +4 -2
package/dist/scaffold.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,WAAW,EAAE,MAAM;CAIhC;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DzF"}
|
package/dist/scaffold.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { appComponentTemplate, bunfigTemplate, bunPluginShimTemplate, clientTemplate, dbTemplate, entryClientTemplate, envExampleTemplate, envModuleTemplate, envTemplate, gitignoreTemplate, homePageTemplate, packageJsonTemplate, schemaTemplate, serverTemplate, tasksEntityTemplate, themeTemplate, tsconfigTemplate, vertzConfigTemplate, } from './templates/index.js';
|
|
3
|
+
import { apiDevelopmentRuleTemplate, appComponentTemplate, bunfigTemplate, bunPluginShimTemplate, claudeMdTemplate, clientTemplate, dbTemplate, entryClientTemplate, envExampleTemplate, envModuleTemplate, envTemplate, gitignoreTemplate, homePageTemplate, packageJsonTemplate, schemaTemplate, serverTemplate, tasksEntityTemplate, themeTemplate, tsconfigTemplate, uiDevelopmentRuleTemplate, vertzConfigTemplate, } from './templates/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* Error thrown when the project directory already exists
|
|
6
6
|
*/
|
|
@@ -35,10 +35,12 @@ export async function scaffold(parentDir, options) {
|
|
|
35
35
|
const entitiesDir = path.join(apiDir, 'entities');
|
|
36
36
|
const pagesDir = path.join(srcDir, 'pages');
|
|
37
37
|
const stylesDir = path.join(srcDir, 'styles');
|
|
38
|
+
const claudeRulesDir = path.join(projectDir, '.claude', 'rules');
|
|
38
39
|
await Promise.all([
|
|
39
40
|
fs.mkdir(entitiesDir, { recursive: true }),
|
|
40
41
|
fs.mkdir(pagesDir, { recursive: true }),
|
|
41
42
|
fs.mkdir(stylesDir, { recursive: true }),
|
|
43
|
+
fs.mkdir(claudeRulesDir, { recursive: true }),
|
|
42
44
|
]);
|
|
43
45
|
// Write all files in parallel
|
|
44
46
|
await Promise.all([
|
|
@@ -63,6 +65,10 @@ export async function scaffold(parentDir, options) {
|
|
|
63
65
|
writeFile(srcDir, 'entry-client.ts', entryClientTemplate()),
|
|
64
66
|
writeFile(pagesDir, 'home.tsx', homePageTemplate()),
|
|
65
67
|
writeFile(stylesDir, 'theme.ts', themeTemplate()),
|
|
68
|
+
// LLM rules
|
|
69
|
+
writeFile(projectDir, 'CLAUDE.md', claudeMdTemplate(projectName)),
|
|
70
|
+
writeFile(claudeRulesDir, 'api-development.md', apiDevelopmentRuleTemplate()),
|
|
71
|
+
writeFile(claudeRulesDir, 'ui-development.md', uiDevelopmentRuleTemplate()),
|
|
66
72
|
]);
|
|
67
73
|
}
|
|
68
74
|
/**
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLAUDE.md — project-level instructions for LLMs
|
|
3
|
+
*/
|
|
4
|
+
export declare function claudeMdTemplate(projectName: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* .claude/rules/api-development.md — API conventions for LLMs
|
|
7
|
+
*/
|
|
8
|
+
export declare function apiDevelopmentRuleTemplate(): string;
|
|
9
|
+
/**
|
|
10
|
+
* .claude/rules/ui-development.md — UI conventions for LLMs
|
|
11
|
+
*/
|
|
12
|
+
export declare function uiDevelopmentRuleTemplate(): string;
|
|
1
13
|
/**
|
|
2
14
|
* Package.json template — full-stack deps + #generated imports map
|
|
3
15
|
*/
|
|
@@ -63,7 +75,7 @@ export declare function appComponentTemplate(): string;
|
|
|
63
75
|
*/
|
|
64
76
|
export declare function entryClientTemplate(): string;
|
|
65
77
|
/**
|
|
66
|
-
* src/styles/theme.ts —
|
|
78
|
+
* src/styles/theme.ts — configureThemeBase from @vertz/theme-shadcn/base
|
|
67
79
|
*/
|
|
68
80
|
export declare function themeTemplate(): string;
|
|
69
81
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA6B/D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAoBzC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAa5C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAI3C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAIvC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAc9C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CA6B1C;AAID;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAW1C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAoBvC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAavC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CASnC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAe5C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAOvC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAgD7C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAW5C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAWtC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAkIzC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA6B5D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAyJnD;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CA0NlD;AAID;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA6B/D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAoBzC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAa5C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAI3C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAIvC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAc9C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CA6B1C;AAID;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAW1C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAoBvC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAavC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CASnC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAe5C;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAOvC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAgD7C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAW5C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAWtC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAkIzC"}
|
package/dist/templates/index.js
CHANGED
|
@@ -1,3 +1,416 @@
|
|
|
1
|
+
// ── LLM rules templates ───────────────────────────────────
|
|
2
|
+
/**
|
|
3
|
+
* CLAUDE.md — project-level instructions for LLMs
|
|
4
|
+
*/
|
|
5
|
+
export function claudeMdTemplate(projectName) {
|
|
6
|
+
return `# ${projectName}
|
|
7
|
+
|
|
8
|
+
A full-stack TypeScript application built with [Vertz](https://vertz.dev).
|
|
9
|
+
|
|
10
|
+
## Stack
|
|
11
|
+
|
|
12
|
+
- Runtime: Bun
|
|
13
|
+
- Framework: Vertz (full-stack TypeScript)
|
|
14
|
+
- Language: TypeScript (strict mode)
|
|
15
|
+
- Docs: https://docs.vertz.dev
|
|
16
|
+
|
|
17
|
+
## Development
|
|
18
|
+
|
|
19
|
+
\`\`\`bash
|
|
20
|
+
bun install # Install dependencies
|
|
21
|
+
bun run dev # Start dev server with HMR
|
|
22
|
+
bun run build # Production build
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
The dev server automatically runs codegen and migrations when files change.
|
|
26
|
+
|
|
27
|
+
## Conventions
|
|
28
|
+
|
|
29
|
+
- See \`.claude/rules/\` for API and UI development conventions
|
|
30
|
+
- Refer to https://docs.vertz.dev for full framework documentation
|
|
31
|
+
- Entity files use the \`.entity.ts\` suffix
|
|
32
|
+
- The Vertz compiler handles all reactivity — never use \`.value\`, \`signal()\`, or \`computed()\` manually
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* .claude/rules/api-development.md — API conventions for LLMs
|
|
37
|
+
*/
|
|
38
|
+
export function apiDevelopmentRuleTemplate() {
|
|
39
|
+
return `# API Development
|
|
40
|
+
|
|
41
|
+
## Imports
|
|
42
|
+
|
|
43
|
+
All Vertz packages are available through the \`vertz\` meta-package:
|
|
44
|
+
|
|
45
|
+
\`\`\`ts
|
|
46
|
+
import { createServer, entity, createEnv } from 'vertz/server';
|
|
47
|
+
import { s } from 'vertz/schema';
|
|
48
|
+
import { d } from 'vertz/db';
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
## Defining Tables and Models
|
|
52
|
+
|
|
53
|
+
Use \`d.table()\` to define database tables and \`d.model()\` to create a typed model:
|
|
54
|
+
|
|
55
|
+
\`\`\`ts
|
|
56
|
+
import { d } from 'vertz/db';
|
|
57
|
+
|
|
58
|
+
export const postsTable = d.table('posts', {
|
|
59
|
+
id: d.uuid().primary(),
|
|
60
|
+
title: d.text(),
|
|
61
|
+
body: d.text(),
|
|
62
|
+
published: d.boolean().default(false),
|
|
63
|
+
authorId: d.uuid(),
|
|
64
|
+
createdAt: d.timestamp().default('now').readOnly(),
|
|
65
|
+
updatedAt: d.timestamp().autoUpdate().readOnly(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const postsModel = d.model(postsTable);
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
### Field Types
|
|
72
|
+
|
|
73
|
+
- \`d.uuid()\` — UUID field
|
|
74
|
+
- \`d.text()\` — Text/string field
|
|
75
|
+
- \`d.boolean()\` — Boolean field
|
|
76
|
+
- \`d.integer()\` — Integer field
|
|
77
|
+
- \`d.timestamp()\` — Timestamp field
|
|
78
|
+
|
|
79
|
+
### Field Modifiers
|
|
80
|
+
|
|
81
|
+
- \`.primary()\` — Primary key
|
|
82
|
+
- \`.default(value)\` — Default value (\`'now'\` for timestamps)
|
|
83
|
+
- \`.readOnly()\` — Not writable via API
|
|
84
|
+
- \`.autoUpdate()\` — Auto-set on update (timestamps)
|
|
85
|
+
- \`.unique()\` — Unique constraint
|
|
86
|
+
|
|
87
|
+
## Defining Entities
|
|
88
|
+
|
|
89
|
+
Entities define your API resources. Each entity gets automatic CRUD endpoints.
|
|
90
|
+
Entity files go in \`src/api/entities/\` with the \`.entity.ts\` suffix.
|
|
91
|
+
|
|
92
|
+
\`\`\`ts
|
|
93
|
+
import { entity } from 'vertz/server';
|
|
94
|
+
import { postsModel } from '../schema';
|
|
95
|
+
|
|
96
|
+
export const posts = entity('posts', {
|
|
97
|
+
model: postsModel,
|
|
98
|
+
access: {
|
|
99
|
+
list: () => true,
|
|
100
|
+
get: () => true,
|
|
101
|
+
create: () => true,
|
|
102
|
+
update: () => true,
|
|
103
|
+
delete: () => true,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
### Entity Operations
|
|
109
|
+
|
|
110
|
+
Each entity automatically provides these operations:
|
|
111
|
+
|
|
112
|
+
| Operation | HTTP | Description |
|
|
113
|
+
| --------- | ----------------------- | ------------------------------ |
|
|
114
|
+
| \`list\` | GET /api/<entity> | List with filtering/pagination |
|
|
115
|
+
| \`get\` | GET /api/<entity>/:id | Get by ID |
|
|
116
|
+
| \`create\` | POST /api/<entity> | Create new record |
|
|
117
|
+
| \`update\` | PATCH /api/<entity>/:id | Update existing record |
|
|
118
|
+
| \`delete\` | DELETE /api/<entity>/:id| Delete record |
|
|
119
|
+
|
|
120
|
+
## Server Configuration
|
|
121
|
+
|
|
122
|
+
Register all entities with \`createServer\`:
|
|
123
|
+
|
|
124
|
+
\`\`\`ts
|
|
125
|
+
import { createServer } from 'vertz/server';
|
|
126
|
+
import { db } from './db';
|
|
127
|
+
import { env } from './env';
|
|
128
|
+
import { posts } from './entities/posts.entity';
|
|
129
|
+
|
|
130
|
+
const app = createServer({
|
|
131
|
+
basePath: '/api',
|
|
132
|
+
entities: [posts],
|
|
133
|
+
db,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export default app;
|
|
137
|
+
|
|
138
|
+
if (import.meta.main) {
|
|
139
|
+
app.listen(env.PORT).then((handle) => {
|
|
140
|
+
console.log(\\\`Server running at http://localhost:\\\${handle.port}/api\\\`);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
\`\`\`
|
|
144
|
+
|
|
145
|
+
## Environment Variables
|
|
146
|
+
|
|
147
|
+
Use \`createEnv\` for validated, typed environment variables:
|
|
148
|
+
|
|
149
|
+
\`\`\`ts
|
|
150
|
+
import { createEnv } from 'vertz/server';
|
|
151
|
+
import { s } from 'vertz/schema';
|
|
152
|
+
|
|
153
|
+
export const env = createEnv({
|
|
154
|
+
schema: s.object({
|
|
155
|
+
PORT: s.coerce.number().default(3000),
|
|
156
|
+
DATABASE_URL: s.string().default('local.db'),
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
## Schemas for Custom Actions
|
|
162
|
+
|
|
163
|
+
Standard CRUD operations (list, get, create, update, delete) derive their schemas automatically
|
|
164
|
+
from the model — you don't need to define schemas for them.
|
|
165
|
+
|
|
166
|
+
Use \`s.*\` builders only when defining **custom actions** on entities:
|
|
167
|
+
|
|
168
|
+
\`\`\`ts
|
|
169
|
+
import { s } from 'vertz/schema';
|
|
170
|
+
|
|
171
|
+
const CompleteTaskInput = s.object({
|
|
172
|
+
note: s.string().optional(),
|
|
173
|
+
completedAt: s.date(),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
type CompleteTaskInput = s.infer<typeof CompleteTaskInput>;
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
179
|
+
### Common Schema Types
|
|
180
|
+
|
|
181
|
+
- \`s.string()\` — String with \`.min()\`, \`.max()\`, \`.email()\`, \`.uuid()\`, \`.url()\`
|
|
182
|
+
- \`s.number()\` — Number with \`.int()\`, \`.min()\`, \`.max()\`, \`.positive()\`
|
|
183
|
+
- \`s.boolean()\` — Boolean
|
|
184
|
+
- \`s.date()\` — Date
|
|
185
|
+
- \`s.enum([...])\` — Enum from literal values
|
|
186
|
+
- \`s.array(schema)\` — Array of schema type
|
|
187
|
+
- \`s.object({...})\` — Object with typed fields
|
|
188
|
+
- \`.optional()\` — Makes any field optional
|
|
189
|
+
- \`.default(value)\` — Provides a default value
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* .claude/rules/ui-development.md — UI conventions for LLMs
|
|
194
|
+
*/
|
|
195
|
+
export function uiDevelopmentRuleTemplate() {
|
|
196
|
+
return `# UI Development
|
|
197
|
+
|
|
198
|
+
Vertz uses a custom JSX runtime with a compiler that transforms reactive code.
|
|
199
|
+
You write plain-looking code and the compiler makes it reactive automatically.
|
|
200
|
+
|
|
201
|
+
## Imports
|
|
202
|
+
|
|
203
|
+
\`\`\`ts
|
|
204
|
+
import { css, query, queryMatch, globalCss, ThemeProvider, variants } from 'vertz/ui';
|
|
205
|
+
import { api } from '../client';
|
|
206
|
+
\`\`\`
|
|
207
|
+
|
|
208
|
+
## Components
|
|
209
|
+
|
|
210
|
+
### Destructure props in parameters
|
|
211
|
+
|
|
212
|
+
Don't annotate return types — the JSX factory handles typing:
|
|
213
|
+
|
|
214
|
+
\`\`\`tsx
|
|
215
|
+
// RIGHT
|
|
216
|
+
export function TaskCard({ task, onClick }: TaskCardProps) {
|
|
217
|
+
return <div onClick={onClick}>{task.title}</div>;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// WRONG — don't annotate return type
|
|
221
|
+
export function TaskCard({ task }: TaskCardProps): HTMLElement { ... }
|
|
222
|
+
\`\`\`
|
|
223
|
+
|
|
224
|
+
### Props Naming
|
|
225
|
+
|
|
226
|
+
- Interface: \`ComponentNameProps\`
|
|
227
|
+
- Callbacks: \`on\` prefix (\`onClick\`, \`onSubmit\`, \`onSuccess\`)
|
|
228
|
+
|
|
229
|
+
## Reactivity
|
|
230
|
+
|
|
231
|
+
The Vertz compiler transforms your code to be reactive. You don't call signal/computed APIs manually.
|
|
232
|
+
|
|
233
|
+
### \`let\` for local state (compiled to signals)
|
|
234
|
+
|
|
235
|
+
\`\`\`tsx
|
|
236
|
+
export function Counter() {
|
|
237
|
+
let count = 0;
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<button onClick={() => { count++; }}>
|
|
241
|
+
Count: {count}
|
|
242
|
+
</button>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
\`\`\`
|
|
246
|
+
|
|
247
|
+
### \`const\` for derived values (compiled to computed)
|
|
248
|
+
|
|
249
|
+
\`\`\`tsx
|
|
250
|
+
export function TaskList() {
|
|
251
|
+
let filter = 'all';
|
|
252
|
+
const tasks = query(api.tasks.list());
|
|
253
|
+
const filtered = filter === 'all'
|
|
254
|
+
? tasks.data.items
|
|
255
|
+
: tasks.data.items.filter((t) => t.status === filter);
|
|
256
|
+
|
|
257
|
+
return <div>{filtered.map((t) => <TaskItem task={t} />)}</div>;
|
|
258
|
+
}
|
|
259
|
+
\`\`\`
|
|
260
|
+
|
|
261
|
+
## JSX
|
|
262
|
+
|
|
263
|
+
### Fully declarative — no imperative DOM manipulation
|
|
264
|
+
|
|
265
|
+
Never use \`appendChild\`, \`innerHTML\`, \`textContent\`, \`document.createElement\`.
|
|
266
|
+
|
|
267
|
+
\`\`\`tsx
|
|
268
|
+
// RIGHT
|
|
269
|
+
return <div class={styles.panel}>{title}</div>;
|
|
270
|
+
|
|
271
|
+
// WRONG — no imperative DOM
|
|
272
|
+
const el = document.createElement('div');
|
|
273
|
+
el.textContent = title;
|
|
274
|
+
\`\`\`
|
|
275
|
+
|
|
276
|
+
### Use JSX for custom components, not function calls
|
|
277
|
+
|
|
278
|
+
\`\`\`tsx
|
|
279
|
+
// RIGHT
|
|
280
|
+
<TaskCard task={task} onClick={handleClick} />
|
|
281
|
+
|
|
282
|
+
// WRONG
|
|
283
|
+
TaskCard({ task, onClick: handleClick });
|
|
284
|
+
\`\`\`
|
|
285
|
+
|
|
286
|
+
### Conditionals and Lists
|
|
287
|
+
|
|
288
|
+
\`\`\`tsx
|
|
289
|
+
{isLoading && <div>Loading...</div>}
|
|
290
|
+
|
|
291
|
+
{error ? <div class={styles.error}>{error.message}</div> : <div>{content}</div>}
|
|
292
|
+
|
|
293
|
+
{tasks.map((task) => (
|
|
294
|
+
<TaskItem key={task.id} task={task} />
|
|
295
|
+
))}
|
|
296
|
+
\`\`\`
|
|
297
|
+
|
|
298
|
+
## Data Fetching
|
|
299
|
+
|
|
300
|
+
### \`query()\` — Reactive data fetching
|
|
301
|
+
|
|
302
|
+
\`\`\`tsx
|
|
303
|
+
const tasks = query(api.tasks.list());
|
|
304
|
+
\`\`\`
|
|
305
|
+
|
|
306
|
+
The query result has reactive properties (\`.data\`, \`.error\`, \`.loading\`) that the compiler
|
|
307
|
+
auto-unwraps everywhere — just access them directly, the compiler handles the rest.
|
|
308
|
+
|
|
309
|
+
### \`queryMatch()\` — Pattern matching for query states
|
|
310
|
+
|
|
311
|
+
\`\`\`tsx
|
|
312
|
+
{queryMatch(tasksQuery, {
|
|
313
|
+
loading: () => <div>Loading...</div>,
|
|
314
|
+
error: (err) => <div>Error: {err.message}</div>,
|
|
315
|
+
data: (response) => (
|
|
316
|
+
<div>
|
|
317
|
+
{response.items.map((item) => (
|
|
318
|
+
<div key={item.id}>{item.title}</div>
|
|
319
|
+
))}
|
|
320
|
+
</div>
|
|
321
|
+
),
|
|
322
|
+
})}
|
|
323
|
+
\`\`\`
|
|
324
|
+
|
|
325
|
+
### Automatic Cache Invalidation
|
|
326
|
+
|
|
327
|
+
After mutations (\`create\`, \`update\`, \`delete\`), related queries are automatically
|
|
328
|
+
refetched in the background. No manual \`refetch()\` calls needed — the framework
|
|
329
|
+
handles cache invalidation via optimistic updates.
|
|
330
|
+
|
|
331
|
+
## Styling
|
|
332
|
+
|
|
333
|
+
### \`css()\` for scoped styles
|
|
334
|
+
|
|
335
|
+
\`\`\`tsx
|
|
336
|
+
const styles = css({
|
|
337
|
+
container: ['flex', 'flex-col', 'gap:4', 'p:6'],
|
|
338
|
+
title: ['font:xl', 'font:bold', 'text:foreground'],
|
|
339
|
+
card: ['rounded:md', 'border:1', 'border:border', 'bg:card', 'p:4'],
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return <div class={styles.container}>...</div>;
|
|
343
|
+
\`\`\`
|
|
344
|
+
|
|
345
|
+
### \`variants()\` for parameterized styles
|
|
346
|
+
|
|
347
|
+
\`\`\`tsx
|
|
348
|
+
const button = variants({
|
|
349
|
+
base: ['inline-flex', 'items:center', 'rounded:md', 'font:medium'],
|
|
350
|
+
variants: {
|
|
351
|
+
intent: {
|
|
352
|
+
primary: ['bg:primary.600', 'text:white'],
|
|
353
|
+
secondary: ['bg:secondary', 'text:secondary-foreground'],
|
|
354
|
+
danger: ['bg:destructive', 'text:white'],
|
|
355
|
+
},
|
|
356
|
+
size: {
|
|
357
|
+
sm: ['text:xs', 'px:3', 'py:1'],
|
|
358
|
+
md: ['text:sm', 'px:4', 'py:2'],
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
defaultVariants: { intent: 'primary', size: 'md' },
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
<button class={button({ intent: 'danger', size: 'sm' })}>Delete</button>
|
|
365
|
+
\`\`\`
|
|
366
|
+
|
|
367
|
+
### Style Tokens
|
|
368
|
+
|
|
369
|
+
Styles use a token system (similar to Tailwind but with Vertz syntax):
|
|
370
|
+
|
|
371
|
+
- **Layout:** \`flex\`, \`grid\`, \`block\`, \`inline-flex\`
|
|
372
|
+
- **Spacing:** \`p:4\`, \`px:6\`, \`py:2\`, \`m:4\`, \`mx:auto\`, \`gap:2\`
|
|
373
|
+
- **Typography:** \`font:lg\`, \`font:bold\`, \`font:medium\`, \`text:foreground\`, \`text:sm\`
|
|
374
|
+
- **Colors:** \`bg:background\`, \`bg:primary.600\`, \`text:white\`, \`border:border\`
|
|
375
|
+
- **Sizing:** \`w:full\`, \`h:screen\`, \`max-w:2xl\`, \`min-h:screen\`
|
|
376
|
+
- **Borders:** \`rounded:md\`, \`rounded:lg\`, \`border:1\`, \`border:border\`
|
|
377
|
+
- **Flexbox:** \`items:center\`, \`justify:between\`, \`flex-1\`, \`flex-col\`
|
|
378
|
+
|
|
379
|
+
## Context
|
|
380
|
+
|
|
381
|
+
\`\`\`tsx
|
|
382
|
+
import { createContext, useContext } from 'vertz/ui';
|
|
383
|
+
|
|
384
|
+
export const SettingsContext = createContext<SettingsValue>();
|
|
385
|
+
|
|
386
|
+
export function useSettings() {
|
|
387
|
+
const ctx = useContext(SettingsContext);
|
|
388
|
+
if (!ctx) throw new Error('useSettings must be called within SettingsContext.Provider');
|
|
389
|
+
return ctx;
|
|
390
|
+
}
|
|
391
|
+
\`\`\`
|
|
392
|
+
|
|
393
|
+
## Router
|
|
394
|
+
|
|
395
|
+
### Navigation
|
|
396
|
+
|
|
397
|
+
Pages access the router via hooks — no prop threading:
|
|
398
|
+
|
|
399
|
+
\`\`\`tsx
|
|
400
|
+
import { useRouter, useParams } from 'vertz/ui';
|
|
401
|
+
|
|
402
|
+
export function TaskListPage() {
|
|
403
|
+
const { navigate } = useRouter();
|
|
404
|
+
return <button onClick={() => navigate({ to: '/tasks/new' })}>New Task</button>;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function TaskDetailPage() {
|
|
408
|
+
const { id } = useParams<'/tasks/:id'>();
|
|
409
|
+
// id is typed as string
|
|
410
|
+
}
|
|
411
|
+
\`\`\`
|
|
412
|
+
`;
|
|
413
|
+
}
|
|
1
414
|
// ── Config file templates ──────────────────────────────────
|
|
2
415
|
/**
|
|
3
416
|
* Package.json template — full-stack deps + #generated imports map
|
|
@@ -314,12 +727,12 @@ mount(App, {
|
|
|
314
727
|
`;
|
|
315
728
|
}
|
|
316
729
|
/**
|
|
317
|
-
* src/styles/theme.ts —
|
|
730
|
+
* src/styles/theme.ts — configureThemeBase from @vertz/theme-shadcn/base
|
|
318
731
|
*/
|
|
319
732
|
export function themeTemplate() {
|
|
320
|
-
return `import {
|
|
733
|
+
return `import { configureThemeBase } from '@vertz/theme-shadcn/base';
|
|
321
734
|
|
|
322
|
-
const { theme, globals } =
|
|
735
|
+
const { theme, globals } = configureThemeBase({
|
|
323
736
|
palette: 'zinc',
|
|
324
737
|
radius: 'md',
|
|
325
738
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/create-vertz-app",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Create a new Vertz application",
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"bin"
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "tsc"
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"test": "bun test"
|
|
33
35
|
},
|
|
34
36
|
"dependencies": {
|
|
35
37
|
"commander": "^14.0.0"
|