Claude-skill-registry common-conventions
TMNL codebase conventions for file organization, barrel exports, naming patterns, comments, and module structure. The meta-skill for consistency.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/common-conventions" ~/.claude/skills/majiayu000-claude-skill-registry-common-conventions && rm -rf "$T"
manifest:
skills/data/common-conventions/SKILL.mdsource content
TMNL Common Conventions
Overview
TMNL maintains strict conventions for code organization, naming, and documentation. This skill codifies patterns observed across the codebase to ensure consistency.
Mandatory Conventions (Non-negotiable):
- Schema discipline — All domain types use Effect Schema
- Typography 12px floor — No text smaller than 12px
- Atom-as-State doctrine — No Effect.Ref for React consumers
- Dependency audit — Grep before cutting imports
Pattern 1: Library Module Structure
When: Creating a new library at
src/lib/{feature}/
Standard Directory Layout
src/lib/{feature}/ ├── index.ts # Barrel export (required) ├── types.ts # TypeScript types (UI props, configs) ├── schemas/ # Effect Schema definitions │ └── index.ts ├── services/ # Effect.Service implementations │ ├── index.ts │ └── {Feature}Service.ts ├── atoms/ # effect-atom definitions │ └── index.ts ├── hooks/ # React hooks │ └── use{Feature}.ts ├── components/ # React components (if UI-heavy) │ └── {Component}.tsx ├── machines/ # XState machines (if stateful) │ └── {feature}-machine.ts ├── v1/, v2/ # Version directories (for evolution) └── CLAUDE.{feature}.md # Agent handoff documentation
When to Use Each Directory
| Directory | When to Create |
|---|---|
| Multiple Effect services, or single complex service |
(root) | Single simple service (e.g., ) |
| effect-atom state management |
| Multiple React hooks |
(root) | Single hook |
| Domain types requiring validation |
| UI props, configs, non-validated types |
| XState state machines |
, | Major version evolution (not breaking changes) |
Pattern 2: Barrel File (index.ts) Structure
When: Every
src/lib/{feature}/ directory requires a barrel export.
Standard Format
/** * {Feature} Library * * {Brief description} * * @example * ```tsx * import { Component, useFeature } from '@/lib/{feature}' * ``` */ // ═══════════════════════════════════════════════════════════════════════════ // CORE EXPORTS // ═══════════════════════════════════════════════════════════════════════════ export { MainComponent } from './components/Main' export { FeatureService } from './services' // ═══════════════════════════════════════════════════════════════════════════ // SCHEMAS // ═══════════════════════════════════════════════════════════════════════════ export * from './schemas' // ═══════════════════════════════════════════════════════════════════════════ // ATOMS (effect-atom) // ═══════════════════════════════════════════════════════════════════════════ export { stateAtom, configAtom, operationsAtom, } from './atoms' // ═══════════════════════════════════════════════════════════════════════════ // REACT HOOKS // ═══════════════════════════════════════════════════════════════════════════ export { useFeature, useFeatureState } from './hooks' // ═══════════════════════════════════════════════════════════════════════════ // TYPES // ═══════════════════════════════════════════════════════════════════════════ export type { FeatureConfig, FeatureState } from './types'
Section Separators
Use box-drawing characters for major sections:
// ═══════════════════════════════════════════════════════════════════════════ // SECTION NAME (all caps) // ═══════════════════════════════════════════════════════════════════════════
Use simple dashes for subsections:
// ─────────────────────────────────────────────────────────────────────────── // Subsection name // ───────────────────────────────────────────────────────────────────────────
Version Re-exports
For modules with v1/v2:
// Default exports from v1 export * from './v1' // Explicit v2 namespace export * as v2 from './v2'
Usage:
import { Slider } from '@/lib/slider' // v1 (default) import { Slider } from '@/lib/slider/v2' // v2 (explicit)
Pattern 3: Naming Conventions
Atoms
| Pattern | Example | Usage |
|---|---|---|
| | State atom |
| | Collection atom |
| | Derived/computed atom |
| | Operation atoms (mutations) |
| , | Individual operation |
Services
| Pattern | Example | Usage |
|---|---|---|
| | Effect.Service class |
| | Interface type |
| , | Strategy pattern |
Hooks
| Pattern | Example | Usage |
|---|---|---|
| | Main feature hook |
| | Read-only value hook |
| | State + updater |
| | Operations only |
| | Atom-specific |
Components
| Pattern | Example | Usage |
|---|---|---|
| | Component file |
| | Props interface |
| | Context type |
| | Context provider |
Machines
| Pattern | Example | Usage |
|---|---|---|
| | XState machine |
| | Type alias |
| | ActorRef type |
Pattern 4: Comment Styles
File Header (JSDoc)
/** * {Module Name} — {Brief description} * * {Longer description if needed} * * @example * ```tsx * // Usage example * ``` */
Architectural Notes
For non-obvious design decisions:
// ARCHITECTURAL NOTE: // CommandProvider lives here (commands/), not in minibuffer/. // Minibuffer is a generic prompt engine. Commands USES minibuffer, not the reverse.
TODO/FIXME
// TODO(prime): Migrate to v2 API after EPOCH-0003 // FIXME: This breaks when input is empty // HACK: Workaround for WSLg rendering bug
Pattern 5: Types vs Schemas
When to Use types.ts
types.ts- React component props
- UI configuration
- Local function parameters
- Types that don't need runtime validation
// src/lib/slider/v1/types.ts export interface SliderProps { value: number onChange: (value: number) => void min?: number max?: number } export interface SliderConfig { min: number max: number step: number defaultValue: number }
When to Use schemas/
schemas/- Domain types requiring validation
- Event payloads
- API responses
- Discriminated unions with pattern matching
- EventLog integration
// src/lib/overlays/schemas/core.ts import { Schema } from 'effect' export const OverlayId = Schema.String.pipe( Schema.brand('OverlayId') ) export type OverlayId = typeof OverlayId.Type export const OverlayOpened = Schema.TaggedStruct('OverlayOpened', { id: OverlayId, timestamp: Schema.DateFromSelf, })
Pattern 6: Test File Organization
Location Patterns
| Pattern | Example | When |
|---|---|---|
directory | | Multiple test files |
suffix | | Single test file |
suffix | | Bun-specific |
Test Naming
// src/lib/feature/__tests__/feature.test.ts describe('FeatureService', () => { describe('operation', () => { it('does X when Y', () => { ... }) it('fails with Z when W', () => { ... }) }) })
Effect Service Tests
Use
@effect/vitest with it.effect():
import { describe, it } from '@effect/vitest' describe('MyService', () => { it.effect('returns data', () => Effect.gen(function* () { const service = yield* MyService const result = yield* service.getData() expect(result).toBeDefined() }).pipe(Effect.provide(MyService.Default)) ) })
Atom Tests
Use
Registry.make():
it('atom updates', () => { const r = Registry.make() expect(r.get(counterAtom)).toBe(0) r.set(counterAtom, 1) expect(r.get(counterAtom)).toBe(1) })
Pattern 7: Documentation Files
Per-Module Documentation
| File | Purpose | Audience |
|---|---|---|
| Agent handoff | AI assistants |
| User-facing docs | Developers |
| Deep design analysis | Architects |
| Agent-specific notes | AI assistants |
CLAUDE.{feature}.md Structure
# {Feature} — Claude Context ## Overview {Brief description} ## Key Files - `service.ts` — Main service implementation - `atoms/index.ts` — Reactive state ## Patterns Used - Effect.Service<>() for DI - Atom.runtime() for state ## Gotchas - Don't use X because Y - Always Z before W ## Related Skills - effect-patterns - effect-atom-integration
Pattern 8: Service File Patterns
Single Service (Root Level)
// src/lib/commands/service.ts /** * TMNL Commands — Effect Service */ // ─────────────────────────────────────────────────────────────────────────── // Atoms (Reactive State) // ─────────────────────────────────────────────────────────────────────────── export const commandsAtom = Atom.make<ReadonlyMap<string, Command>>(new Map()) // ─────────────────────────────────────────────────────────────────────────── // Service Implementation // ─────────────────────────────────────────────────────────────────────────── export class CommandService extends Effect.Service<CommandService>()('app/CommandService', { effect: Effect.gen(function* () { // ... }), }) {}
Multiple Services (Directory)
src/lib/overlays/services/ ├── index.ts # Re-exports all services ├── OverlayRegistry.ts # Registry service ├── PortHub.ts # Port management └── EventDispatcher.ts # Event dispatch
Anti-Patterns
1. Barrel File Without Sections
// WRONG — Unorganized exports export * from './components' export * from './hooks' export * from './types' export * from './atoms' // CORRECT — Sectioned exports // ═══════════════════════════════════════════════════════════════════════════ // COMPONENTS // ═══════════════════════════════════════════════════════════════════════════ export { Slider } from './components/Slider' // ═══════════════════════════════════════════════════════════════════════════ // HOOKS // ═══════════════════════════════════════════════════════════════════════════ export { useSlider } from './hooks/useSlider'
2. Types in Wrong Location
// WRONG — Domain type without Schema // src/lib/feature/types.ts export interface UserEvent { _tag: 'UserCreated' id: string name: string } // CORRECT — Domain type with Schema // src/lib/feature/schemas/events.ts export const UserCreated = Schema.TaggedStruct('UserCreated', { id: Schema.String, name: Schema.NonEmptyString, })
3. Inconsistent Naming
// WRONG — Mixed naming styles export const user_state_atom = Atom.make(...) // snake_case export const UseUserHook = () => { ... } // PascalCase for hook export const userservice = Effect.Service() // no separator // CORRECT — Consistent camelCase with type suffix export const userStateAtom = Atom.make(...) export const useUser = () => { ... } export const UserService = Effect.Service()
4. Missing JSDoc on Exports
// WRONG — No documentation export const searchOp = runtimeAtom.fn<string>()(...) // CORRECT — JSDoc on public exports /** * Search operation. Triggers search with given query. * * @param query - Search query string * @returns Effect that updates resultsAtom */ export const searchOp = runtimeAtom.fn<string>()(...)
Checklist: New Module Creation
When creating
src/lib/{feature}/:
- Create
with JSDoc header and sectioned exportsindex.ts - Create
for non-domain types (UI props, configs)types.ts - Create
for domain types using Effect Schemaschemas/ - Create
(orservices/
) with Effect.Service<>()service.ts - Create
with Atom definitionsatoms/index.ts - Create
with React hookshooks/ - Use section comments:
// ═══════... - Add ARCHITECTURAL NOTE for non-obvious decisions
- Create
for agent handoffCLAUDE.{feature}.md - Prefer
overAtom.make<T>()
for ReactEffect.Ref<T> - Test with
for services,@effect/vitest
for atomsRegistry.make()
Canonical Examples
| Convention | Best Example | File |
|---|---|---|
| Barrel file | Overlays | |
| Service pattern | Commands | |
| Atom organization | Slider | |
| Schema usage | Commands | |
| Hook naming | Commands | |
| Version strategy | Slider | |
| Test organization | STX | |
Integration Points
- tmnl-file-organization — Directory structure details
- effect-patterns — Service definition patterns
- effect-atom-integration — Atom patterns
- tmnl-typography-discipline — Typography rules
- effect-schema-mastery — Schema patterns