Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.
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
36 changes: 28 additions & 8 deletions .claude/rules/anti-patterns.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
# Anti-patterns — Common Mistakes in Pyreon Zero Apps
# Anti-patterns — Common Mistakes in Pyreon Zero

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

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

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

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

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

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

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

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

## Architecture mistakes
- **Never** bundle `@pyreon/core` or `@pyreon/reactivity` — they must be peer dependencies
- **Never** import from another package's `src/` directly — use the package name
- Duck-type external library interfaces instead of importing their types — avoids hard version coupling
- Error messages prefixed with `[zero]` and include actionable guidance

## Testing mistakes
- Test the public API as consumers use it, not internal implementation details
- Always run tests from the package directory
- Test error cases (throws, invalid inputs, edge cases) not just happy paths

## File organization
- If a source file exceeds ~300 lines, consider splitting
- Tests go in `src/tests/`, not `__tests__/` or root
- Every new public function/type must be exported from `src/index.ts`

## Hydration
- `hydrateRoot(container, vnode)` — container first, then vnode (not reversed)
- The `startClient()` function handles this automatically — prefer it over manual hydration
89 changes: 89 additions & 0 deletions .claude/rules/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Development Rules

## Signal Reactivity

- Every public reactive value MUST be a `Signal<T>` or `Computed<T>` — never a raw value that could go stale.
- Use `batch()` when setting multiple signals in a single operation to coalesce notifications.
- Use `signal.peek()` when reading without subscribing (e.g., inside event handlers, snapshot code).
- Never read signals inside constructors or module-level code — only inside effects, computeds, or component render functions.

## Package Boundaries

- Each package is independent. Never import from another `@pyreon/*` package's `src/` directly — use the package name.
- Peer dependencies (`@pyreon/core`, `@pyreon/reactivity`) are resolved at the consumer's level — never bundle them.
- `@pyreon/meta` is the barrel for fundamentals + UI system — `@pyreon/zero` is the framework. Never re-export fundamentals from zero directly.
- Shared utilities go in `packages/zero/src/utils/` — only extract when used by 2+ files.

## Context Pattern

All context-based APIs follow the same structure:

```typescript
const XContext = createContext<T | null>(null)

function XProvider(props) {
pushContext(new Map([[XContext.id, props.instance]]))
onUnmount(() => popContext())
return props.children
}

function useX(): T {
const instance = useContext(XContext)
if (!instance) throw new Error("[zero] useX() must be used within <XProvider>")
return instance
}
```

Or using `provide()` helper:

```typescript
function XProvider(props) {
provide(XContext, props.instance)
return props.children
}
```

## Component Customization Pattern

Components that need customization expose 3 levels:

1. `useX(props)` — composable returning handlers, state, ref callback
2. `createX(Component)` — HOC wrapping any component with behavior
3. `X` — default component built on `createX`

Example: `useLink()` → `createLink(Component)` → `Link`

## Error Messages

Prefix all thrown errors with `[zero]` and include actionable guidance:
```typescript
throw new Error("[zero] Missing #app container element. Add <div id=\"app\"> to your index.html.")
```

## Middleware Pattern

All middleware follows `(ctx: MiddlewareContext) => Response | void | Promise<Response | void>`:
- Return `Response` to short-circuit
- Return `void` / `undefined` to pass through
- Use `withHeaders()` from `utils/with-headers.ts` for header modification

## Vite Plugin Pattern

Vite plugins follow: `export function pluginName(config = {}): Plugin`

## Route Module Exports

Route files can export: `default`, `loader`, `guard`, `meta`, `error`, `loading`, `middleware`, `renderMode`

## Version Alignment

- All 4 packages (`zero`, `meta`, `zero-cli`, `create-zero`) share the same version (fixed versioning via changesets).
- `workspace:^` for inter-package peer deps (resolved at publish time).
- When bumping Pyreon deps, update: zero package.json, cli package.json, meta package.json, create-zero pyreonVersion(), template package.json, and CLAUDE.md.
- Always run `rm -f bun.lock && bun install` after version changes.

## Bun Quirks

- Bun doesn't resolve `extends` chains into `node_modules` for `jsxImportSource` — always duplicate it in root tsconfig.
- `customConditions: ["bun"]` needed for workspace deps to resolve in CI (lib/ not built).
- `bun run --filter='*' test` runs all workspace package tests — individual failures may cascade.
121 changes: 121 additions & 0 deletions .claude/rules/workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Workflow Rules

## Role

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.

## Mindset

_Inspired by: Next.js (convention over configuration), Nuxt (file-based everything), Svelte (focused batched progress), Solid (alignment before implementation), Hono (lean core, modular extensions)_

- **Do it properly, not quickly.** No shortcuts. No workarounds when root causes can be found. No `as any`. No disabling strict flags.
- **Understand before changing.** Read existing code. Understand the problem fully. Form a hypothesis. Verify it. Then fix.
- **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.
- **Think before acting.** For any non-trivial task, think through the approach first. Use plan mode for complex multi-step work.
- **Find root causes.** When something fails, investigate why — don't patch symptoms. Check versions, module resolution, types, and runtime behavior before writing code.
- **When uncertain, say so.** Don't guess. Don't fabricate confidence. Ask or investigate.
- **Verify before reporting.** Never blame upstream packages without reproducing in isolation. Read the actual type definition. Test the actual behavior.
- **One effort at a time.** Focus on completing the current task properly before starting the next. Batched, focused progress over scattered work.

## API Design Philosophy

_Inspired by: tRPC (types flow end-to-end), Zod (one clean chainable API), Drizzle ("if you know SQL, you know Drizzle")_

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?

- **Signals are the primitive.** `signal()`, `computed()`, `effect()`, `provide()` — four primitives that compose into everything. No dependency arrays, no re-renders.
- **File-based everything.** Routes, layouts, middleware, API routes, error boundaries — all from the file system. Convention over configuration.
- **Types flow end-to-end.** If users need `as any`, the types are wrong.
- **Zero-config defaults, full-control escape hatches.** Common case needs zero configuration. Advanced cases get explicit options.
- **Composability over configuration.** Three lines of explicit code beats a magic config object.

## Before Writing Code

- Read the existing source files before modifying.
- Check CLAUDE.md for the package's API surface.
- For new features, check if the pattern exists in another package.
- For complex tasks, outline the approach and get alignment before coding.

## Code Changes

- Keep changes minimal. One feature per file change.
- Follow existing naming: `useX` for hooks, `XProvider` for context, `createX` for factories.
- Export types separately: `export type { Foo }` not mixed with value exports.
- New public APIs need JSDoc with `@example` blocks.
- No unused imports, no dead code, no `// TODO` in committed code.
- Error messages prefixed with `[zero]` and include actionable guidance.

## Testing

- Every new public API needs tests.
- Test error cases, not just happy paths.
- Test files live at `packages/[name]/src/tests/`.
- Always run tests from the package directory.
- Run full test suite before pushing.

## Git Practices — MANDATORY

- **NEVER push directly to main.** Always create a branch and PR.
- **NEVER commit without running validation.**
- Don't commit unless explicitly asked.
- Never force push, never amend published commits.
- Use descriptive commit messages focused on "why" not "what".
- Stage specific files, not `git add .`.
- Always `git checkout main && git pull` before starting a new task.

## Validation Checklist — Before EVERY Push

Run ALL of these in order:

1. `bun run lint` — no lint errors
2. `bun run typecheck` — zero type errors
3. `bun run test` — all tests pass

If any step fails, fix it before pushing. Do not push broken code.

## Before Considering Work Complete

Every PR must be self-contained — code + docs + rules all in one:

1. All validation checklist steps pass
2. Exports updated: new APIs in `src/index.ts` with type exports
3. **CLAUDE.md** updated if API surface or ecosystem changed
4. **READMEs** updated (root, package, meta) if user-facing features changed
5. **Template** updated (package.json, CLAUDE.md, source) if new patterns introduced
6. **Anti-patterns/development rules** updated if new gotchas or patterns discovered
7. **Building rules** updated if new "how to use" patterns for AI agents
8. **.mcp.json** verified still correct
9. **create-zero `pyreonVersion()`** updated if versions changed
10. No breaking changes without discussion
11. Honest quality assessment

## Debugging

- Check dependency versions and module resolution FIRST.
- Don't assume — verify. Read the actual error, check the actual types.
- If a workaround is needed temporarily, document WHY and create a follow-up.
- Never blame upstream without reproducing in isolation first.

## Releases

- Use changesets for versioning: `bunx changeset` to create, `bunx changeset version` to bump.
- Fixed versioning: all packages share the same version.
- New packages need manual first publish before CI can handle OIDC.

## Continuous Learning — MANDATORY

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:

- **New anti-pattern discovered?** Add it to `anti-patterns.md` in the same commit.
- **New development pattern established?** Add it to `development.md` in the same PR.
- **API changed upstream?** Update `CLAUDE.md`, template `CLAUDE.md`, and `building.md` as part of the dep bump PR.
- **Bun/TypeScript quirk found?** Document it in `development.md` under "Bun Quirks" immediately.
- **Workaround added?** Document WHY in a code comment AND add to anti-patterns in the same commit.

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.

## Context Management

- Use `/compact` at ~50% context usage for long sessions.
- Start complex multi-package tasks in plan mode.
- Break work into steps that can complete within a single context window.
62 changes: 41 additions & 21 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
{
"permissions": {
"allow": [
"Bash(bun run build)",
"Bash(bun run test)",
"Bash(bun run typecheck)",
"Bash(bun run dev)",
"Bash(bunx biome check .)",
"Bash(bunx biome check --write .)",
"Bash(bunx changeset)",
"Bash(bunx changeset version)",
"Bash(git status*)",
"Bash(git diff*)",
"Bash(git log*)",
"Bash(git branch*)",
"Bash(git stash*)",
"Bash(wc *)",
"Bash(ls *)",
"Bash(mkdir *)",
"Bash(cat *)"
"Read",
"Glob",
"Grep",
"Bash(bunx vitest:*)",
"Bash(bunx tsc:*)",
"Bash(bunx biome:*)",
"Bash(bunx changeset:*)",
"Bash(bun run:*)",
"Bash(bun install:*)",
"Bash(bun test:*)",
"Bash(bun x:*)",
"Bash(bun add:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git branch:*)",
"Bash(git checkout:*)",
"Bash(git fetch:*)",
"Bash(git pull:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)",
"Bash(git stash:*)",
"Bash(gh pr:*)",
"Bash(gh run:*)",
"Bash(gh api:*)",
"Bash(ls:*)",
"Bash(mkdir:*)",
"Bash(npm view:*)",
"Bash(npm pack:*)",
"Bash(sed:*)",
"Bash(grep:*)",
"Bash(wc:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(cp:*)"
],
"deny": [
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Bash(rm -rf *)",
"Bash(bun publish*)"
"Bash(rm -rf:*)",
"Bash(git push --force:*)",
"Bash(git reset --hard:*)",
"Bash(git checkout .)",
"Bash(bun publish:*)"
]
}
}
Loading
Loading