Tools
Tools: We built an admin dashboard that Claude can reshape into anything — here's how
2026-03-05
0 views
admin
the stack ## oklch changed how we think about theming ## the AI-native thing — why it matters ## what you can build with it ## the architecture (kept simple on purpose) ## stuff that bit us We just put out Dashwise over at agile turtles. It's an admin dashboard kit where every directory has a CLAUDE.md — so you can open it in Claude Code, say "make this into a CRM" or "turn this into a project tracker", and it actually works. The AI knows the architecture, the conventions, and where everything goes. Before we get into the AI stuff — the stack. We went with the latest everything. Here's the demo if you want to click around first. Everything runs on Node 20.9+. npm install && npm run dev and you're up. No Docker, no database, no env vars to configure. This deserves its own section because it changes how theming works. Tailwind v4 defaults to oklch. The "L" channel is perceptually uniform — a blue at oklch(0.6 0.15 265) and a green at oklch(0.6 0.15 150) actually look the same brightness. With hsl, that was never true. Colors at "50% lightness" looked wildly different depending on the hue. We built a 6-color theme switcher on top of this. Each preset just rotates the hue: All six look coherent without manual per-color tuning. That's oklch doing the work. The catch: if you write hsl(var(--primary)) out of muscle memory (or because you copied from an old shadcn/ui tutorial), nothing renders. No error. Just invisible elements. We stared at a blank screen for an hour before figuring that one out. Everything in the project uses Tailwind semantic classes — bg-primary, text-muted-foreground — and we documented this in the CLAUDE.md files so nobody else (human or AI) repeats it. Every directory has a CLAUDE.md: These aren't regular docs. They're structured context for AI coding assistants. Each one covers how to add new things (with copy-paste templates), conventions that aren't obvious from the code, and gotchas that would waste time. Real example. You open the project in Claude Code and say: "Add an invoices page with a table showing invoice number, client, amount, status, and due date." Without CLAUDE.md, the AI would probably put things in weird places, use hsl() for colors, add event handlers to Server Components, and build a table from scratch instead of using TanStack Table. With the CLAUDE.md chain, it: Same patterns, same structure, same conventions as the rest of the codebase. Dashwise ships as a generic admin dashboard — 10 pages, 5 auth flows, theme customizer. But because of the CLAUDE.md files, you can reshape it into pretty much anything: The CLAUDE.md files guide the AI to follow existing patterns. You describe what you want, and it builds it consistently with the rest of the codebase. We kept things predictable because that's what makes AI assistance actually useful. Every page follows one pattern: Server Component exports metadata. Client Component handles interaction. Every single page. No exceptions. All data comes from one file: grep -r "TODO: Replace" finds every integration point. When you connect a real backend, you know exactly what to swap. Route groups split the layouts: (dashboard)/ wraps pages in sidebar + topbar. (auth)/ wraps pages in a full-screen gradient layout. The parentheses don't show up in URLs. Clean separation, no layout nesting headaches. We documented these in the CLAUDE.md files so neither you nor the AI repeat them: agile turtles on gumroad. MIT license. BFF — no subscriptions, no lock-in. demo: dashwise.netlify.app if you end up building something with it, we'd like to hear about it. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
:root { --primary: oklch(0.488 0.243 264.376); } /* violet */
[data-color="blue"] { --primary: oklch(0.488 0.243 240); } /* blue */
[data-color="green"] { --primary: oklch(0.488 0.243 150); } /* green */ Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
:root { --primary: oklch(0.488 0.243 264.376); } /* violet */
[data-color="blue"] { --primary: oklch(0.488 0.243 240); } /* blue */
[data-color="green"] { --primary: oklch(0.488 0.243 150); } /* green */ CODE_BLOCK:
:root { --primary: oklch(0.488 0.243 264.376); } /* violet */
[data-color="blue"] { --primary: oklch(0.488 0.243 240); } /* blue */
[data-color="green"] { --primary: oklch(0.488 0.243 150); } /* green */ CODE_BLOCK:
CLAUDE.md — project overview, architecture, common tasks
src/app/CLAUDE.md — routing patterns, how to add pages
src/components/CLAUDE.md — component conventions, what not to touch
src/lib/CLAUDE.md — data layer, schemas, utilities
src/hooks/CLAUDE.md — hook patterns
src/types/CLAUDE.md — type conventions Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
CLAUDE.md — project overview, architecture, common tasks
src/app/CLAUDE.md — routing patterns, how to add pages
src/components/CLAUDE.md — component conventions, what not to touch
src/lib/CLAUDE.md — data layer, schemas, utilities
src/hooks/CLAUDE.md — hook patterns
src/types/CLAUDE.md — type conventions CODE_BLOCK:
CLAUDE.md — project overview, architecture, common tasks
src/app/CLAUDE.md — routing patterns, how to add pages
src/components/CLAUDE.md — component conventions, what not to touch
src/lib/CLAUDE.md — data layer, schemas, utilities
src/hooks/CLAUDE.md — hook patterns
src/types/CLAUDE.md — type conventions COMMAND_BLOCK:
export const metadata: Metadata = { title: "Kanban" } export default function KanbanPage() { return ( <div className="space-y-6"> <PageHeader title="Kanban Board" description="Drag and drop tasks" /> <KanbanContent /> {/* "use client" — all state lives here */} </div> )
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
export const metadata: Metadata = { title: "Kanban" } export default function KanbanPage() { return ( <div className="space-y-6"> <PageHeader title="Kanban Board" description="Drag and drop tasks" /> <KanbanContent /> {/* "use client" — all state lives here */} </div> )
} COMMAND_BLOCK:
export const metadata: Metadata = { title: "Kanban" } export default function KanbanPage() { return ( <div className="space-y-6"> <PageHeader title="Kanban Board" description="Drag and drop tasks" /> <KanbanContent /> {/* "use client" — all state lives here */} </div> )
} CODE_BLOCK:
import { kanbanTasks } from "@/lib/mock-data" // TODO: Replace with your API call Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { kanbanTasks } from "@/lib/mock-data" // TODO: Replace with your API call CODE_BLOCK:
import { kanbanTasks } from "@/lib/mock-data" // TODO: Replace with your API call - Next.js 16.1 with Turbopack as the default bundler. View Transitions baked in. No config needed — one flag in next.config.ts and every <Link> navigation gets a smooth crossfade.
- React 19.2 — startTransition driving those View Transitions, plus all the Server Component stuff actually working properly now.
- Tailwind CSS v4 — this is a big one. The whole color system moved from hsl to oklch. Perceptually uniform colors. You can build a theme switcher by rotating one hue value and every color in the palette stays visually consistent. Try doing that with hsl.
- shadcn/ui on the latest — 31 components, all pre-installed. We didn't touch any of them. That's the rule: npx shadcn@latest add, never hand-edit.
- zod 4 — breaking changes from v3, nothing dramatic but enough to burn an afternoon if you're not paying attention. required_error is now just error. .refine() breaks TypeScript inference with zodResolver and you need a gnarly type assertion.
- @dnd-kit for drag-and-drop — not react-beautiful-dnd (deprecated), not react-dnd (painful API). @dnd-kit with the DragOverlay pattern for proper cross-column kanban dragging.
- TanStack Table 8 — headless, typed, does sorting/filtering/pagination without fighting you.
- Recharts 3.7 — clean API, works well with Server Components. - Creates the page in src/app/(dashboard)/invoices/page.tsx as a Server Component
- Puts the interactive bits in a separate "use client" file
- Defines columns following the existing TanStack Table pattern
- Adds mock data with // TODO: Replace with your API call markers
- Adds the sidebar nav item
- Creates a loading skeleton - SaaS dashboard — swap mock data for your API, add billing, ship it
- project management tool — kanban board is already built, add projects and teams
- CRM — tables, profile page, inbox are your starting point
- analytics platform — charts page + theme customizer = white-label analytics
- client portal — auth pages exist, add role-based views
- internal tool — forms and settings are ready, hook up your internal APIs - oklch not hsl — invisible elements, no error, nothing. use Tailwind classes only.
- Server Component event handlers — we added an onClick to a page file. twice. React just silently ignores it. the interactive stuff has to live in a "use client" file.
- zod 4 migration — error instead of required_error. small change, silent breakage. .refine() needs a type assertion with zodResolver or TypeScript complains.
- @dnd-kit click vs drag — without activationConstraint: { distance: 5 }, every tiny mouse movement during a click triggers a drag. buttons on kanban cards become unusable.
- shadcn/ui files — never edit components/ui/ directly. we learned this the hard way when an update overwrote our changes.
how-totutorialguidedev.toaiserverroutingswitchdockernodedatabase