Skip to content
common-conventions logo

TMNL Common Conventions

common-conventions

TMNL codebase conventions for file organization, barrel exports, naming patterns, comments, and module structure. The meta-skill for consistency.

SKILL.md

Full skill instructions

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):

  1. Schema discipline — All domain types use Effect Schema
  2. Typography 12px floor — No text smaller than 12px
  3. Atom-as-State doctrine — No Effect.Ref for React consumers
  4. 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

DirectoryWhen to Create
services/Multiple Effect services, or single complex service
service.ts (root)Single simple service (e.g., src/​lib/​commands/​service.ts)
atoms/effect-atom state management
hooks/Multiple React hooks
use{Feature}.tsx (root)Single hook
schemas/Domain types requiring validation
types.tsUI props, configs, non-validated types
machines/XState state machines
v1/, v2/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

PatternExampleUsage
{noun}AtomresultsAtomState atom
{noun}sAtomlayersAtomCollection atom
{verb}AtomcomputedAtomDerived/​computed atom
{feature}OpsAtomlayerOpsAtomOperation atoms (mutations)
{verb}OpsearchOp, clearOpIndividual operation

Services

PatternExampleUsage
{Feature}ServiceDataManagerServiceEffect.Service class
{Feature}ServiceShapeChannelServiceShapeInterface type
{Behavior}BehaviorLinearBehavior, DecibelBehaviorStrategy pattern

Hooks

PatternExampleUsage
use{Feature}useSliderMain feature hook
use{Feature}ValueuseSliderValueRead-only value hook
use{Feature}StateuseMinibufferStateState + updater
use{Feature}OpsuseOverlayOpsOperations only
useAtom{Feature}useAtomValueAtom-specific

Components

PatternExampleUsage
{Name}.tsxSlider.tsxComponent file
{Name}PropsSliderPropsProps interface
{Name}ContextDataGridContextContext type
{Name}ProviderCommandProviderContext provider

Machines

PatternExampleUsage
{feature}MachineminibufferMachineXState machine
{Feature}MachineMinibufferMachineType alias
{Feature}ActorMinibufferActorActorRef 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

  • 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/

  • 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

PatternExampleWhen
__tests__/ directorysrc/​lib/​stx/​__tests__/Multiple test files
.test.ts suffixservice.test.tsSingle test file
.bun.test.ts suffixeventlog-integration.bun.test.tsBun-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

FilePurposeAudience
CLAUDE.{feature}.mdAgent handoffAI assistants
README.mdUser-facing docsDevelopers
ARCHITECTURE.mdDeep design analysisArchitects
AGENTS.{feature}.mdAgent-specific notesAI 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 index.ts with JSDoc header and sectioned exports
  • Create types.ts for non-domain types (UI props, configs)
  • Create schemas/ for domain types using Effect Schema
  • Create services/ (or service.ts) with Effect.Service<>()
  • Create atoms/​index.ts with Atom definitions
  • Create hooks/ with React hooks
  • Use section comments: // ═══════...
  • Add ARCHITECTURAL NOTE for non-obvious decisions
  • Create CLAUDE.{feature}.md for agent handoff
  • Prefer Atom.make<T>() over Effect.Ref<T> for React
  • Test with @effect/​vitest for services, Registry.make() for atoms

Canonical Examples

ConventionBest ExampleFile
Barrel fileOverlayssrc/​lib/​overlays/​index.ts
Service patternCommandssrc/​lib/​commands/​service.ts
Atom organizationSlidersrc/​lib/​slider/​v1/​atoms/​index.ts
Schema usageCommandssrc/​lib/​commands/​types.ts
Hook namingCommandssrc/​lib/​commands/​useCommandWire.tsx
Version strategySlidersrc/​lib/​slider/​index.ts
Test organizationSTXsrc/​lib/​stx/​__tests__/

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

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View