Skip to main content

Overview

Following consistent code guidelines ensures maintainability and readability across the Kitsu codebase. These guidelines are enforced through ESLint and code review.

General Principles

Readability First

Write code that others can easily understand

Consistency

Follow existing patterns in the codebase

Simplicity

Prefer simple solutions over clever ones

Performance

Be mindful of performance implications

Code Style

Line Length

Keep lines under 80 characters when possible:
const result = someFunction(
  parameter1,
  parameter2,
  parameter3
)

ESLint Rules

Respect the configured ESLint rules. Run the linter before committing:
npm run lint
Automatically fix issues when possible:
npm run lint:fix

Object Field Ordering

Always order object fields alphabetically:
const task = {
  assignees: [],
  dueDate: '2024-01-15',
  id: '123',
  name: 'Animation',
  status: 'wip'
}

Vue.js Guidelines

Component Structure

Organize component sections in this order:
<template>
  <!-- Template -->
</template>

<script>
export default {
  name: 'ComponentName',
  
  components: {},
  mixins: [],
  props: {},
  
  data() {
    return {}
  },
  
  computed: {},
  watch: {},
  
  created() {},
  mounted() {},
  beforeUnmount() {},
  
  methods: {}
}
</script>

<style lang="scss" scoped>
/* Styles */
</style>

Component Naming

Use PascalCase for component names:
// Good
export default {
  name: 'TaskList'
}

// Avoid
export default {
  name: 'task-list'
}

Props Definition

Always define props with type and validation:
props: {
  task: {
    type: Object,
    required: true
  },
  editable: {
    type: Boolean,
    default: false
  },
  maxItems: {
    type: Number,
    default: 10,
    validator: value => value > 0
  }
}

Template Syntax

Use shorthand directives:
<button @click="handleClick" :disabled="isLoading">
  {{ buttonText }}
</button>

Vuex Guidelines

State Management

Use Vuex state only when necessary. Vuex creates performance overhead - prefer local component state when possible.
Use Vuex when:
  • State is shared across multiple components
  • State persists across route changes
  • Real-time updates affect multiple views
Use local state when:
  • State is only used in one component
  • State is temporary (e.g., modal visibility)
  • Performance is critical

Always Use Actions for API Calls

// In Vuex action
actions: {
  async loadTasks({ commit }, productionId) {
    const tasks = await tasksApi.getTasks({ productionId })
    commit(LOAD_TASKS_END, tasks)
    return tasks
  }
}

// In component
methods: {
  ...mapActions(['loadTasks']),
  
  async mounted() {
    await this.loadTasks(this.productionId)
  }
}

Mutation Types

Use constants for mutation types:
// mutation-types.js
export const LOAD_TASKS_END = 'LOAD_TASKS_END'
export const UPDATE_TASK = 'UPDATE_TASK'

// In module
import { LOAD_TASKS_END, UPDATE_TASK } from '@/store/mutation-types'

const mutations = {
  [LOAD_TASKS_END](state, tasks) {
    state.tasks = tasks
  },
  [UPDATE_TASK](state, task) {
    const index = state.tasks.findIndex(t => t.id === task.id)
    if (index >= 0) {
      state.tasks[index] = task
    }
  }
}

Getters for Derived State

Don’t store computed values in state:
const getters = {
  tasks: state => Array.from(state.taskMap.values()),
  openTasks: (state, getters) => {
    return getters.tasks.filter(t => t.status !== 'done')
  },
  taskById: state => id => state.taskMap.get(id)
}

JavaScript Guidelines

Use Modern ES6+ Syntax

// Arrow functions
const double = x => x * 2

// Destructuring
const { name, status } = task

// Template literals
const message = `Task ${name} is ${status}`

// Spread operator
const newTask = { ...task, status: 'done' }

// Async/await
async function loadData() {
  const data = await api.getData()
  return data
}

Null Safety

Use optional chaining and nullish coalescing:
// Optional chaining
const assigneeName = task?.assignees?.[0]?.name

// Nullish coalescing
const status = task.status ?? 'unknown'

// Default parameters
function greet(name = 'Guest') {
  return `Hello, ${name}`
}

Array Methods

Prefer declarative array methods:
const openTasks = tasks.filter(t => t.status !== 'done')
const taskNames = tasks.map(t => t.name)
const hasWipTasks = tasks.some(t => t.status === 'wip')
const total = tasks.reduce((sum, t) => sum + t.duration, 0)

Performance Guidelines

Minimize Watchers

Prefer computed properties over watchers:
computed: {
  fullName() {
    return `${this.firstName} ${this.lastName}`
  }
}

Use v-show vs v-if Wisely

<!-- Use v-if for rarely toggled content -->
<modal v-if="isModalOpen">
  <expensive-component />
</modal>

<!-- Use v-show for frequently toggled content -->
<div v-show="isVisible">
  <simple-content />
</div>

Lazy Load Components

For large components, use dynamic imports:
const HeavyComponent = () => import('@/components/HeavyComponent.vue')

export default {
  components: {
    HeavyComponent
  }
}

Testing Guidelines

Write tests for:
  • Vuex getters, actions, and mutations
  • Utility functions
  • Complex component logic
import { describe, it, expect } from 'vitest'
import { sortByName } from '@/lib/sorting'

describe('sortByName', () => {
  it('sorts items alphabetically by name', () => {
    const items = [
      { name: 'Charlie' },
      { name: 'Alice' },
      { name: 'Bob' }
    ]
    const sorted = sortByName(items)
    expect(sorted[0].name).toBe('Alice')
    expect(sorted[2].name).toBe('Charlie')
  })
})

Comments and Documentation

When to Comment

// Calculate weighted average based on task priority
const weightedAverage = tasks.reduce((sum, task) => {
  return sum + (task.duration * task.priority)
}, 0) / totalPriority

// HACK: Workaround for Safari rendering bug
// TODO: Remove when Safari 17 is minimum version
if (isSafari) {
  element.style.transform = 'translateZ(0)'
}

JSDoc for Complex Functions

/**
 * Calculates the weighted average of task durations
 * @param {Array<Object>} tasks - Array of task objects
 * @param {string} weightField - Field name to use for weighting
 * @returns {number} Weighted average duration
 */
function calculateWeightedAverage(tasks, weightField) {
  // Implementation
}

Git Commit Messages

Write clear, descriptive commit messages:
Fix: Correct task sorting when status is null
Feature: Add batch export for assets
Refactor: Simplify task comment rendering
Docs: Update API integration guide

Accessibility

Ensure UI is accessible:
<!-- Use semantic HTML -->
<button @click="submit">Submit</button>
<!-- Not <div @click="submit">Submit</div> -->

<!-- Add labels for form inputs -->
<label for="task-name">Task Name</label>
<input id="task-name" v-model="taskName" />

<!-- Add alt text for images -->
<img :src="thumbnail" :alt="task.name" />

<!-- Use ARIA attributes when needed -->
<div role="dialog" aria-labelledby="modal-title">
  <h2 id="modal-title">{{ title }}</h2>
</div>

Code Review Checklist

Before submitting a PR, verify:
1

Linting

npm run lint passes without errors
2

Tests

npm run test passes, new tests added if needed
3

Code Style

Follows all guidelines in this document
4

Performance

No obvious performance issues introduced
5

Documentation

Complex logic is commented, README updated if needed

Contributing Guide

Ready to contribute? Read the full contributing guide

Build docs developers (and LLMs) love