Unnamed Skill

Vue 3 patterns and best practices for this workout tracker: feature module architecture, createGlobalState() singleton state (not Pinia), defineModel two-way binding, and component gotchas. Use when creating/refactoring components, features, composables, managing shared state, or debugging reactivity issues. Triggers include "add component", "create feature", "refactor", "composable", "v-model", "defineModel", "global state", "createGlobalState", "singleton", "reactive", "two-way binding", "feature structure", "reka-ui", "shadcn-vue".

$ Installieren

git clone https://github.com/alexanderop/workoutTracker /tmp/workoutTracker && cp -r /tmp/workoutTracker/.claude/skills/vue-patterns ~/.claude/skills/workoutTracker

// tip: Run this command in your terminal to install the skill


name: vue-patterns description: Vue 3 patterns and best practices for this workout tracker: feature module architecture, createGlobalState() singleton state (not Pinia), defineModel two-way binding, and component gotchas. Use when creating/refactoring components, features, composables, managing shared state, or debugging reactivity issues. Triggers include "add component", "create feature", "refactor", "composable", "v-model", "defineModel", "global state", "createGlobalState", "singleton", "reactive", "two-way binding", "feature structure", "reka-ui", "shadcn-vue".

Vue Patterns

Feature Module Architecture

Bulletproof architecture: Self-contained domain modules. Each feature owns UI components, composables, and business logic.

FeaturePurposeEntry Point
workout/Active workout state & executioncomposables/useWorkout.ts
exercises/Exercise library CRUDcomposables/useExerciseForm.ts
templates/Workout template managementcomposables/useTemplateForm.ts
benchmarks/Benchmark workout trackingcomposables/useBenchmark.ts
settings/App settings & preferencescomposables/useLanguageSettings.ts
timers/Standalone timer UIcomponents/TimerCard.vue
log-past-workout/Retroactive workout entrycomposables/usePastWorkout.ts

Structure:

src/features/[feature]/
├── components/      # Feature-specific Vue components
├── composables/     # Feature-specific composables
├── lib/             # Feature utilities
└── state/           # Singleton state (if needed)

Singleton State Pattern

Use createGlobalState() from VueUse for shared state (NOT Pinia):

// src/stores/workoutState.ts
import { createGlobalState } from '@vueuse/core'

export const useWorkoutState = createGlobalState(() => {
  const workout = ref<Workout | null>(null)
  return { workout }
})

// Feature composable provides singleton ref
// src/features/workout/composables/useWorkout.ts
import { getWorkoutRef } from '@/stores/workoutState'

const workout = getWorkoutRef() // Shared singleton ref

export function useWorkout() {
  return {
    workout,           // Ref<Workout> - shared across all components
    selectBlock,
    // ...
  }
}

Two-Way Binding

Always use defineModel for v-model:

// Props with v-model
const open = defineModel<boolean>('open')
const value = defineModel<string>() // default model

Gotchas

1. Wrap Destructured Props in Getters for Watchers

// BAD - breaks reactivity
const { count } = defineProps<{ count: number }>()
watch(count, ...)

// GOOD - wrap in getter
watch(() => count, ...)

2. shadcn-vue Uses reka-ui (Not Radix)

<!-- BAD - v-model:checked doesn't exist -->
<Switch v-model:checked="enabled" />

<!-- GOOD - use v-model -->
<Switch v-model="enabled" />

Check reka-ui docs for correct API.

Quick Find

rg -n "export function use" src/features/workout/composables  # Feature composables
find src/features/workout/components -name "*.vue"            # Feature components
rg -n "kind: '(strength|amrap|emom|tabata|fortime)'" src/     # Block types