Variant Systems

Vue Vibe Code Cleanup

Your AI mixed Options and Composition API in the same project. Half your reactivity is broken. We'll sort it out.

At Variant Systems, we pair the right technology with the right approach to ship products that work.

Why this combination

  • AI randomly switches between Options API and Composition API in the same codebase
  • Reactive state gets unwrapped accidentally, breaking change detection
  • Components grow to 400+ lines because AI doesn't extract composables
  • Pinia stores hold everything including local UI state that doesn't belong there

Reactivity Pitfalls AI Assistants Miss

AI can’t pick an API style and stick with it. One component uses Options API with data(), methods, and computed. The next uses Composition API with setup() and ref(). The third uses <script setup>. This isn’t a style preference - it creates real problems. Developers can’t grep for patterns consistently. Shared logic can’t be extracted because half the codebase uses mixins and half uses composables. Code review takes twice as long because every component is a surprise.

Reactivity breaks silently. AI destructures reactive objects, losing reactivity. It uses ref where reactive makes sense and reactive where ref is simpler. It forgets toRefs() when passing reactive object properties to composables. It creates computed properties that depend on non-reactive values and never update. The UI looks correct on first render and then stops responding to state changes. Users click buttons and nothing happens.

Components become massive because AI doesn’t think in composables. A single-file component handles form validation, API calls, toast notifications, and keyboard shortcuts. The template is 150 lines of conditional rendering. The script section is 300 lines of mixed concerns. There are no composables because AI generates everything inline.

Pinia stores accumulate everything. Modal open/close state. Form input values. Temporary filter selections. AI puts it all in the global store because that’s where it learned to put state. Your devtools show 40 state properties in a single store, half of which are isModalOpen booleans that only one component reads.

Standardizing on Composition API and Scoped State

We standardize on Composition API with <script setup>. Every Options API component gets migrated. This isn’t cosmetic - it’s structural. <script setup> enables better TypeScript inference, tree-shaking, and composable extraction. We migrate component by component, verifying behavior with existing tests after each conversion.

Reactivity gets audited line by line. We trace every ref, reactive, computed, and watch to verify the reactivity chain is unbroken. Destructured reactive objects get wrapped with toRefs(). Computed properties that reference non-reactive values get fixed or converted to plain functions. We use Vue DevTools’ timeline to verify that state changes propagate as expected through the component tree.

Composables extract shared logic. Form handling becomes useForm(). API calls become useQuery() wrappers. Toast notifications become useToast(). Each composable is a function that returns reactive state and methods. It’s testable without rendering a component. It’s reusable across the app. The component that was 400 lines becomes 80 lines that compose focused pieces.

Pinia stores get scoped by domain. User state stays in the user store. Cart state stays in the cart store. UI state that belongs to a single component moves back to the component. We remove stores that have no subscribers outside their parent component. If only one component reads and writes a store, the state belongs in that component.

Three API Styles Become One Consistent Codebase

Before: A Vue app with 3 different API styles, 12 components over 300 lines, computed properties that don’t update, and a Pinia store with 45 properties. New developers take a full week to understand the patterns because there are no patterns - just 3 different approaches used interchangeably.

After: Every component uses <script setup> with TypeScript. The largest component is 120 lines. All computed properties react to state changes correctly. Pinia stores contain only truly global state, with clear domain boundaries. New developers follow the same patterns from day one because there’s only one way to do things.

The debugging experience transforms. Before, tracking down why a click doesn’t update the UI means tracing through reactive chains that may be broken anywhere. After, Vue DevTools shows clean state flow. Every reactive dependency is intentional and traceable.

Linting and Type Safety That Block Bad Patterns at CI

We configure vue-tsc with strict mode in CI. Every prop, every emitted event, every store access is type-checked. AI-generated components with wrong prop types or missing event declarations fail the build. The compiler catches what code review misses.

ESLint with eslint-plugin-vue enforces Composition API only. Options API syntax triggers an error, not a warning. Component size limits flag files that exceed 200 lines. Required composable patterns are enforced - no inline API calls in components.

We add Vitest for component testing with @vue/test-utils. Every composable gets unit tests that verify reactive behavior. Component tests mount with real Pinia stores, not mocks, so reactivity issues surface during testing. The test suite verifies that state changes propagate to the template as expected.

A component library in Storybook documents every shared component with its props, events, and slots. When AI generates a new component, the team compares it against existing patterns in Storybook. Duplication is caught visually before it enters the codebase.

What you get

Composition API migration for all Options API components
Reactivity audit to fix broken refs, reactive objects, and computed properties
Component decomposition with extracted composables
Pinia store rationalization with proper state scoping
TypeScript integration with vue-tsc strict checking

Ideal for

  • Vue apps with mixed API styles from AI code generation
  • Products where UI state changes don't trigger expected updates
  • Teams seeing Pinia stores that have become god objects
  • Founders who need a consistent Vue codebase for their growing team

Other technologies

Industries

Ready to build?

Tell us about your project and we'll figure out how we can help.

Get in touch