Onboarding notes for Claude Code sessions working on jonmatum/next-shell.
Skim the sections most relevant to the task at hand; the authoritative
source for each topic is the linked skill / file / issue.
@jonmatum/next-shell is a Next.js + shadcn/ui app shell + design-system
extraction from the Smart Pad Rules codebase. It's a pnpm monorepo that
publishes a single package:
packages/next-shell/— the librarytools/eslint-plugin-next-shell/— custom ESLint rules (no-raw-colorsis the ground-truth enforcer of the semantic-token system)
The rollout is tracked by meta-issue #13 and phased sub-issues #1 through #12. Every phase lands on its own branch and PR.
- No raw color literals in library code. Every color reaches the DOM
through the semantic-token CSS variables declared in
packages/next-shell/src/styles/tokens.css. Thenext-shell/no-raw-colorsESLint rule fails CI immediately on regressions. If a new token is genuinely needed, add it totokens.css+preset.css+ thecolorTokensliteral array insrc/tokens/index.tsfirst, update tests, then use it. - Client/server boundaries are explicit. Client components get
'use client'at the top of their source file. The tsup post-build hook re-prepends'use client'todist/<subpath>/index.{js,cjs}for every entry in theCLIENT_ENTRIESallowlist inpackages/next-shell/tsup.config.ts. When a new top-level subpath entry is a client module, add it to that list. - Server-safe helpers live under
<subpath>/server/. Seesrc/providers/server/— cookie readers importable from a Server Component without forcing a client boundary. - Radix primitives are the default substrate. Don't substitute homegrown equivalents without discussing trade-offs first.
- Accessibility is non-negotiable. Every interactive primitive has
an accessible name; focus rings are token-driven
(
focus-visible:ring-ring); Radix semantics (roles, keyboard nav) are preserved.
When a PR the session just opened reaches CI green + AI auto-approval, flip it from draft to ready-for-review and squash-merge without pausing for human confirmation. The user has standing authorization; pausing on every green PR wastes round-trips. Then:
git checkout maingit pull origin main- Continue with the next slice on a fresh branch.
Applies only to PRs the current session authored on a claude/* branch.
Does NOT apply to PRs opened by others, PRs with unresolved review
comments, or PRs still red/yellow in CI. If an AI review is NOT
auto-approved or a human reviewer leaves comments, pause and address them
before merging.
The auto-merge rule is specific to PR merges on this repo. It does
NOT relax confirmation requirements for other destructive actions
(git reset --hard, force push, rm -rf, dropping tables, etc.). Ask
first for those.
Mirror the companion
next-shell-contributor
skill (the authoritative source):
| Kind | Branch pattern |
|---|---|
| Phase work | claude/phase-N-<slug> |
| Chore | claude/chore-<slug> |
| Fix | claude/fix-<slug> |
Commit and PR titles use conventional-commit style, scoped by phase when applicable:
feat(phase-3): vendor base primitives — Button, Label, Input, …
chore(claude): vendor official shadcn/ui skill from shadcn-ui/ui
fix(primitives): Dialog close button focus trap regression
PR bodies follow the skill's 5-section template: one-line summary + issue links · what's in the box · what is intentionally NOT in this PR · verification (paste the lint/typecheck/test/build results) · checklist.
PRs open as draft. Flip to ready only after CI is green (or after the auto-approval / auto-merge standing rule fires).
GitHub MCP is restricted to jonmatum/next-shell by design, so
there's no risk of acting on another repo. Use mcp__github__* for all
GitHub interactions — no gh CLI available.
pnpm format # prettier --write
pnpm lint # eslint --max-warnings=0
pnpm typecheck # tsc --noEmit
pnpm test # vitest run (no watch)
pnpm build # tsup + tailwind preset + DTS
Pre-commit runs lint-staged via Husky (prettier + eslint --fix on
staged files only) — you still need the full pnpm test + pnpm build
locally because lint-staged only touches what's staged.
packages/next-shell/
├── components.json # shadcn CLI config (aliases → src/…)
├── src/
│ ├── index.ts # root barrel
│ ├── core/ # cn(), packageVersion, shared utilities
│ ├── primitives/ # shadcn-vendored primitives (42 of them)
│ ├── providers/ # ThemeProvider + hooks (client)
│ │ └── server/ # SSR cookie helpers (server-safe)
│ ├── tokens/ # TS view of the semantic-token contract
│ ├── styles/
│ │ ├── tokens.css # :root + [data-theme='dark'] tokens
│ │ └── preset.css # Tailwind v4 @theme mappings
│ ├── tailwind-preset/
│ ├── layout/ # (Phase 4)
│ ├── auth/ # (Phase 7)
│ └── hooks/ # (Phase 8)
├── tsup.config.ts
├── vitest.config.ts
└── package.json
Subpaths exported through package.json#exports:
@jonmatum/next-shell # root barrel
@jonmatum/next-shell/core # cn, version
@jonmatum/next-shell/primitives # shadcn primitives (client)
@jonmatum/next-shell/providers # ThemeProvider etc. (client)
@jonmatum/next-shell/providers/server # SSR cookie helpers (server-safe)
@jonmatum/next-shell/tokens # TS token contract
@jonmatum/next-shell/tailwind-preset
@jonmatum/next-shell/styles/{tokens,preset}.css
@jonmatum/next-shell/layout
@jonmatum/next-shell/layout/server
@jonmatum/next-shell/formatters
@jonmatum/next-shell/auth
@jonmatum/next-shell/auth/nextauth
@jonmatum/next-shell/auth/mock
@jonmatum/next-shell/auth/server
@jonmatum/next-shell/hooks
- Primitives are vendored verbatim from
shadcn-ui/ui, pinned to a specific commit SHA (84d1d476b1d1…as of Phase 3). To update, re-fetch from the new SHA and bump the pin in.claude/skills/README.md+ PR bodies. - All shadcn-style
@/…absolute imports are rewritten on vendor:@/lib/utils→@/core/cn@/registry/new-york-v4/ui/button→@/primitives/button@/registry/new-york-v4/ui/dialog→@/primitives/dialog@/registry/new-york-v4/ui/label→@/primitives/label@/registry/new-york-v4/ui/toggle→@/primitives/toggle
- Path aliases are wired in
packages/next-shell/tsconfig.json(@/* → src/*) and mirrored invitest.config.ts(resolve.alias). .mcp.jsonat the repo root registers the official shadcn MCP server so future sessions get the canonical component catalog automatically.
All in .claude/skills/, loaded automatically per session:
| Skill | When it helps |
|---|---|
shadcn |
Any shadcn primitive work — canonical composition rules, CLI commands |
shadcn-next-shell |
Repo-local layer on top of shadcn — token discipline, client/server split, motion tokens, cva shape |
next-shell-contributor |
Phase model, branch / commit / PR conventions, verification pipeline, common failure modes |
frontend-design |
Production-grade UI creation |
web-artifacts-builder |
Multi-component React + Tailwind + shadcn scaffolding |
webapp-testing |
Playwright-based UI testing, screenshots, DOM inspection |
theme-factory |
Color + typography theming |
JSDOM doesn't implement everything Radix + shadcn libraries expect. The setup file installs no-op stubs for:
window.matchMedia— next-themes'prefers-color-schemewatcherResizeObserver— Radix Slider, Popover, ScrollAreaIntersectionObserver— embla-carousel-reactElement.prototype.scrollIntoView— cmdk's active-item tracking
Do not call vi.restoreAllMocks() in afterEach — it wipes these
stubs. If a future primitive needs another browser-API stub, add it
here alongside the existing ones.
| Symptom | Likely cause |
|---|---|
pnpm install complains about peerDeps |
Consumer app needs React 19 + Next 15 + Tailwind 4 |
'use client' missing from bundled output |
New top-level subpath not in CLIENT_ENTRIES in tsup.config.ts |
Vitest can't find matchMedia / ResizeObserver / etc. |
Missing shim in tests/setup.ts (or vi.restoreAllMocks() wiped it) |
| Hydration warning in a Next.js consumer | Add suppressHydrationWarning to <html> — next-themes writes data-theme before hydration |
| Lint fails on a hex literal inside a CSS attribute selector | no-raw-colors can't distinguish selector-hex from value-hex; wrap that one className with a targeted /* eslint-disable next-shell/no-raw-colors */ block + a comment explaining (see chart.tsx for the pattern) |
Lint fails on text-white in a destructive variant |
Retheme to text-destructive-foreground; resolves to oklch(0.985 0 0) in both themes |
- The two repo-authored skills (
shadcn-next-shell,next-shell-contributor) - The most recently merged phase PR's description (always paints a detailed picture of the surface it added)
- The upstream
shadcn-ui/uisource at the pinned commit, underapps/v4/registry/new-york-v4/ui/*.tsx - The extraction plan meta-issue #13 for phase boundaries
- Meta-issue · #13
- Phase 1 (tokens) · #2
- Phase 2 (theme) · #3
- Phase 3 (shadcn primitives) · #4
- Phase 4 (app shell) · #5
- Phase 5 (navigation) · #6
- Phase 6 (providers composer) · #7
- Phase 7 (auth) · #8
- Phase 8 (hooks + formatters) · #9
- Phase 9 (docs + Storybook) · #10
- Phase 10 (publishing) · #11
- Phase 11 (example consumer app) · #12