mit einem Klick
frontend-philosophy
// React/Preact frontend conventions: TanStack Query for server state, 8-section components, no TypeScript, no barrel files.
// React/Preact frontend conventions: TanStack Query for server state, 8-section components, no TypeScript, no barrel files.
Read and post to Twitter/X via the `bird` CLI — tweets, threads, search, timelines, bookmarks, follows, lists, media.
Ultra-compressed communication mode. Cuts tokens ~75%. Levels: lite, full, ultra, wenyan-lite/full/ultra. Trigger: /caveman, "be brief".
Write codebase docs: README, architecture, API, comments. Project structure and getting-started focus.
Compress memory files (CLAUDE.md, todos) to caveman format. Backup saved as FILE.original.md. Trigger: /caveman:compress <filepath>.
Manage Hetzner Cloud infrastructure via the hcloud CLI. Trigger: "hcloud", "hetzner", any Hetzner task.
Find architecture improvements: consolidate modules, improve testability and AI navigation. Trigger: improve architecture, refactor.
| name | frontend-philosophy |
| description | React/Preact frontend conventions: TanStack Query for server state, 8-section components, no TypeScript, no barrel files. |
Authoritative conventions for all React/Preact work. These are not suggestions — they are the standard. When in doubt, choose the simpler path.
"Every line of code is a liability. The best code is no code. The second best is simple, boring code that obviously works."
| Purpose | Default | Notes |
|---|---|---|
| Runtime/pkg | Bun | Fallback: npm. Never pnpm/yarn |
| Bundler | Vite | With @/ path alias |
| Framework | React or Preact | + TanStack Query/Router |
| Styling | Tailwind CSS | Utility-first, cn() for merging |
| UI primitives | shadcn/ui pattern | CVA for variants, Radix primitives |
| Server state | TanStack Query | ONLY source of truth for API data |
| Client state | Zustand or Preact Signals | UI state only (modals, theme, filters) |
| Icons | @remixicon/react | Ri*Line for outline, Ri*Fill for solid |
| Types | No TypeScript | .jsx components, .js utilities |
Speed over ceremony. Type gymnastics add grief, not joy. Runtime validation at system boundaries, shared constants, JSDoc for complex functions.
src/
├── components/
│ ├── ui/ # Base UI (shadcn pattern)
│ ├── billing/ # Feature-based grouping
│ ├── support/ # NOT type-based (lists/, forms/, displays/)
│ └── common/ # Shared across features
├── hooks/ # Custom hooks (use* prefix)
├── stores/ # Zustand stores
├── constants/ # Shared constants
├── lib/ # Utilities (cn, formatters)
├── pages/ # Route-level components
└── api/ # API client
Naming rules:
.jsx components, .js utilities — alwayshandle* event handlers, use* hooks, render* render methodsEvery component follows this order. See references/component-patterns.md for full examples.
const Component = ({ prop1, prop2 }) => {
// 1. Server state (TanStack Query)
const { data, isLoading, error } = useQuery({ ... });
// 2. Global client state (Zustand/signals)
const { theme } = useThemeStore();
// 3. Local state (useState)
const [isOpen, setIsOpen] = useState(false);
// 4. Refs
const formRef = useRef(null);
// 5. Custom hooks
const { isOnline } = useNetworkStatus();
// 6. Derived values (compute in render, no useState)
const isValid = email.includes("@");
const fullName = `${data?.first} ${data?.last}`;
// 7. Effects (DOM sync, subscriptions, analytics ONLY)
useEffect(() => { ... }, [deps]);
// 8. Event handlers
const handleSubmit = () => { ... };
// Return — early returns for loading/error first
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
return ( ... );
};
// CORRECT — single source of truth
const { data: users } = useQuery({
queryKey: ["users", filters],
queryFn: () => api.get("/users", { params: filters }),
staleTime: 5 * 60 * 1000,
gcTime: 60 * 60 * 1000, // NOT cacheTime (v5)
});
// WRONG — duplicating server data in local state
const [users, setUsers] = useState([]);
useEffect(() => { fetchUsers().then(setUsers); }, []);
Only for UI concerns: modals, theme, sidebar, filters, form inputs.
// CORRECT — derive in render
const hasError = error !== null;
const activeUsers = users?.filter(u => u.isActive);
// WRONG — useState for derived values
const [hasError, setHasError] = useState(false);
useEffect(() => setHasError(error !== null), [error]);
These are hard rules, not guidelines.
| NEVER do this | Do this instead |
|---|---|
useEffect for state sync | Move logic to event handlers |
useCallback | Just define the function (almost never needed) |
useState for derived values | Compute in render |
| Duplicate server data in useState | TanStack Query is the source of truth |
| Barrel files (index.ts re-exports) | Import directly from the file |
useMemo without profiling first | Just compute it |
| Prop drilling beyond 2 levels | Composition or Zustand |
| Business logic in components | Extract to hooks or utilities |
| Multiple state management libs | TanStack Query + ONE client state lib |
| Inline styles or global CSS | Tailwind utilities |
| Premature abstraction | Rule of 3 — wait for 3+ use cases |
| Custom wrappers around libraries | Use the library directly |
| Over-commenting | Self-documenting names |
Only three:
Everything else belongs in an event handler or TanStack Query.
Prettier defaults:
rounded-fullrounded-xlcn() from @/lib/utils for class mergingdata-slot attributes for CSS targetingBefore shipping, ask: