23 KiB
Claude Code Guidelines for Atlas CMMS
This document provides guidelines for Claude Code when working on this Vue.js 3 CMMS application.
⚠️ CRITICAL: STRICT ENFORCEMENT OF PATTERNS
NEVER REINVENT THE WHEEL - ALWAYS COPY EXISTING WORKING IMPLEMENTATIONS
Before writing ANY new code:
- SEARCH for existing similar implementations in the codebase
- COPY the exact pattern from working examples
- DO NOT modify or "improve" existing patterns
- DO NOT mix business logic with view components
Token Conservation Rules:
- Violating separation of concerns = wasted tokens on rewrites
- Reinventing existing patterns = wasted tokens on rewrites
- Not following established UI patterns = wasted tokens on rewrites
When implementing any new feature:
- Find the most similar existing feature (vendors, assets, work orders, etc.)
- Copy the EXACT file structure and implementation
- Only change the entity-specific details (names, fields, etc.)
- NEVER change the architectural patterns
If you violate these rules, you are wasting the user's tokens and usage limits.
Architecture Guidelines
Separation of Concerns
- Always create composables for business logic - Use the
use*pattern (e.g.,useQRCode,useAssetManagement) - Components should be thin - Components handle UI rendering, user interactions, and state presentation only
- Extract business logic to composables - Data processing, API calls, complex calculations belong in composables
- Follow Vue 3 Composition API best practices - Use reactive refs, computed properties, and watchers appropriately
Code Organization Patterns
Composables Structure
src/composables/
├── assets/
│ ├── useAssetManagement.js
│ ├── useQRCode.js
│ └── useAssetAnalytics.js
├── workorders/
└── shared/
Component Structure
<script setup>
// 1. Imports (composables, components, utilities)
// 2. Props and emits definitions
// 3. Composable usage (business logic)
// 4. UI-specific reactive state
// 5. UI event handlers
// 6. Lifecycle hooks
</script>
What Goes Where
Composables Should Handle:
- Business logic and calculations
- Data transformation and validation
- Local reactive state (component-specific refs, computed)
- Complex operations (QR generation, file processing, etc.)
- Reusable functionality across components
- Orchestrating between repositories and stores
Components Should Handle:
- Template rendering and UI structure
- User interaction events (clicks, inputs)
- UI-specific state (modals, loading states)
- Props validation and emitting events
- Basic data formatting for display
- Component lifecycle management
Repositories Should Handle:
- All API calls and HTTP requests
- Data fetching and posting to backend
- Request/response transformation
- API error handling and retry logic
- Authentication headers and request configuration
- Endpoint management and URL construction
Pinia Stores Should Handle:
- Global application state
- Cross-component data sharing
- Persistent state management
- State mutations and actions
- Computed state (getters)
- State synchronization with backend data
Test-Driven Development (TDD) Process
ALWAYS follow TDD - Write tests FIRST, then implementation:
- Write the test - Define expected behavior with failing tests
- Write minimal code - Make the test pass with simplest implementation
- Refactor - Improve code while keeping tests green
- Repeat - Continue with next test case
Implementation Checklist
Before implementing new features, ask:
- Have you written the test first? → TDD is mandatory
- Is there business logic? → Create a composable (with tests)
- Will this be reused? → Create a composable (with tests)
- Does it involve data processing? → Use a composable (with tests)
- Is it purely UI-related? → Keep in component (with tests)
- Does it need API calls? → Use a repository (with tests)
- Does it manage global state? → Use a Pinia store (with tests)
- Does it need cross-component data sharing? → Use a Pinia store (with tests)
- Is it local component state? → Use reactive refs in composable or component (with tests)
Examples
✅ Good: Proper Separation of Concerns
<script setup>
import { useAssetManagement } from '@/composables/assets/useAssetManagement'
import { useAssetStore } from '@/stores/assets'
// Store for global state
const assetStore = useAssetStore()
// Composable for business logic
const { processAssetData, validateAsset } = useAssetManagement()
// Local UI state
const showModal = ref(false)
const handleSaveAsset = async () => {
// Business logic in composable
const processedData = processAssetData(formData.value)
// Validation in composable
if (!validateAsset(processedData)) return
// API call through store (which uses repository)
await assetStore.createAsset(processedData)
// UI logic in component
showModal.value = false
}
</script>
✅ Good: TDD Example - Write Test First
// __tests__/useQRCode.test.js
import { describe, it, expect, vi } from 'vitest'
import { useQRCode } from '@/composables/assets/useQRCode'
describe('useQRCode', () => {
it('should generate QR code with valid asset data', async () => {
const { generateQRCode } = useQRCode()
const assetData = { name: 'Test Asset', assetNumber: 'AST-001' }
const result = await generateQRCode(assetData)
expect(result.qrCode).toContain('data:image/png;base64')
expect(result.data.name).toBe('Test Asset')
})
it('should throw error for invalid asset data', async () => {
const { generateQRCode } = useQRCode()
const invalidData = {}
await expect(generateQRCode(invalidData)).rejects.toThrow()
})
})
✅ Good: Repository Pattern with JSDoc
// AssetRepository.js
/**
* Repository for handling asset-related API operations
*/
export class AssetRepository extends BaseRepository {
/**
* Create a new asset
* @param {Object} assetData - The asset data to create
* @param {string} assetData.name - Asset name
* @param {string} assetData.category - Asset category
* @returns {Promise<Object>} Created asset data
*/
async createAsset(assetData) {
return this.post('/assets', assetData)
}
/**
* Update an existing asset
* @param {string} id - Asset ID to update
* @param {Object} assetData - Updated asset data
* @returns {Promise<Object>} Updated asset data
*/
async updateAsset(id, assetData) {
return this.put(`/assets/${id}`, assetData)
}
}
✅ Good: Pinia Store Pattern with JSDoc
// stores/assets.js
import { defineStore } from 'pinia'
import { AssetRepository } from '@/services/repositories/AssetRepository'
/**
* Asset store for managing global asset state
*/
export const useAssetStore = defineStore('assets', {
state: () => ({
/** @type {Array<Object>} */
assets: [],
/** @type {Object|null} */
currentAsset: null,
/** @type {boolean} */
loading: false,
/** @type {string|null} */
error: null
}),
getters: {
/**
* Get asset by ID
* @param {Object} state
* @returns {function(string): Object|undefined}
*/
getAssetById: (state) => (id) => {
return state.assets.find(asset => asset.id === id)
}
},
actions: {
/**
* Create a new asset
* @param {Object} assetData - Asset data to create
* @returns {Promise<Object>} Created asset
*/
async createAsset(assetData) {
this.loading = true
this.error = null
try {
const newAsset = await AssetRepository.createAsset(assetData)
this.assets.push(newAsset)
return newAsset
} catch (error) {
this.error = error.message
throw error
} finally {
this.loading = false
}
}
}
})
✅ Good: Composable with JSDoc and TDD
// composables/assets/useAssetManagement.js
import { ref, computed } from 'vue'
/**
* Composable for asset management business logic
* @returns {Object} Asset management methods and state
*/
export function useAssetManagement() {
/** @type {import('vue').Ref<string|null>} */
const error = ref(null)
/**
* Validate asset data
* @param {Object} assetData - Asset data to validate
* @param {string} assetData.name - Asset name
* @param {string} assetData.category - Asset category
* @returns {boolean} Whether asset data is valid
*/
const validateAsset = (assetData) => {
error.value = null
if (!assetData.name || assetData.name.trim().length === 0) {
error.value = 'Asset name is required'
return false
}
if (!assetData.category) {
error.value = 'Asset category is required'
return false
}
return true
}
/**
* Process asset data before saving
* @param {Object} rawData - Raw form data
* @returns {Object} Processed asset data
*/
const processAssetData = (rawData) => {
return {
...rawData,
name: rawData.name?.trim(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
}
return {
error: computed(() => error.value),
validateAsset,
processAssetData
}
}
❌ Bad: No Tests Written First
// Don't write implementation without tests first
export function useAssetManagement() {
// Implementation without tests = bad practice
const validateAsset = (assetData) => {
// Complex logic without tests to verify behavior
}
}
❌ Bad: API Calls in Components
<script setup>
// Don't make API calls directly in components
const saveAsset = async () => {
const response = await fetch('/api/assets', {
method: 'POST',
body: JSON.stringify(assetData)
})
// ... handling response
}
</script>
❌ Bad: No JSDoc Documentation
// Don't skip documentation
export function useAssetManagement() {
const validateAsset = (assetData) => {
// No documentation about parameters or return values
}
}
Vue.js 3 + Pinia Specific Guidelines
- Use JavaScript consistently - No TypeScript, pure JavaScript with JSDoc for type hints
- Use Composition API consistently
- Prefer
<script setup>syntax - Use JSDoc comments for better type safety and documentation
- Follow Vue 3 reactivity patterns (avoid Vue 2 patterns)
- Use
computedfor derived state,watchfor side effects - Implement proper error handling in composables and stores
- Use Pinia stores for global state, not reactive() in composables
- Repository pattern for all API interactions
File Naming Conventions
- Composables:
use*.js(camelCase) - Components:
PascalCase.vue - Views:
*View.vue - Repositories:
*Repository.js(PascalCase) - Stores:
camelCase.js(lowercase with camelCase export) - Utilities:
camelCase.js
Error Handling
- Repositories should handle HTTP errors and throw meaningful exceptions
- Stores should catch repository errors and expose error state
- Composables should handle business logic errors via reactive refs
- Components should display error states appropriately from stores/composables
- Always provide user-friendly error messages
- Log detailed errors for debugging
Data Flow Architecture
Component → Composable → Store → Repository → API
↑ ↑ ↑ ↑
UI Logic Business Global HTTP
Logic State Calls
Testing Framework & TDD Requirements
Test Framework Setup
- Use Vitest for unit testing (not Jest)
- Use @vue/test-utils for component testing
- Use MSW (Mock Service Worker) for API mocking in integration tests
- Test files should be in
__tests__directories or*.test.jsfiles
TDD Implementation Rules
MANDATORY: Always write tests before implementation:
- Red Phase: Write a failing test that describes the expected behavior
- Green Phase: Write minimal code to make the test pass
- Refactor Phase: Improve code quality while keeping tests green
Test Structure by Layer
Repository Tests (with MSW)
// __tests__/AssetRepository.test.js
import { describe, it, expect, beforeEach } from 'vitest'
import { setupServer } from 'msw/node'
import { rest } from 'msw'
import { AssetRepository } from '@/services/repositories/AssetRepository'
const server = setupServer(
rest.post('/api/assets', (req, res, ctx) => {
return res(ctx.json({ id: '1', name: 'Test Asset' }))
})
)
describe('AssetRepository', () => {
beforeEach(() => server.listen())
it('should create asset via API', async () => {
const repository = new AssetRepository()
const assetData = { name: 'Test Asset' }
const result = await repository.createAsset(assetData)
expect(result.id).toBe('1')
expect(result.name).toBe('Test Asset')
})
})
Store Tests (with mocked repositories)
// __tests__/assets.store.test.js
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { useAssetStore } from '@/stores/assets'
vi.mock('@/services/repositories/AssetRepository', () => ({
AssetRepository: {
createAsset: vi.fn()
}
}))
describe('Asset Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
})
it('should create asset and update state', async () => {
const store = useAssetStore()
const mockAsset = { id: '1', name: 'Test Asset' }
AssetRepository.createAsset.mockResolvedValue(mockAsset)
await store.createAsset({ name: 'Test Asset' })
expect(store.assets).toContain(mockAsset)
expect(store.loading).toBe(false)
})
})
Composable Tests (with mocked stores)
// __tests__/useAssetManagement.test.js
import { describe, it, expect } from 'vitest'
import { useAssetManagement } from '@/composables/assets/useAssetManagement'
describe('useAssetManagement', () => {
it('should validate asset with required fields', () => {
const { validateAsset } = useAssetManagement()
const validAsset = { name: 'Test Asset', category: 'Equipment' }
const result = validateAsset(validAsset)
expect(result).toBe(true)
})
it('should reject asset without name', () => {
const { validateAsset, error } = useAssetManagement()
const invalidAsset = { category: 'Equipment' }
const result = validateAsset(invalidAsset)
expect(result).toBe(false)
expect(error.value).toBe('Asset name is required')
})
})
Component Tests (with mocked composables)
// __tests__/AssetForm.test.js
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import AssetForm from '@/components/assets/AssetForm.vue'
vi.mock('@/composables/assets/useAssetManagement', () => ({
useAssetManagement: () => ({
validateAsset: vi.fn(() => true),
error: { value: null }
})
}))
describe('AssetForm', () => {
it('should render form fields', () => {
const wrapper = mount(AssetForm)
expect(wrapper.find('input[name="name"]').exists()).toBe(true)
expect(wrapper.find('select[name="category"]').exists()).toBe(true)
})
})
TDD Workflow Summary
For every new feature, follow this exact order:
- Write failing test describing expected behavior
- Run test to confirm it fails (Red phase)
- Write minimal implementation to make test pass (Green phase)
- Run test to confirm it passes
- Refactor code while keeping tests green
- Repeat for next requirement
Mandatory Requirements Checklist
✅ JavaScript only - No TypeScript
✅ JSDoc documentation - All functions and complex types
✅ TDD first - Tests written before implementation
✅ Repository pattern - All API calls go through repositories
✅ Pinia stores - All global state management
✅ Composables - All business logic
✅ Vitest + MSW - Testing frameworks
Remember:
- Tests first → MANDATORY for all code
- JavaScript + JSDoc → No TypeScript allowed
- API calls → Always use repositories with MSW tests
- Global state → Always use Pinia stores with mocked repository tests
- Business logic → Always use composables with unit tests
- UI logic → Keep in components with mocked composable tests
Data Flow: Component → Composable → Store → Repository → API
Test Flow: Component Tests → Composable Tests → Store Tests → Repository Tests
UI/UX Consistency Guidelines
Modal Forms Pattern (MANDATORY)
All create and edit forms MUST use the modal pattern established in AssetsListView.vue:
- ✅ Modal-based forms - No separate pages for create/edit operations
- ✅ Auto-save functionality - Implemented using localStorage with user interaction tracking
- ✅ Tab-based organization - Multi-step forms organized in tabs (Basic, Details, etc.)
- ✅ Query parameter navigation - Use
?edit=123or?create=true&assetId=456patterns - ✅ Form restoration - Show notifications for unsaved data with restore/dismiss options
- ✅ Consistent actions - Edit buttons navigate with query params to open modals
Detail View Pattern (MANDATORY)
All detail views MUST follow the structure established in AssetDetailView.vue:
<template>
<FPLayout>
<template #header>
<FPPageHeader
:title="entity.name"
:description="entity.description"
:breadcrumbs="breadcrumbsArray"
>
<template #actions>
<FPButton variant="secondary" @click="editEntity">Edit</FPButton>
<FPButton variant="primary" @click="createRelatedEntity">Create Related</FPButton>
</template>
</FPPageHeader>
</template>
<div class="px-6 py-8">
<!-- Loading, Content, Error states with v-if/v-else-if/v-else -->
</div>
</FPLayout>
</template>
Required elements:
- ✅ FPLayout with header slot - Consistent layout structure
- ✅ FPPageHeader with actions - Title, description, breadcrumbs, action buttons
- ✅ Proper spacing -
px-6 py-8for content padding - ✅ Three-state rendering - Loading, content, error states
- ✅ Action buttons - Edit and create related entity buttons in header
List View Pattern (MANDATORY)
All listing pages MUST follow the structure established in WorkOrdersListView.vue:
<template>
<FPLayout>
<template #header>
<FPPageHeader title="Entities" description="Description">
<template #actions>
<FPButton @click="refreshData">Refresh</FPButton>
<FPButton variant="primary" @click="createEntity">Create Entity</FPButton>
</template>
<template #stats>
<FPStats :stats="entityStats" />
</template>
<template #tabs>
<FPTabs v-model="activeTab" :tabs="statusTabs" />
</template>
</FPPageHeader>
</template>
<div class="p-6">
<!-- Filters -->
<!-- FPTable with actions -->
<!-- Create/Edit Modal -->
</div>
</FPLayout>
</template>
Required elements:
- ✅ Header with stats and tabs - Statistics row and filter tabs
- ✅ Filter controls - Search, selects, date pickers above table
- ✅ FPTable component - Consistent table with actions column
- ✅ Inline actions - Edit, View, Delete buttons in table rows
- ✅ Modal integration - Create/edit modals in same component
Navigation Patterns (MANDATORY)
Edit Actions:
const editEntity = (id) => {
router.push(`/entities?edit=${id}`)
}
Create Related Actions:
const createRelatedEntity = (parentId) => {
router.push(`/related-entities?create=true&parentId=${parentId}`)
}
Query Parameter Handling:
// In onMounted of list views
const editEntityId = route.query.edit
if (editEntityId) {
setTimeout(() => {
editEntity(parseInt(editEntityId))
router.replace({ path: '/entities' })
}, 500)
}
const shouldCreate = route.query.create === 'true'
if (shouldCreate) {
const parentId = route.query.parentId
setTimeout(() => {
if (parentId) {
entityForm.parentId = parseInt(parentId)
}
createEntityAction()
router.replace({ path: '/entities' })
}, 500)
}
Form Structure Requirements
Modal Configuration:
- ✅ Size:
size="xl"for complex forms - ✅ Auto-save: localStorage with user interaction tracking
- ✅ Restore notifications: Show unsaved data restore options
- ✅ Tab navigation: Multi-step forms with next/previous buttons
- ✅ Validation: Real-time validation with error display
Form Tabs Pattern:
const formTabs = [
{ key: 'basic', label: 'Basic Information', icon: 'info' },
{ key: 'details', label: 'Details', icon: 'document' },
{ key: 'advanced', label: 'Advanced', icon: 'cog' }
]
Implementation Checklist
For every new entity (Users, Locations, Parts, etc.):
List View (EntitiesListView.vue):
- Uses FPLayout with header slot
- Implements FPPageHeader with stats and tabs
- Has filter controls above FPTable
- Contains create/edit modal in same component
- Handles query parameters for edit/create
- Uses consistent action buttons (Edit, View, Delete)
Detail View (EntityDetailView.vue):
- Uses FPLayout with header slot
- Implements FPPageHeader with breadcrumbs and actions
- Has proper content spacing (px-6 py-8)
- Implements three-state rendering (loading/content/error)
- Edit button uses query parameter navigation
Modal Forms:
- Size="xl" for complex forms
- Tab-based organization for multi-step forms
- Auto-save with localStorage integration
- Form restoration notifications
- Real-time validation and error handling
- Consistent button placement and styling
Examples Directory Structure
views/
├── entities/
│ ├── EntitiesView.vue # Parent route component
│ ├── EntitiesListView.vue # List with modal (primary pattern)
│ └── EntityDetailView.vue # Detail view with header actions
Anti-Patterns (AVOID)
❌ Separate create/edit pages - Use modals instead ❌ No auto-save - All forms must have auto-save ❌ Inconsistent navigation - Always use query parameters ❌ Missing breadcrumbs - Detail views must have navigation context ❌ No loading states - Always implement three-state rendering ❌ Inline edit forms - Use modals for consistency
Remember:
- All forms → Modal-based with auto-save and tabs
- All detail views → FPLayout with header slot and actions
- All list views → Stats, tabs, filters, and integrated modals
- All navigation → Query parameter based for edit/create actions
These patterns ensure consistency, better UX, and maintainable code across the entire application.