Vue 3 code generation for the Toot Scheduler project. Use this skill whenever writing or modifying Vue components, composables, Pinia stores, TypeScript types, or any Vue-related code in this project. Trigger when the user asks to create a component, add a feature, fix a Vue bug, write a composable, extend a store, or refactor existing Vue code. This skill ensures all generated code follows Vue 3 Composition API best practices and the conventions already in place in this codebase.
This project is a Vue 3 + TypeScript + Vite application for scheduling Mastodon posts.
Always generate code that feels native to this codebase — consistent with existing files in src/.
<script setup lang="ts"> — never Options API, never <script> without setupimport { ref, computed, watch, onMounted } from 'vue'<script setup>, before imports<script setup lang="ts">
// Constants first
const MAX_POLL_OPTIONS = 4;
const DEFAULT_VISIBILITY = 'public';
import { ref, computed } from 'vue';
import { useAuthStore } from '../stores/auth';
defineProps<{}>() with TypeScript generics — never the object syntaxdefineEmits<{}>() with typed signatures for all eventsv-model:propName pattern with update:propName eventsdefineProps<{
scheduledDate: string;
visibility: ScheduledToot['visibility'];
isEditing: boolean;
}>();
const emit = defineEmits<{
(e: 'update:scheduledDate', value: string): void;
(e: 'cancel'): void;
}>();
ref() for primitive values, computed() for derived statewatch() with { immediate: true } when you need to react on mount toosrc/composables/ and name them use<Feature>.tsuseMastodonApi.ts as the reference/**
* Brief description of what this composable does.
* @returns {Object} Descriptive return value
*/
export function useMyFeature() {
// ...
return { doThing, isLoading };
}
src/stores/auth.tsdefineStore('store-name', () => { ... })ref(), actions as functions, computed as computed()localStorage where appropriate (see auth.ts for the pattern)src/types/mastodon.ts — add new types thereinterface for object shapes, type for unions/aliasesany — if needed temporarily, add a comment explaining whyimport type { ... } for type-only importssrc/
├── components/ # Vue SFCs — organized by feature subfolder when related
│ ├── Auth/ # Auth-specific components
│ ├── Toot/ # Toot-specific components
│ ├── Modals/ # Modal components
│ └── icons/ # SVG icon components
├── composables/ # use*.ts composables
├── stores/ # Pinia stores
├── types/ # TypeScript interfaces and types
├── utils/ # Pure utility functions (no Vue, no Pinia)
└── router/ # Vue Router config
Respect this structure when creating new files. If a component is specific to a feature, create or use a subfolder.
v-if / v-else for conditional rendering (not v-show unless toggling is very frequent)v-for with :key always — prefer stable IDs over array indexes@submit.prevent on forms:value + @input when dealing with typed emits (see ControlsBar.vue)<input
:value="scheduledDate"
@input="emit('update:scheduledDate', ($event.target as HTMLInputElement).value)"
/>
try/catch blockshandleApiError utility from src/utils/error.tsref<string>('error') and display with <p v-if="error" class="error">finally to reset loading statesasync function doSomething() {
try {
error.value = '';
isLoading.value = true;
// ...
} catch (err) {
error.value = err instanceof Error ? err.message : 'Something went wrong.';
} finally {
isLoading.value = false;
}
}
data(), methods:, computed:)defineProps with object syntaxref bindings insteadany without a commentvue without destructuring (import Vue from 'vue')