create-patties 0.0.11 → 0.0.12
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/CHANGELOG.md +30 -0
- package/package.json +4 -2
- package/src/index.ts +345 -167
- package/src/prompts.ts +62 -17
- package/src/readme.ts +12 -5
- package/src/ui.ts +93 -0
- package/templates/_backend/app/routes/api/todos.ts +30 -0
- package/templates/_claude/.claude/commands/patties-init.md +33 -0
- package/templates/_claude/.claude/skills/patties/SKILL.md +104 -0
- package/templates/_codex/.codex/rules/patties-patterns.md +105 -0
- package/templates/_codex/AGENTS.md +1 -0
- package/templates/_container/.dockerignore +7 -0
- package/templates/_container/Dockerfile +27 -0
- package/templates/_monorepo/packages/README.md +18 -0
- package/templates/_shared/patties-patterns.md +105 -0
- package/templates/default/README-template.md +85 -71
- package/templates/default/app/routes/api/health.ts +8 -0
- package/templates/ui-starter/_internal/cn.ts +6 -0
- package/templates/ui-starter/_internal/slot.ts +50 -0
- package/templates/ui-starter/_internal/variants.ts +1 -0
- package/templates/ui-starter/button.tsx +60 -0
- package/templates/ui-starter/card.tsx +92 -0
- package/templates/ui-starter/demo/TodoApp.tsx +86 -0
- package/templates/ui-starter/demo/index.tsx +41 -0
- package/templates/ui-starter/input.tsx +20 -0
- package/templates/ui-starter/label.tsx +18 -0
- package/templates/ui-starter/themes/neutral/tokens.css +46 -0
- package/templates/ui-starter/themes/slate/tokens.css +46 -0
- package/templates/ui-starter/themes/stone/tokens.css +46 -0
- package/templates/ui-starter/themes/zinc/tokens.css +46 -0
- package/templates/ui-starter/tokens.css +46 -0
package/src/prompts.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
// Interactive prompts for create-patties. See
|
|
2
|
-
//
|
|
1
|
+
// Interactive prompts for create-patties. See cli specs 09 + 18.
|
|
2
|
+
// Uses Bun's built-in prompt() — no new dependency.
|
|
3
3
|
//
|
|
4
4
|
// Non-TTY callers must supply the equivalent flags; the run loop only invokes
|
|
5
|
-
// these helpers when stdin is a TTY and --yes was not passed.
|
|
5
|
+
// these helpers when stdin is a TTY and --yes was not passed. The prompt order
|
|
6
|
+
// (spec 18) is gated by project type: UI is asked only for frontend/fullstack,
|
|
7
|
+
// monorepo only for fullstack, and the deploy target option set depends on type.
|
|
6
8
|
|
|
7
9
|
export type AgentTemplate = "claude" | "codex" | "none";
|
|
8
|
-
export type
|
|
9
|
-
export type
|
|
10
|
+
export type ProjectType = "frontend" | "backend" | "fullstack";
|
|
11
|
+
export type Target = "bun" | "edge" | "container";
|
|
12
|
+
export type Theme = "neutral" | "slate" | "stone" | "zinc";
|
|
10
13
|
export type Deploy =
|
|
11
14
|
| "cloudflare"
|
|
12
15
|
| "vercel"
|
|
@@ -48,41 +51,83 @@ export function promptName(
|
|
|
48
51
|
return undefined;
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
// "old school" is the TTY label for the no-agent choice; the flag value stays
|
|
55
|
+
// `none` (spec 18). `--template` remains an alias for `--agent`.
|
|
51
56
|
export function promptAgent(io: PromptIO = {}): AgentTemplate {
|
|
52
57
|
const ask = io.prompt ?? prompt;
|
|
53
58
|
const answer = (
|
|
54
|
-
ask(
|
|
55
|
-
|
|
59
|
+
ask(
|
|
60
|
+
"Which coding agent are you using? [Claude/Codex/old school] (Claude) ",
|
|
61
|
+
) ?? ""
|
|
56
62
|
)
|
|
57
63
|
.trim()
|
|
58
64
|
.toLowerCase();
|
|
59
65
|
if (answer === "codex") return "codex";
|
|
60
|
-
if (answer === "none"
|
|
66
|
+
if (answer === "none" || answer === "old school" || answer === "old-school") {
|
|
67
|
+
return "none";
|
|
68
|
+
}
|
|
61
69
|
return "claude";
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
export function
|
|
72
|
+
export function promptType(io: PromptIO = {}): ProjectType {
|
|
65
73
|
const ask = io.prompt ?? prompt;
|
|
66
|
-
const answer = (
|
|
74
|
+
const answer = (
|
|
75
|
+
ask(
|
|
76
|
+
"What kind of project are you building? [frontend/backend/fullstack] (fullstack) ",
|
|
77
|
+
) ?? ""
|
|
78
|
+
)
|
|
67
79
|
.trim()
|
|
68
80
|
.toLowerCase();
|
|
69
|
-
if (answer === "
|
|
70
|
-
return "
|
|
81
|
+
if (answer === "frontend") return "frontend";
|
|
82
|
+
if (answer === "backend") return "backend";
|
|
83
|
+
return "fullstack";
|
|
71
84
|
}
|
|
72
85
|
|
|
73
|
-
|
|
86
|
+
// Asked only for frontend/fullstack — backend has no UI surface.
|
|
87
|
+
export function promptUi(io: PromptIO = {}): boolean {
|
|
74
88
|
const ask = io.prompt ?? prompt;
|
|
75
|
-
const answer = (
|
|
89
|
+
const answer = (
|
|
90
|
+
ask("Do you want a styled UI template (Patties UI)? [Y/n] (Y) ") ?? ""
|
|
91
|
+
)
|
|
76
92
|
.trim()
|
|
77
93
|
.toLowerCase();
|
|
78
|
-
return answer === "
|
|
94
|
+
return !(answer === "n" || answer === "no");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Asked only for fullstack.
|
|
98
|
+
export function promptMonorepo(io: PromptIO = {}): boolean {
|
|
99
|
+
const ask = io.prompt ?? prompt;
|
|
100
|
+
const answer = (ask("Do you want a monorepo structure? [y/N] (N) ") ?? "")
|
|
101
|
+
.trim()
|
|
102
|
+
.toLowerCase();
|
|
103
|
+
return answer === "y" || answer === "yes";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// The option set depends on project type: container is fullstack-only.
|
|
107
|
+
export function promptTarget(type: ProjectType, io: PromptIO = {}): Target {
|
|
108
|
+
const ask = io.prompt ?? prompt;
|
|
109
|
+
const options = type === "fullstack" ? "bun/edge/container" : "bun/edge";
|
|
110
|
+
const answer = (ask(`Where will you deploy? [${options}] (bun) `) ?? "")
|
|
111
|
+
.trim()
|
|
112
|
+
.toLowerCase();
|
|
113
|
+
if (answer === "edge" || answer === "worker" || answer === "worker/edge") {
|
|
114
|
+
return "edge";
|
|
115
|
+
}
|
|
116
|
+
if (
|
|
117
|
+
type === "fullstack" &&
|
|
118
|
+
(answer === "container" ||
|
|
119
|
+
answer === "docker" ||
|
|
120
|
+
answer === "container/docker")
|
|
121
|
+
) {
|
|
122
|
+
return "container";
|
|
123
|
+
}
|
|
124
|
+
return "bun";
|
|
79
125
|
}
|
|
80
126
|
|
|
81
127
|
export function promptDeploy(io: PromptIO = {}): Deploy {
|
|
82
128
|
const ask = io.prompt ?? prompt;
|
|
83
129
|
const answer = (
|
|
84
|
-
ask("Deploy plugin? [cloudflare/vercel/deno/netlify/
|
|
85
|
-
""
|
|
130
|
+
ask("Deploy plugin? [cloudflare/vercel/deno/netlify/none] (none) ") ?? ""
|
|
86
131
|
)
|
|
87
132
|
.trim()
|
|
88
133
|
.toLowerCase();
|
package/src/readme.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
// 1. Strip/keep conditional blocks delimited by:
|
|
5
5
|
// <!-- if:KEY=VALUE -->...<!-- /if -->
|
|
6
6
|
// The block is kept iff `vars[KEY] === VALUE`.
|
|
7
|
-
// 2. Replace `{{name}}`, `{{agent}}`, `{{
|
|
8
|
-
//
|
|
7
|
+
// 2. Replace `{{name}}`, `{{agent}}`, `{{type}}`, `{{ui}}`, `{{monorepo}}`,
|
|
8
|
+
// `{{app_name}}`, `{{target}}`, `{{deploy}}` placeholders with the
|
|
9
|
+
// corresponding `vars` entry.
|
|
9
10
|
//
|
|
10
11
|
// `renderTemplatesInTree` walks a scaffolded project and applies the template
|
|
11
12
|
// to every text file we care about (README.md, *.md, *.json, *.ts, *.tsx).
|
|
@@ -14,9 +15,12 @@
|
|
|
14
15
|
export interface TemplateVars {
|
|
15
16
|
name: string;
|
|
16
17
|
agent: "claude" | "codex" | "none";
|
|
17
|
-
|
|
18
|
+
type: "frontend" | "backend" | "fullstack";
|
|
19
|
+
ui: "yes" | "no";
|
|
20
|
+
monorepo: "yes" | "no";
|
|
21
|
+
target: "bun" | "edge" | "container";
|
|
18
22
|
deploy: "cloudflare" | "vercel" | "deno" | "netlify" | "bun" | "none";
|
|
19
|
-
|
|
23
|
+
app_name: string;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
const CONDITIONAL_RE =
|
|
@@ -37,9 +41,12 @@ export function applyTemplate(source: string, vars: TemplateVars): string {
|
|
|
37
41
|
stripped
|
|
38
42
|
.replaceAll("{{name}}", vars.name)
|
|
39
43
|
.replaceAll("{{agent}}", vars.agent)
|
|
44
|
+
.replaceAll("{{type}}", vars.type)
|
|
45
|
+
.replaceAll("{{ui}}", vars.ui)
|
|
46
|
+
.replaceAll("{{monorepo}}", vars.monorepo)
|
|
47
|
+
.replaceAll("{{app_name}}", vars.app_name)
|
|
40
48
|
.replaceAll("{{target}}", vars.target)
|
|
41
49
|
.replaceAll("{{deploy}}", vars.deploy)
|
|
42
|
-
.replaceAll("{{scaffold}}", vars.scaffold)
|
|
43
50
|
// Legacy placeholders from earlier overlay revisions — kept so existing
|
|
44
51
|
// CLAUDE.md / AGENTS.md template text keeps interpolating.
|
|
45
52
|
.replaceAll("{{PROJECT_NAME}}", vars.name)
|
package/src/ui.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Patties UI starter integration (spec 18 §Patties UI).
|
|
2
|
+
//
|
|
3
|
+
// create-patties stays zero-dependency and offline, so instead of shelling out
|
|
4
|
+
// to `patties ui init` + `patties add` (which need the project's installed
|
|
5
|
+
// binary) we vendor the starter set under templates/ui-starter/ and copy it
|
|
6
|
+
// directly. The vendored copies are byte-for-byte identical to
|
|
7
|
+
// packages/patties-ui/templates/ — a drift test enforces that — so this is
|
|
8
|
+
// equivalent to what `patties add button card input label` would stamp.
|
|
9
|
+
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import type { Theme } from "./prompts.ts";
|
|
13
|
+
|
|
14
|
+
// Runtime peer deps the starter components + _internal helpers import.
|
|
15
|
+
// Derived from packages/patties-ui/src/registry.ts entries for the starter set:
|
|
16
|
+
// button → cn + cva, card → cn, input → cn, label → cn + @radix-ui/react-label
|
|
17
|
+
export const UI_DEPS: Record<string, string> = {
|
|
18
|
+
"@radix-ui/react-label": "^2.1.0",
|
|
19
|
+
"class-variance-authority": "^0.7.0",
|
|
20
|
+
clsx: "^2.1.0",
|
|
21
|
+
"tailwind-merge": "^2.5.0",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Dev-only: the catalog the user runs `patties add` / `patties ui` against.
|
|
25
|
+
export const UI_DEV_DEPS: Record<string, string> = {
|
|
26
|
+
"patties-ui": "latest",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const STARTER_COMPONENTS = ["button", "card", "input", "label"] as const;
|
|
30
|
+
|
|
31
|
+
// Stamp the vendored starter set into an app directory and rewrite the demo to
|
|
32
|
+
// import the stamped components. `uiStarterDir` is templates/ui-starter.
|
|
33
|
+
export async function applyUiStarter(
|
|
34
|
+
appRoot: string,
|
|
35
|
+
theme: Theme,
|
|
36
|
+
uiStarterDir: string,
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
const uiDir = `${appRoot}/app/components/ui`;
|
|
39
|
+
const internalDir = `${uiDir}/_internal`;
|
|
40
|
+
const stylesDir = `${appRoot}/app/styles`;
|
|
41
|
+
await Bun.$`mkdir -p ${internalDir} ${stylesDir}`.quiet();
|
|
42
|
+
|
|
43
|
+
for (const name of STARTER_COMPONENTS) {
|
|
44
|
+
await Bun.$`cp ${uiStarterDir}/${name}.tsx ${uiDir}/${name}.tsx`.quiet();
|
|
45
|
+
}
|
|
46
|
+
await Bun.$`cp -R ${uiStarterDir}/_internal/. ${internalDir}`.quiet();
|
|
47
|
+
|
|
48
|
+
const themeTokens = resolve(uiStarterDir, "themes", theme, "tokens.css");
|
|
49
|
+
const tokens = existsSync(themeTokens)
|
|
50
|
+
? themeTokens
|
|
51
|
+
: resolve(uiStarterDir, "tokens.css");
|
|
52
|
+
await Bun.$`cp ${tokens} ${stylesDir}/tokens.css`.quiet();
|
|
53
|
+
// app.css is written (not vendored) because a committed `@theme` stylesheet
|
|
54
|
+
// would trip this repo's Biome CSS parser, which has Tailwind directives off.
|
|
55
|
+
await Bun.write(`${stylesDir}/app.css`, APP_CSS);
|
|
56
|
+
|
|
57
|
+
// Demo page + island that import the stamped components. Only applies to
|
|
58
|
+
// frontend / fullstack, which always have app/routes + app/islands.
|
|
59
|
+
await Bun.$`cp ${uiStarterDir}/demo/index.tsx ${appRoot}/app/routes/index.tsx`.quiet();
|
|
60
|
+
await Bun.$`cp ${uiStarterDir}/demo/TodoApp.tsx ${appRoot}/app/islands/TodoApp.tsx`.quiet();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Pre-wired Tailwind v4 + tokens stylesheet (the wiring `patties ui init`
|
|
64
|
+
// prints). Patties does not bundle Tailwind — compile this with the Tailwind
|
|
65
|
+
// CLI; see the generated README "Styling" section.
|
|
66
|
+
const APP_CSS = `@import "tailwindcss";
|
|
67
|
+
@import "./tokens.css";
|
|
68
|
+
|
|
69
|
+
@theme inline {
|
|
70
|
+
--color-background: var(--background);
|
|
71
|
+
--color-foreground: var(--foreground);
|
|
72
|
+
--color-card: var(--card);
|
|
73
|
+
--color-card-foreground: var(--card-foreground);
|
|
74
|
+
--color-popover: var(--popover);
|
|
75
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
76
|
+
--color-primary: var(--primary);
|
|
77
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
78
|
+
--color-secondary: var(--secondary);
|
|
79
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
80
|
+
--color-muted: var(--muted);
|
|
81
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
82
|
+
--color-accent: var(--accent);
|
|
83
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
84
|
+
--color-destructive: var(--destructive);
|
|
85
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
86
|
+
--color-border: var(--border);
|
|
87
|
+
--color-input: var(--input);
|
|
88
|
+
--color-ring: var(--ring);
|
|
89
|
+
--radius-lg: var(--radius);
|
|
90
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
91
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PattiesContext } from "patties/middleware";
|
|
2
|
+
|
|
3
|
+
// A small in-memory sample resource so a backend project boots with a working
|
|
4
|
+
// endpoint. Swap the array for a real data source when you're ready.
|
|
5
|
+
interface Todo {
|
|
6
|
+
id: number;
|
|
7
|
+
text: string;
|
|
8
|
+
done: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const todos: Todo[] = [
|
|
12
|
+
{ id: 1, text: "wire up a database", done: false },
|
|
13
|
+
{ id: 2, text: "add authentication", done: false },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export function GET(_req: Request, ctx: PattiesContext): Response {
|
|
17
|
+
return ctx.json({ todos });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function POST(
|
|
21
|
+
req: Request,
|
|
22
|
+
ctx: PattiesContext,
|
|
23
|
+
): Promise<Response> {
|
|
24
|
+
const body = (await req.json()) as { text?: unknown };
|
|
25
|
+
const text = typeof body.text === "string" ? body.text.trim() : "";
|
|
26
|
+
if (!text) return ctx.json({ error: "text is required" }, { status: 400 });
|
|
27
|
+
const todo: Todo = { id: todos.length + 1, text, done: false };
|
|
28
|
+
todos.push(todo);
|
|
29
|
+
return ctx.json({ todo }, { status: 201 });
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Guided, plan-mode first scaffold for a Patties project — pick feature patterns, see a plan, then scaffold after approval.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /patties-init
|
|
6
|
+
|
|
7
|
+
Guide the user through their first feature scaffold. This command is a thin
|
|
8
|
+
driver — all the recipe detail lives in the `patties` skill. Do **not**
|
|
9
|
+
duplicate the pattern catalog here; defer to `/patties`.
|
|
10
|
+
|
|
11
|
+
You are (or should be) in **plan mode**: explore and propose, but write nothing
|
|
12
|
+
until the user approves the plan.
|
|
13
|
+
|
|
14
|
+
Run these four steps:
|
|
15
|
+
|
|
16
|
+
1. **Discover** — read the project to fit the plan to what was scaffolded:
|
|
17
|
+
- project type (frontend / backend / fullstack) and deploy target, from
|
|
18
|
+
`patties.config.ts` and `package.json`,
|
|
19
|
+
- whether Patties UI is initialized (does `app/components/ui/_internal/`
|
|
20
|
+
exist?),
|
|
21
|
+
- flat vs. monorepo layout (is there an `apps/` workspace?).
|
|
22
|
+
2. **Ask** — which feature pattern(s) from the `patties` skill catalog the user
|
|
23
|
+
wants (auth-rbac, crm, task, pivot, dashboard), plus the domain specifics the
|
|
24
|
+
recipes adapt to: entity names, fields, and roles.
|
|
25
|
+
3. **Plan** — present the file list, the `patties add` components to stamp, and
|
|
26
|
+
the mock-data shape, honoring the skill's **UI + mock data** depth contract
|
|
27
|
+
(no `bun:sqlite`, migrations, or real backend). Write nothing yet.
|
|
28
|
+
4. **Scaffold** — once the user approves and exits plan mode, generate the files
|
|
29
|
+
per the `patties` skill recipes. If the catalog isn't initialized, run
|
|
30
|
+
`patties ui init` first.
|
|
31
|
+
|
|
32
|
+
For ongoing work after this first scaffold (adding one component or one more
|
|
33
|
+
pattern later), the user invokes the `/patties` skill directly.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: patties
|
|
3
|
+
description: Use when adding a Patties UI component or scaffolding a feature pattern (auth + RBAC, CRM, task board, pivot table, dashboard). Trigger phrases include "add a component", "scaffold a CRM / dashboard / auth", "patties pattern", "/patties".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /patties — components & feature patterns
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
Use this when the user wants to **add a Patties UI component** or **scaffold a
|
|
11
|
+
feature pattern** (auth + RBAC, a CRM, a task board, a pivot table, a
|
|
12
|
+
dashboard). It covers scaffolding only — for running, building, deploying, or
|
|
13
|
+
managing secrets, use the `patties-cli` skill instead.
|
|
14
|
+
|
|
15
|
+
Two capabilities:
|
|
16
|
+
|
|
17
|
+
1. **Add a UI component** — a thin wrapper over the deterministic catalog.
|
|
18
|
+
2. **Scaffold a feature pattern** — instruction-driven: you generate the files,
|
|
19
|
+
adapting names/fields/copy to the user's domain.
|
|
20
|
+
|
|
21
|
+
## Add a component
|
|
22
|
+
|
|
23
|
+
The component catalog is deterministic — never hand-author component source; the
|
|
24
|
+
registry is the source of truth.
|
|
25
|
+
|
|
26
|
+
1. If `app/components/ui/_internal/` does not exist, the catalog isn't
|
|
27
|
+
initialized — run `patties ui init` first (pass `--theme <neutral|slate|stone|zinc>`
|
|
28
|
+
to match the project).
|
|
29
|
+
2. Run `patties add <name>` (preview first with `patties view <name>` or
|
|
30
|
+
`patties add --view <name>` if the user wants to see the source).
|
|
31
|
+
3. Import the stamped component from `app/components/ui/<name>.tsx` and use it.
|
|
32
|
+
|
|
33
|
+
`patties add` edits `package.json` (it never runs an install) and stamps source
|
|
34
|
+
into `app/components/ui/`. After adding, remind the user to run `bun install` if
|
|
35
|
+
new peer deps were added.
|
|
36
|
+
|
|
37
|
+
## Scaffold a pattern
|
|
38
|
+
|
|
39
|
+
Pick the pattern from the catalog, `patties add` its components first, then
|
|
40
|
+
generate the listed files — adapting entity names, fields, columns, and copy to
|
|
41
|
+
the user's stated domain (e.g. "a CRM for veterinary clinics").
|
|
42
|
+
|
|
43
|
+
| Pattern | Goal | `patties add` components | Generated files (under `app/`) |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| **auth-rbac** | Login / logout + role-gated route | `form`, `input`, `label`, `button`, `card` | `routes/login.tsx`, `routes/logout.ts`, `routes/admin.tsx` (role-gated), `lib/auth.ts` (cookie session over mock users), `lib/rbac.ts` (role guard), `lib/mock-users.ts` |
|
|
46
|
+
| **crm** | Contacts list + detail / edit | `data-table`, `dialog`, `form`, `input`, `button`, `badge` | `routes/contacts/index.tsx` (table), `routes/contacts/[id].tsx` (detail), `islands/ContactForm.tsx`, `lib/mock-contacts.ts` |
|
|
47
|
+
| **task** | Task board / list | `card`, `checkbox`, `badge`, `dialog`, `button` | `routes/tasks.tsx`, `islands/TaskBoard.tsx` (columns + toggle), `lib/mock-tasks.ts` |
|
|
48
|
+
| **pivot** | Group-by / pivot over rows | `table`, `select`, `card` | `routes/pivot.tsx`, `islands/PivotTable.tsx` (pick row/col/measure), `lib/mock-rows.ts` |
|
|
49
|
+
| **dashboard** | Metrics overview | `card`, `chart`, `separator`, `sidebar` | `routes/dashboard.tsx` (cards + chart + sidebar shell), `lib/mock-metrics.ts` |
|
|
50
|
+
|
|
51
|
+
Per-pattern recipes:
|
|
52
|
+
|
|
53
|
+
- **auth-rbac** — sessions are a signed cookie over the mock user list; the RBAC
|
|
54
|
+
guard is a small helper the protected route calls in its handler. Make the
|
|
55
|
+
mock-only nature loud: a banner on the login page and a TODO at the top of the
|
|
56
|
+
auth module. Real auth needs persistence (a future DB spec).
|
|
57
|
+
- **crm** — the list uses `data-table` over `mock-contacts`; create / edit
|
|
58
|
+
happens in a `dialog` driven by a `form` island. The detail route reads the
|
|
59
|
+
same mock store by id.
|
|
60
|
+
- **task** — the board is an island holding `useState` columns; toggling a
|
|
61
|
+
`checkbox` moves a task between done / not-done. No persistence — state resets
|
|
62
|
+
on reload (call this out).
|
|
63
|
+
- **pivot** — an island lets the user pick a row dimension, a column dimension,
|
|
64
|
+
and a numeric measure from `select`s, then renders the aggregated `table`.
|
|
65
|
+
Aggregation runs client-side over the mock rows.
|
|
66
|
+
- **dashboard** — a static SSR shell (`sidebar` is `subtree`, `chart` hydrates)
|
|
67
|
+
with metric `card`s fed by `mock-metrics`; the chart is the one interactive
|
|
68
|
+
island.
|
|
69
|
+
|
|
70
|
+
Always `patties add` the listed components (initializing the catalog first if
|
|
71
|
+
needed) before generating files that import them. Only reference components that
|
|
72
|
+
exist in the shipped registry with `status: "completed"`.
|
|
73
|
+
|
|
74
|
+
## Depth contract: UI + mock data
|
|
75
|
+
|
|
76
|
+
Every pattern is scaffolded at **"UI + mock data"** depth:
|
|
77
|
+
|
|
78
|
+
- **In scope:** SSR routes / pages, islands where interactivity is needed,
|
|
79
|
+
stamped Patties UI components, and a typed in-memory seed in
|
|
80
|
+
`app/lib/mock-<entity>.ts` that the pages read from.
|
|
81
|
+
- **Out of scope:** `bun:sqlite`, migrations, a real persistence or auth
|
|
82
|
+
backend, network calls. The pattern *shows the shape*; the developer swaps the
|
|
83
|
+
mock data for a real source later.
|
|
84
|
+
- Every generated `mock-*` module starts with `// TODO: replace mock data with a
|
|
85
|
+
real source`, and the route / page carries a short note marking the mock
|
|
86
|
+
boundary.
|
|
87
|
+
|
|
88
|
+
Do not generate a database layer, migrations, or a real auth/network backend
|
|
89
|
+
from this skill — that is deferred to a future spec.
|
|
90
|
+
|
|
91
|
+
## Conventions
|
|
92
|
+
|
|
93
|
+
- Pages live under `app/routes/*.tsx` (default-export a React component); API
|
|
94
|
+
handlers under `app/routes/api/*.ts` (named `GET`/`POST`/… returning a
|
|
95
|
+
`Response`, e.g. via `ctx.json()`). Dynamic segments use `[id]`.
|
|
96
|
+
- Islands (interactive client components) live under `app/islands/` and are
|
|
97
|
+
wrapped at the use site in `<Island name="…">…</Island>` from `patties/render`.
|
|
98
|
+
- Mock data lives in `app/lib/mock-<entity>.ts`, typed, with the TODO marker.
|
|
99
|
+
- Never re-author component source — stamp from the catalog and import.
|
|
100
|
+
|
|
101
|
+
## See also
|
|
102
|
+
|
|
103
|
+
- The `patties-cli` skill — running, building, deploying the app.
|
|
104
|
+
- cli specs 11–15 (Patties UI: `ui init`, `add`, `view`, `update`, registries).
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Patties patterns — components & feature scaffolding
|
|
2
|
+
|
|
3
|
+
> Codex rule. The catalog and recipes below are shared verbatim with the
|
|
4
|
+
> Claude `/patties` skill — both are generated from one source
|
|
5
|
+
> (`templates/_shared/patties-patterns.md` in create-patties), so they
|
|
6
|
+
> cannot drift. Read this when the user asks to add a component or scaffold a
|
|
7
|
+
> feature pattern.
|
|
8
|
+
|
|
9
|
+
## When to use
|
|
10
|
+
|
|
11
|
+
Use this when the user wants to **add a Patties UI component** or **scaffold a
|
|
12
|
+
feature pattern** (auth + RBAC, a CRM, a task board, a pivot table, a
|
|
13
|
+
dashboard). It covers scaffolding only — for running, building, deploying, or
|
|
14
|
+
managing secrets, use the `patties-cli` skill instead.
|
|
15
|
+
|
|
16
|
+
Two capabilities:
|
|
17
|
+
|
|
18
|
+
1. **Add a UI component** — a thin wrapper over the deterministic catalog.
|
|
19
|
+
2. **Scaffold a feature pattern** — instruction-driven: you generate the files,
|
|
20
|
+
adapting names/fields/copy to the user's domain.
|
|
21
|
+
|
|
22
|
+
## Add a component
|
|
23
|
+
|
|
24
|
+
The component catalog is deterministic — never hand-author component source; the
|
|
25
|
+
registry is the source of truth.
|
|
26
|
+
|
|
27
|
+
1. If `app/components/ui/_internal/` does not exist, the catalog isn't
|
|
28
|
+
initialized — run `patties ui init` first (pass `--theme <neutral|slate|stone|zinc>`
|
|
29
|
+
to match the project).
|
|
30
|
+
2. Run `patties add <name>` (preview first with `patties view <name>` or
|
|
31
|
+
`patties add --view <name>` if the user wants to see the source).
|
|
32
|
+
3. Import the stamped component from `app/components/ui/<name>.tsx` and use it.
|
|
33
|
+
|
|
34
|
+
`patties add` edits `package.json` (it never runs an install) and stamps source
|
|
35
|
+
into `app/components/ui/`. After adding, remind the user to run `bun install` if
|
|
36
|
+
new peer deps were added.
|
|
37
|
+
|
|
38
|
+
## Scaffold a pattern
|
|
39
|
+
|
|
40
|
+
Pick the pattern from the catalog, `patties add` its components first, then
|
|
41
|
+
generate the listed files — adapting entity names, fields, columns, and copy to
|
|
42
|
+
the user's stated domain (e.g. "a CRM for veterinary clinics").
|
|
43
|
+
|
|
44
|
+
| Pattern | Goal | `patties add` components | Generated files (under `app/`) |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| **auth-rbac** | Login / logout + role-gated route | `form`, `input`, `label`, `button`, `card` | `routes/login.tsx`, `routes/logout.ts`, `routes/admin.tsx` (role-gated), `lib/auth.ts` (cookie session over mock users), `lib/rbac.ts` (role guard), `lib/mock-users.ts` |
|
|
47
|
+
| **crm** | Contacts list + detail / edit | `data-table`, `dialog`, `form`, `input`, `button`, `badge` | `routes/contacts/index.tsx` (table), `routes/contacts/[id].tsx` (detail), `islands/ContactForm.tsx`, `lib/mock-contacts.ts` |
|
|
48
|
+
| **task** | Task board / list | `card`, `checkbox`, `badge`, `dialog`, `button` | `routes/tasks.tsx`, `islands/TaskBoard.tsx` (columns + toggle), `lib/mock-tasks.ts` |
|
|
49
|
+
| **pivot** | Group-by / pivot over rows | `table`, `select`, `card` | `routes/pivot.tsx`, `islands/PivotTable.tsx` (pick row/col/measure), `lib/mock-rows.ts` |
|
|
50
|
+
| **dashboard** | Metrics overview | `card`, `chart`, `separator`, `sidebar` | `routes/dashboard.tsx` (cards + chart + sidebar shell), `lib/mock-metrics.ts` |
|
|
51
|
+
|
|
52
|
+
Per-pattern recipes:
|
|
53
|
+
|
|
54
|
+
- **auth-rbac** — sessions are a signed cookie over the mock user list; the RBAC
|
|
55
|
+
guard is a small helper the protected route calls in its handler. Make the
|
|
56
|
+
mock-only nature loud: a banner on the login page and a TODO at the top of the
|
|
57
|
+
auth module. Real auth needs persistence (a future DB spec).
|
|
58
|
+
- **crm** — the list uses `data-table` over `mock-contacts`; create / edit
|
|
59
|
+
happens in a `dialog` driven by a `form` island. The detail route reads the
|
|
60
|
+
same mock store by id.
|
|
61
|
+
- **task** — the board is an island holding `useState` columns; toggling a
|
|
62
|
+
`checkbox` moves a task between done / not-done. No persistence — state resets
|
|
63
|
+
on reload (call this out).
|
|
64
|
+
- **pivot** — an island lets the user pick a row dimension, a column dimension,
|
|
65
|
+
and a numeric measure from `select`s, then renders the aggregated `table`.
|
|
66
|
+
Aggregation runs client-side over the mock rows.
|
|
67
|
+
- **dashboard** — a static SSR shell (`sidebar` is `subtree`, `chart` hydrates)
|
|
68
|
+
with metric `card`s fed by `mock-metrics`; the chart is the one interactive
|
|
69
|
+
island.
|
|
70
|
+
|
|
71
|
+
Always `patties add` the listed components (initializing the catalog first if
|
|
72
|
+
needed) before generating files that import them. Only reference components that
|
|
73
|
+
exist in the shipped registry with `status: "completed"`.
|
|
74
|
+
|
|
75
|
+
## Depth contract: UI + mock data
|
|
76
|
+
|
|
77
|
+
Every pattern is scaffolded at **"UI + mock data"** depth:
|
|
78
|
+
|
|
79
|
+
- **In scope:** SSR routes / pages, islands where interactivity is needed,
|
|
80
|
+
stamped Patties UI components, and a typed in-memory seed in
|
|
81
|
+
`app/lib/mock-<entity>.ts` that the pages read from.
|
|
82
|
+
- **Out of scope:** `bun:sqlite`, migrations, a real persistence or auth
|
|
83
|
+
backend, network calls. The pattern *shows the shape*; the developer swaps the
|
|
84
|
+
mock data for a real source later.
|
|
85
|
+
- Every generated `mock-*` module starts with `// TODO: replace mock data with a
|
|
86
|
+
real source`, and the route / page carries a short note marking the mock
|
|
87
|
+
boundary.
|
|
88
|
+
|
|
89
|
+
Do not generate a database layer, migrations, or a real auth/network backend
|
|
90
|
+
from this skill — that is deferred to a future spec.
|
|
91
|
+
|
|
92
|
+
## Conventions
|
|
93
|
+
|
|
94
|
+
- Pages live under `app/routes/*.tsx` (default-export a React component); API
|
|
95
|
+
handlers under `app/routes/api/*.ts` (named `GET`/`POST`/… returning a
|
|
96
|
+
`Response`, e.g. via `ctx.json()`). Dynamic segments use `[id]`.
|
|
97
|
+
- Islands (interactive client components) live under `app/islands/` and are
|
|
98
|
+
wrapped at the use site in `<Island name="…">…</Island>` from `patties/render`.
|
|
99
|
+
- Mock data lives in `app/lib/mock-<entity>.ts`, typed, with the TODO marker.
|
|
100
|
+
- Never re-author component source — stamp from the catalog and import.
|
|
101
|
+
|
|
102
|
+
## See also
|
|
103
|
+
|
|
104
|
+
- The `patties-cli` skill — running, building, deploying the app.
|
|
105
|
+
- cli specs 11–15 (Patties UI: `ui init`, `add`, `view`, `update`, registries).
|
|
@@ -15,6 +15,7 @@ Built with Patties — a Bun-native full-stack meta-framework.
|
|
|
15
15
|
- [Build-time discovery](.codex/rules/build-time-discovery.md) — discovery happens at build time, not at request time.
|
|
16
16
|
- [Optional AI dependency](.codex/rules/optional-ai.md) — keep `@anthropic-ai/sdk` import-disjoint from non-AI code paths.
|
|
17
17
|
- [Patties CLI](.codex/rules/patties-cli.md) — `patties dev / build / start / deploy / secret`.
|
|
18
|
+
- [Patties patterns](.codex/rules/patties-patterns.md) — add UI components and scaffold feature patterns (auth, CRM, task board, pivot, dashboard).
|
|
18
19
|
|
|
19
20
|
## Live inventory
|
|
20
21
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
# Multi-stage build for a Patties app on the `bun` adapter.
|
|
3
|
+
# Build: docker build -t {{name}} .
|
|
4
|
+
# Run: docker run -p 3000:3000 {{name}}
|
|
5
|
+
|
|
6
|
+
FROM oven/bun:1.3 AS base
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
# --- deps: install once, cached on package.json changes ---
|
|
10
|
+
FROM base AS deps
|
|
11
|
+
COPY package.json bun.lock* ./
|
|
12
|
+
RUN bun install
|
|
13
|
+
|
|
14
|
+
# --- build: produce the production server bundle in .patties/ ---
|
|
15
|
+
FROM base AS build
|
|
16
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
17
|
+
COPY . .
|
|
18
|
+
RUN bunx patties build --target bun
|
|
19
|
+
|
|
20
|
+
# --- release: minimal runtime image ---
|
|
21
|
+
FROM base AS release
|
|
22
|
+
ENV NODE_ENV=production
|
|
23
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
24
|
+
COPY --from=build /app/.patties ./.patties
|
|
25
|
+
COPY package.json ./
|
|
26
|
+
EXPOSE 3000
|
|
27
|
+
CMD ["bun", ".patties/server/server-entry.js"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# packages/
|
|
2
|
+
|
|
3
|
+
Shared code for this Bun workspace. Create a package per shared concern
|
|
4
|
+
(e.g. `packages/ui`, `packages/db`) with its own `package.json`, then depend
|
|
5
|
+
on it from an app under `apps/*` using the workspace protocol:
|
|
6
|
+
|
|
7
|
+
```jsonc
|
|
8
|
+
// apps/<app>/package.json
|
|
9
|
+
{
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@{{name}}/ui": "workspace:*"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Bun resolves `workspace:*` to the local package and runs scripts
|
|
17
|
+
topologically with `bun --filter`. This monorepo is intentionally
|
|
18
|
+
Bun-workspaces-only — no Turborepo / Nx.
|