DEV Community

Abhishek Pandit
Abhishek Pandit

Posted on

Your Coding DNA: The Three Files That Shape Every Line Claude Writes

Part 5 of 7 · Series: Building Your AI Developer Handbook · GitHub


What User Preference Files Are

Feedback files record mistakes. User preference files record who you are as a developer.

"Two carpenters given the same wood will build different tables — same tools, same materials, different hands, different results. User preference files are what make Claude build YOUR table, not the generic one."

Three preference files cover the three decisions you make on every single feature:

  1. How you organize code — architecture
  2. How you manage data — state management
  3. How you verify correctness — testing

Preference 1: Architecture — Feature-Based Folders

/features/auth/
  AuthForm.tsx
  AuthForm.test.tsx
  useAuth.ts
  auth.types.ts
/components/ui/
  Button.tsx
  Input.tsx
Enter fullscreen mode Exit fullscreen mode

Two competing philosophies:

Layer-based (what most tutorials teach):

/components/
/hooks/
/types/
/tests/
Enter fullscreen mode Exit fullscreen mode

Feature-based (what this workflow uses):

/features/auth/
/features/payment/
/features/dashboard/
Enter fullscreen mode Exit fullscreen mode

"In a layer-based structure, adding a 'login' feature means touching four separate folders. In a feature-based structure, you touch one."

Layer-based groups files by what they are. Feature-based groups by what they do.

The difference becomes obvious when you need to delete a feature:

  • Layer-based: hunt through /components, /hooks, /types, /tests — find and delete each file individually, hope you didn't miss anything
  • Feature-based: delete the /features/auth/ folder. Done.

The shared primitives rule:
Buttons, inputs, modals — building blocks shared across all features — live in /components/ui/ only. Never copy a Button into a feature folder.

"The kitchen is shared. Your desk is yours."

No barrel exports on large modules:

// Avoid on large modules — slows TS server, hurts tree-shaking
export { AuthForm } from './AuthForm'
export { useAuth } from './useAuth'
Enter fullscreen mode Exit fullscreen mode

Import directly from the file. The extra path is worth it.


Preference 2: State Management — The Ladder

1. useState    — local UI state, one component
2. Zustand     — shared client state across components  
3. TanStack Query — anything from a server
Enter fullscreen mode Exit fullscreen mode

"State management is like choosing a vehicle. A bicycle for the corner shop, a car for the city, a truck for cross-country. The mistake is driving a truck to the corner shop."

Step 1: useState — Start Here, Always

const [isOpen, setIsOpen] = useState(false)
Enter fullscreen mode Exit fullscreen mode

If state is local to one component and doesn't need to be shared — useState is perfect. Simple, readable, zero overhead.

"Don't add complexity until complexity is required."

Step 2: Zustand — Shared Client State Only

// Good: client-only shared state
const useUIStore = create((set) => ({
  sidebarOpen: false,
  toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}))
Enter fullscreen mode Exit fullscreen mode

When state needs sharing across multiple components and it's client-only — not from a server — Zustand is right. Modal state, sidebar flags, user preferences.

The hard rule: never put server data in Zustand.

// WRONG — server data in Zustand
const useUserStore = create((set) => ({
  user: null,
  fetchUser: async () => {
    const user = await api.getUser()
    set({ user })
  }
}))
Enter fullscreen mode Exit fullscreen mode

This creates a second copy of the data. They get out of sync. You add refresh logic. Then invalidation logic. Then loading states. You've just rebuilt TanStack Query, badly.

Step 3: TanStack Query — Anything From a Server

// RIGHT — server data belongs here
const { data: user } = useQuery({
  queryKey: ['user'],
  queryFn: () => api.getUser()
})
Enter fullscreen mode Exit fullscreen mode

Loading states, error states, caching, background refetching, cache invalidation — all free.

"Zustand is a drawer. TanStack Query is a real-time window to the outside world. Don't put windows in drawers."


Preference 3: Testing — The Pyramid

Unit tests:        pure logic, no side effects, no internal mocks
Integration tests: real database, real data flows
E2E tests:         critical paths only (login, checkout, core flow)
Enter fullscreen mode Exit fullscreen mode

"Testing is like quality control on a car assembly line. You don't test the whole car every time you tighten one bolt. You test the bolt, then the subsystem, then the full car before it ships."

Unit Tests — Pure Logic Only

// Perfect unit test — pure function, no external dependencies
test('formats currency correctly', () => {
  expect(formatCurrency(1000, 'USD')).toBe('$1,000.00')
})
Enter fullscreen mode Exit fullscreen mode

Belongs here: validators, formatters, reducers, pure utility functions.
Doesn't belong here: anything touching a database, API, filesystem, or network.

Integration Tests — Real Database, Always

"A flight simulator is great for training. But the first time you land a real plane, you discover the simulator lied about the crosswind."

Mocked database tests pass even when the real migration fails. Integration tests must use a real test database. Yes, they're slower. That's the point — they're testing real behavior.

E2E Tests — Critical Paths Only

E2E opens a real browser and clicks through the UI. Most accurate, slowest, most brittle.

"You don't test every road in the country to verify the highway exists. You just drive the highway."

E2E for: login, checkout, the one flow that makes you money. Not for edge cases — those belong in unit and integration tests.


How These Three Work Together

When Claude builds a new feature with these files loaded, it automatically:

  • Creates /features/feature-name/ — not scattered layer folders
  • Starts with useState, reaches for Zustand only if state needs sharing
  • Writes unit tests for logic, integration tests for data layer
  • Never adds barrel exports or server data to Zustand

You don't say any of this. It's already loaded.

"The best rule is one you never have to repeat."


Key Takeaway

User preference files transform Claude from a generic code generator into a collaborator that builds code the way you would build it.

  • Architecture: feature-based, co-located, no barrel exports
  • State: useState → Zustand → TanStack Query, never server state in Zustand
  • Testing: unit for logic, real DB for integration, E2E for critical paths only

Three files. Every feature they shape.


Next: Part 6 — Context is King: How Project Files and Templates Keep Claude on Track

All workflow files on GitHub

Top comments (0)