Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit a47b883

Browse files
authored
Merge pull request #38 from pyreon/improve-workflows-and-rules
Upgrade workflows, rules, and settings
2 parents f03be34 + 716ac07 commit a47b883

6 files changed

Lines changed: 301 additions & 49 deletions

File tree

.claude/rules/anti-patterns.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,45 @@
1-
# Anti-patterns — Common Mistakes in Pyreon Zero Apps
1+
# Anti-patterns — Common Mistakes in Pyreon Zero
22

33
## React patterns that don't apply here
44
- **Never** use `useState`, `useEffect`, `useCallback`, `useMemo` — these are React hooks
55
- **Never** import `h` manually in `.tsx` files — JSX transform handles it
66
- **Never** return cleanup functions from `onMount` — use `onUnmount` separately
7+
- **Never** use `className` or `htmlFor` — use `class` and `for`
78

89
## Signal mistakes
910
- **Never** create signals inside the render return function — they must be in the component body
1011
- **Never** nest `effect()` inside `effect()` — effects are flat, use `computed()` for derived state
1112
- **Never** set 3+ signals individually without `batch()` — causes unnecessary re-renders
1213
- **Never** call `signal(value)` to set — use `signal.set(value)` or `signal.update(fn)`
1314
- **Never** forget to call signal as function to read: use `count()` not `count`
15+
- **Stale closures**: Don't capture `signal.peek()` in a closure that outlives the current tick. Use `signal()` (subscribing read) inside effects/computeds
16+
- **Signal in hot path**: Don't create signals inside render functions or loops — create them once in setup
1417

1518
## JSX attribute casing
16-
- **Always** use camelCase for JSX attributes: `onClick`, `onMouseEnter`, `onTouchStart`, `onLoad`
19+
- **Always** use camelCase for events: `onClick`, `onMouseEnter`, `onTouchStart`, `onLoad`
1720
- **Never** use lowercase DOM event names: `onclick`, `onmouseenter`, `ontouchstart`, `onload`
1821
- **Always** use `srcSet` not `srcset`, `fetchPriority` not `fetchpriority`
19-
- **Always** use `className``class`, `htmlFor``for` (Pyreon uses HTML names, not React names)
2022

2123
## JSX reactive expressions
2224
- **Never** use bare signal reads in JSX text — wrap in arrow: `{() => count()}` not `{count()}`
23-
- **Never** return `undefined` from reactive JSX attributes — return empty string `''` instead
24-
- **Never** use reactive functions for `aria-current` — compute it as a static value
25+
- **Never** return `undefined` from reactive JSX attributes — return empty string `''` or use conditional spread `{...(value ? { attr: value } : {})}`
26+
- Use conditional spreads for optional attributes with `exactOptionalPropertyTypes`
2527

2628
## Effect cleanup
2729
- `effect()` returns an `Effect` object with `.dispose()` — not a raw function
2830
- Use `onUnmount(() => dispose.dispose())` for cleanup, not `onUnmount(dispose)`
29-
- Prefer `onCleanup()` inside effects for inline cleanup (available since @pyreon/reactivity 0.7.0)
31+
- Prefer `onCleanup()` inside effects for inline cleanup
3032

31-
## Context mistakes
33+
## Context pattern
3234
- **Never** use `<Context.Provider value={...}>` JSX — Pyreon contexts don't have a `.Provider` component
33-
- Use `provide(context, value)` from `@pyreon/core` (available since 0.6.0) — it calls `pushContext` + `onUnmount(popContext)` automatically
35+
- Use `provide(context, value)` from `@pyreon/core` — it calls `pushContext` + `onUnmount(popContext)` automatically
3436
- For manual control: `pushContext(new Map([[ctx.id, value]]))` + `onUnmount(() => popContext())`
37+
- Always throw if context is missing: `if (!instance) throw new Error("[zero] useX() must be used within <XProvider>")`
3538

3639
## Store mistakes
3740
- **Never** import `signal`/`computed`/`effect` from `@pyreon/reactivity` in app code — use `@pyreon/store` re-exports
3841
- **Never** call `defineStore` inside a component — it's a module-level declaration
42+
- `useStore()` returns `{ store, patch, subscribe }` — destructure `store` to access state
3943
- Store setup function runs once (singleton) — don't rely on it re-running
4044

4145
## Form mistakes
@@ -51,6 +55,22 @@
5155
- Return `void` / `undefined` to pass through (don't short-circuit)
5256
- Use `withHeaders()` from `utils/with-headers.ts` to modify Response headers
5357

58+
## Architecture mistakes
59+
- **Never** bundle `@pyreon/core` or `@pyreon/reactivity` — they must be peer dependencies
60+
- **Never** import from another package's `src/` directly — use the package name
61+
- Duck-type external library interfaces instead of importing their types — avoids hard version coupling
62+
- Error messages prefixed with `[zero]` and include actionable guidance
63+
64+
## Testing mistakes
65+
- Test the public API as consumers use it, not internal implementation details
66+
- Always run tests from the package directory
67+
- Test error cases (throws, invalid inputs, edge cases) not just happy paths
68+
69+
## File organization
70+
- If a source file exceeds ~300 lines, consider splitting
71+
- Tests go in `src/tests/`, not `__tests__/` or root
72+
- Every new public function/type must be exported from `src/index.ts`
73+
5474
## Hydration
5575
- `hydrateRoot(container, vnode)` — container first, then vnode (not reversed)
5676
- The `startClient()` function handles this automatically — prefer it over manual hydration

.claude/rules/development.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Development Rules
2+
3+
## Signal Reactivity
4+
5+
- Every public reactive value MUST be a `Signal<T>` or `Computed<T>` — never a raw value that could go stale.
6+
- Use `batch()` when setting multiple signals in a single operation to coalesce notifications.
7+
- Use `signal.peek()` when reading without subscribing (e.g., inside event handlers, snapshot code).
8+
- Never read signals inside constructors or module-level code — only inside effects, computeds, or component render functions.
9+
10+
## Package Boundaries
11+
12+
- Each package is independent. Never import from another `@pyreon/*` package's `src/` directly — use the package name.
13+
- Peer dependencies (`@pyreon/core`, `@pyreon/reactivity`) are resolved at the consumer's level — never bundle them.
14+
- `@pyreon/meta` is the barrel for fundamentals + UI system — `@pyreon/zero` is the framework. Never re-export fundamentals from zero directly.
15+
- Shared utilities go in `packages/zero/src/utils/` — only extract when used by 2+ files.
16+
17+
## Context Pattern
18+
19+
All context-based APIs follow the same structure:
20+
21+
```typescript
22+
const XContext = createContext<T | null>(null)
23+
24+
function XProvider(props) {
25+
pushContext(new Map([[XContext.id, props.instance]]))
26+
onUnmount(() => popContext())
27+
return props.children
28+
}
29+
30+
function useX(): T {
31+
const instance = useContext(XContext)
32+
if (!instance) throw new Error("[zero] useX() must be used within <XProvider>")
33+
return instance
34+
}
35+
```
36+
37+
Or using `provide()` helper:
38+
39+
```typescript
40+
function XProvider(props) {
41+
provide(XContext, props.instance)
42+
return props.children
43+
}
44+
```
45+
46+
## Component Customization Pattern
47+
48+
Components that need customization expose 3 levels:
49+
50+
1. `useX(props)` — composable returning handlers, state, ref callback
51+
2. `createX(Component)` — HOC wrapping any component with behavior
52+
3. `X` — default component built on `createX`
53+
54+
Example: `useLink()``createLink(Component)``Link`
55+
56+
## Error Messages
57+
58+
Prefix all thrown errors with `[zero]` and include actionable guidance:
59+
```typescript
60+
throw new Error("[zero] Missing #app container element. Add <div id=\"app\"> to your index.html.")
61+
```
62+
63+
## Middleware Pattern
64+
65+
All middleware follows `(ctx: MiddlewareContext) => Response | void | Promise<Response | void>`:
66+
- Return `Response` to short-circuit
67+
- Return `void` / `undefined` to pass through
68+
- Use `withHeaders()` from `utils/with-headers.ts` for header modification
69+
70+
## Vite Plugin Pattern
71+
72+
Vite plugins follow: `export function pluginName(config = {}): Plugin`
73+
74+
## Route Module Exports
75+
76+
Route files can export: `default`, `loader`, `guard`, `meta`, `error`, `loading`, `middleware`, `renderMode`
77+
78+
## Version Alignment
79+
80+
- All 4 packages (`zero`, `meta`, `zero-cli`, `create-zero`) share the same version (fixed versioning via changesets).
81+
- `workspace:^` for inter-package peer deps (resolved at publish time).
82+
- When bumping Pyreon deps, update: zero package.json, cli package.json, meta package.json, create-zero pyreonVersion(), template package.json, and CLAUDE.md.
83+
- Always run `rm -f bun.lock && bun install` after version changes.
84+
85+
## Bun Quirks
86+
87+
- Bun doesn't resolve `extends` chains into `node_modules` for `jsxImportSource` — always duplicate it in root tsconfig.
88+
- `customConditions: ["bun"]` needed for workspace deps to resolve in CI (lib/ not built).
89+
- `bun run --filter='*' test` runs all workspace package tests — individual failures may cascade.

.claude/rules/workflow.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Workflow Rules
2+
3+
## Role
4+
5+
You are a senior framework engineer building Pyreon Zero — a next-generation full-stack meta-framework designed for AI agents to build with successfully. Every decision should optimize for: correctness first, developer experience second, AI-friendliness third.
6+
7+
## Mindset
8+
9+
_Inspired by: Next.js (convention over configuration), Nuxt (file-based everything), Svelte (focused batched progress), Solid (alignment before implementation), Hono (lean core, modular extensions)_
10+
11+
- **Do it properly, not quickly.** No shortcuts. No workarounds when root causes can be found. No `as any`. No disabling strict flags.
12+
- **Understand before changing.** Read existing code. Understand the problem fully. Form a hypothesis. Verify it. Then fix.
13+
- **Be honest about quality.** A truthful 6/10 is infinitely more valuable than an inflated 9/10. List what's broken before claiming something works.
14+
- **Think before acting.** For any non-trivial task, think through the approach first. Use plan mode for complex multi-step work.
15+
- **Find root causes.** When something fails, investigate why — don't patch symptoms. Check versions, module resolution, types, and runtime behavior before writing code.
16+
- **When uncertain, say so.** Don't guess. Don't fabricate confidence. Ask or investigate.
17+
- **Verify before reporting.** Never blame upstream packages without reproducing in isolation. Read the actual type definition. Test the actual behavior.
18+
- **One effort at a time.** Focus on completing the current task properly before starting the next. Batched, focused progress over scattered work.
19+
20+
## API Design Philosophy
21+
22+
_Inspired by: tRPC (types flow end-to-end), Zod (one clean chainable API), Drizzle ("if you know SQL, you know Drizzle")_
23+
24+
Zero's competitive advantage is simplicity. Every API must pass the "AI agent test" — can an AI agent use it correctly on the first try?
25+
26+
- **Signals are the primitive.** `signal()`, `computed()`, `effect()`, `provide()` — four primitives that compose into everything. No dependency arrays, no re-renders.
27+
- **File-based everything.** Routes, layouts, middleware, API routes, error boundaries — all from the file system. Convention over configuration.
28+
- **Types flow end-to-end.** If users need `as any`, the types are wrong.
29+
- **Zero-config defaults, full-control escape hatches.** Common case needs zero configuration. Advanced cases get explicit options.
30+
- **Composability over configuration.** Three lines of explicit code beats a magic config object.
31+
32+
## Before Writing Code
33+
34+
- Read the existing source files before modifying.
35+
- Check CLAUDE.md for the package's API surface.
36+
- For new features, check if the pattern exists in another package.
37+
- For complex tasks, outline the approach and get alignment before coding.
38+
39+
## Code Changes
40+
41+
- Keep changes minimal. One feature per file change.
42+
- Follow existing naming: `useX` for hooks, `XProvider` for context, `createX` for factories.
43+
- Export types separately: `export type { Foo }` not mixed with value exports.
44+
- New public APIs need JSDoc with `@example` blocks.
45+
- No unused imports, no dead code, no `// TODO` in committed code.
46+
- Error messages prefixed with `[zero]` and include actionable guidance.
47+
48+
## Testing
49+
50+
- Every new public API needs tests.
51+
- Test error cases, not just happy paths.
52+
- Test files live at `packages/[name]/src/tests/`.
53+
- Always run tests from the package directory.
54+
- Run full test suite before pushing.
55+
56+
## Git Practices — MANDATORY
57+
58+
- **NEVER push directly to main.** Always create a branch and PR.
59+
- **NEVER commit without running validation.**
60+
- Don't commit unless explicitly asked.
61+
- Never force push, never amend published commits.
62+
- Use descriptive commit messages focused on "why" not "what".
63+
- Stage specific files, not `git add .`.
64+
- Always `git checkout main && git pull` before starting a new task.
65+
66+
## Validation Checklist — Before EVERY Push
67+
68+
Run ALL of these in order:
69+
70+
1. `bun run lint` — no lint errors
71+
2. `bun run typecheck` — zero type errors
72+
3. `bun run test` — all tests pass
73+
74+
If any step fails, fix it before pushing. Do not push broken code.
75+
76+
## Before Considering Work Complete
77+
78+
Every PR must be self-contained — code + docs + rules all in one:
79+
80+
1. All validation checklist steps pass
81+
2. Exports updated: new APIs in `src/index.ts` with type exports
82+
3. **CLAUDE.md** updated if API surface or ecosystem changed
83+
4. **READMEs** updated (root, package, meta) if user-facing features changed
84+
5. **Template** updated (package.json, CLAUDE.md, source) if new patterns introduced
85+
6. **Anti-patterns/development rules** updated if new gotchas or patterns discovered
86+
7. **Building rules** updated if new "how to use" patterns for AI agents
87+
8. **.mcp.json** verified still correct
88+
9. **create-zero `pyreonVersion()`** updated if versions changed
89+
10. No breaking changes without discussion
90+
11. Honest quality assessment
91+
92+
## Debugging
93+
94+
- Check dependency versions and module resolution FIRST.
95+
- Don't assume — verify. Read the actual error, check the actual types.
96+
- If a workaround is needed temporarily, document WHY and create a follow-up.
97+
- Never blame upstream without reproducing in isolation first.
98+
99+
## Releases
100+
101+
- Use changesets for versioning: `bunx changeset` to create, `bunx changeset version` to bump.
102+
- Fixed versioning: all packages share the same version.
103+
- New packages need manual first publish before CI can handle OIDC.
104+
105+
## Continuous Learning — MANDATORY
106+
107+
Every PR must include updates to rules and docs alongside the code changes. Don't submit code-only PRs when something was learned — update the rules in the SAME PR:
108+
109+
- **New anti-pattern discovered?** Add it to `anti-patterns.md` in the same commit.
110+
- **New development pattern established?** Add it to `development.md` in the same PR.
111+
- **API changed upstream?** Update `CLAUDE.md`, template `CLAUDE.md`, and `building.md` as part of the dep bump PR.
112+
- **Bun/TypeScript quirk found?** Document it in `development.md` under "Bun Quirks" immediately.
113+
- **Workaround added?** Document WHY in a code comment AND add to anti-patterns in the same commit.
114+
115+
The rules files are your institutional memory. Update them as you work, not as a separate follow-up. A PR that changes behavior without updating docs is incomplete.
116+
117+
## Context Management
118+
119+
- Use `/compact` at ~50% context usage for long sessions.
120+
- Start complex multi-package tasks in plan mode.
121+
- Break work into steps that can complete within a single context window.

.claude/settings.json

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,49 @@
11
{
22
"permissions": {
33
"allow": [
4-
"Bash(bun run build)",
5-
"Bash(bun run test)",
6-
"Bash(bun run typecheck)",
7-
"Bash(bun run dev)",
8-
"Bash(bunx biome check .)",
9-
"Bash(bunx biome check --write .)",
10-
"Bash(bunx changeset)",
11-
"Bash(bunx changeset version)",
12-
"Bash(git status*)",
13-
"Bash(git diff*)",
14-
"Bash(git log*)",
15-
"Bash(git branch*)",
16-
"Bash(git stash*)",
17-
"Bash(wc *)",
18-
"Bash(ls *)",
19-
"Bash(mkdir *)",
20-
"Bash(cat *)"
4+
"Read",
5+
"Glob",
6+
"Grep",
7+
"Bash(bunx vitest:*)",
8+
"Bash(bunx tsc:*)",
9+
"Bash(bunx biome:*)",
10+
"Bash(bunx changeset:*)",
11+
"Bash(bun run:*)",
12+
"Bash(bun install:*)",
13+
"Bash(bun test:*)",
14+
"Bash(bun x:*)",
15+
"Bash(bun add:*)",
16+
"Bash(git status:*)",
17+
"Bash(git diff:*)",
18+
"Bash(git log:*)",
19+
"Bash(git branch:*)",
20+
"Bash(git checkout:*)",
21+
"Bash(git fetch:*)",
22+
"Bash(git pull:*)",
23+
"Bash(git add:*)",
24+
"Bash(git commit:*)",
25+
"Bash(git push:*)",
26+
"Bash(git stash:*)",
27+
"Bash(gh pr:*)",
28+
"Bash(gh run:*)",
29+
"Bash(gh api:*)",
30+
"Bash(ls:*)",
31+
"Bash(mkdir:*)",
32+
"Bash(npm view:*)",
33+
"Bash(npm pack:*)",
34+
"Bash(sed:*)",
35+
"Bash(grep:*)",
36+
"Bash(wc:*)",
37+
"Bash(head:*)",
38+
"Bash(tail:*)",
39+
"Bash(cp:*)"
2140
],
2241
"deny": [
23-
"Bash(git push --force*)",
24-
"Bash(git reset --hard*)",
25-
"Bash(rm -rf *)",
26-
"Bash(bun publish*)"
42+
"Bash(rm -rf:*)",
43+
"Bash(git push --force:*)",
44+
"Bash(git reset --hard:*)",
45+
"Bash(git checkout .)",
46+
"Bash(bun publish:*)"
2747
]
2848
}
2949
}

0 commit comments

Comments
 (0)