Architectural Overview
The DeployStack frontend follows a feature-based modular architecture with clear separation of concerns. The application is built on Vue 3’s Composition API, emphasizing type safety, reusability, and maintainability.Core Principles
- Feature-First Organization: Code is organized by feature domains rather than technical layers
- Type Safety First: TypeScript is mandatory for all new code
- Composition Over Inheritance: Use composables and the Composition API exclusively
- Direct API Communication: No abstraction layers over fetch() calls
- Component-Driven Development: Build from small, reusable components up to complex features
Directory Architecture
Views Layer (/views)
Views represent page-level components that map directly to routes. They orchestrate the overall page functionality and data flow.
Organization Rules
- Route Mapping: Each view corresponds to a specific route in the application
- Nested Structure: Mirror the URL structure in the directory hierarchy
- Feature Grouping: Group related views in subdirectories
View Responsibilities
- Route handling: Process route parameters and query strings
- Data orchestration: Coordinate multiple service calls
- Layout selection: Choose appropriate layout wrapper
- Permission checks: Verify user access rights
- Error boundaries: Handle page-level errors
What Views Should NOT Do
- Contain complex business logic (use services)
- Implement reusable UI patterns (use components)
- Directly manage global state (use stores)
- Include detailed form validation (use composables)
Components Layer (/components)
Components are reusable UI building blocks that encapsulate specific functionality and presentation logic.
Component Organization Structure
MANDATORY: All feature-specific components must follow a hierarchical directory structure that mirrors the view organization. This creates clear ownership boundaries and improves code discoverability.Real-World Example: MCP Server Components
Barrel Export Pattern (Mandatory)
Every feature component directory must include anindex.ts file that exports all components. This creates a clean import API and makes refactoring easier.
Usage in Views
Component Categories
-
UI Components (
/ui): Generic, design-system components - read the UI Design System- Examples: Buttons, Modals, Inputs
- Stateless and focused on presentation
- Use shadcn-vue components where applicable
- Follow shadcn-vue patterns
- No business logic
- Highly reusable across features
-
Feature Components (
/components/[feature]/[sub-feature]): Domain-specific components- Must follow hierarchical organization matching views
- Contain feature-specific logic
- Reusable within their domain
- Must include barrel exports via
index.ts - May compose UI components
-
Shared Components (
/components): Cross-feature components- Used across multiple features
- Examples:
AppSidebar,NavbarLayout - Only place components here if they truly span features
Component Organization Rules
- Mirror View Structure: Component organization should parallel view hierarchy
- Feature Isolation: Keep feature components within their feature directory
- Mandatory Barrel Exports: Every feature directory must export via
index.ts - No Deep Nesting: Maximum 3 levels deep (feature/sub-feature/component)
- Colocation: Related components stay together
When to Create a New Feature Directory
Create a new feature component directory when:- You have 3+ components related to the same feature
- The components are reused across multiple views within the feature
- The feature has clear boundaries and ownership
- The components share common types or logic
Component Design Rules
- Single Responsibility: Each component has one clear purpose
- Props Down, Events Up: Maintain unidirectional data flow
- Composition Pattern: Break complex components into smaller parts
- Self-Contained: Components should work in isolation
- Empty States: Always use the shadcn-vue Empty component for no-data states. Never create custom empty state markup with manual styling.
Exceptions to the Structure
You may deviate from the structure for:- One-off components: Components used in a single view can stay in the view file
- UI library components: shadcn-vue components in
/uifollow their own structure - Extremely small features: Features with only 1-2 simple components
Services Layer (/services)
Services handle all external communication and business logic processing. They act as the bridge between the frontend and backend APIs.
Service Architecture Patterns
- Static Class Pattern: All service methods must be static
- Direct Fetch Usage: Use native fetch() API exclusively
- Type-Safe Contracts: Define interfaces for all API requests/responses
- Error Transformation: Convert API errors to user-friendly messages
Service Responsibilities
- API endpoint communication
- Request/response transformation
- Error handling and normalization
- Cache management (when applicable)
- Business logic that spans multiple components
Composables Layer (/composables)
Composables are reusable logic units that leverage Vue’s Composition API to share stateful logic across components.
Composable Organization Structure
MANDATORY: Feature-specific composables must follow a hierarchical directory structure that mirrors the component and view organization. This creates consistency across the codebase and makes related logic easy to find.Real-World Example: MCP Server Composables
Barrel Export Pattern (Mandatory)
Every feature composable directory must include anindex.ts file that exports all composables. This mirrors the component structure and provides a clean import API.
Usage in Components
Composable Organization Rules
- Mirror Component Structure: Composable organization should parallel component hierarchy
- Feature Isolation: Keep feature composables within their feature directory
- Mandatory Barrel Exports: Every feature directory must export via
index.ts - No Deep Nesting: Maximum 3 levels deep (feature/sub-feature/composable)
- Colocation: Keep related composables together
When to Create a Feature Composable Directory
Create a new feature composable directory when:- You have 2+ composables related to the same feature
- The composables are reused across multiple components within the feature
- The feature has clear boundaries and ownership
- The composables share common types or state
Composable Design Patterns
- Naming Convention: Always prefix with
use(e.g.,useAuth,useEventBus,useInstallationCache) - Single Purpose: Each composable solves one specific problem
- Return Interface: Clearly define what’s returned (state, methods, computed)
- Lifecycle Awareness: Handle setup/cleanup in lifecycle hooks
Common Composable Patterns
- Data Fetching:
useAsyncData,usePagination - Form Handling:
useForm,useValidation - UI State:
useModal,useToast - Feature Logic:
useTeamManagement,useCredentials,useInstallationCache
Exceptions to the Structure
You may deviate from the structure for:- Global utilities: Composables used across all features (e.g.,
useAuth,useEventBus) - Single composable features: Features with only one composable can stay at the root
- Third-party integrations: External library wrappers may have their own structure
Stores Layer (/stores)
Stores manage global application state using Pinia, Vue’s official state management solution.
Store Guidelines
- Feature-Based Stores: One store per major feature domain
- Composition API Style: Use setup stores, not options API
- Readonly State: Export readonly refs to prevent external mutations
- Action Pattern: All state changes through defined actions
When to Use Stores
- User session and authentication state
- Cross-component shared data
- Cache for expensive operations
- Application-wide settings
When NOT to Use Stores
- Component-specific state
- Temporary UI state
- Form data (use local state)
API Integration Architecture
Service Layer Pattern
IMPORTANT: The frontend uses a service layer pattern with directfetch() calls for API communication. This is the established pattern and must be followed for consistency.
✅ Required Pattern - Direct Fetch Calls
All API services must use directfetch() calls instead of API client libraries. This ensures consistency across the codebase and simplifies maintenance.
❌ Avoid - API Client Libraries
Do not use API client libraries like Axios, or custom API client wrappers:Service Layer Guidelines
- Use Static Classes: All service methods should be static
- Direct Fetch: Always use native
fetch()API - Error Handling: Throw meaningful errors for failed requests
- Type Safety: Define proper TypeScript interfaces for requests/responses
- Consistent Naming: Use descriptive method names (e.g.,
getAllServers,createCategory) - Base URL: Always use environment variables for API endpoints
Backend API Environment Variables
Always useVITE_DEPLOYSTACK_BACKEND_URL for all backend API calls and SSE connections.
getEnv('VITE_DEPLOYSTACK_BACKEND_URL') for all API calls, SSE connections, and WebSocket connections to the backend.
Using Services in Components
Data Flow Architecture
Unidirectional Data Flow
- User triggers action in a View or Component
- View/Component calls Service method
- Service communicates with API using fetch()
- Response updates Store (if global state)
- Components react to store changes
Event-Driven Updates
The application uses an event bus for cross-component communication without direct coupling. This enables real-time updates across unrelated components, cache invalidation signals, global notifications, and feature-to-feature communication. For complete details on the event bus system, including usage patterns, naming conventions, and implementation examples, see the Event Bus Documentation.Persistent State Management
The application includes a storage system built into the event bus for managing persistent state across route changes and browser sessions. This system provides type-safe localStorage access with automatic event emission for reactive updates. For complete details on the storage system, including usage patterns, naming conventions, and best practices, see the Frontend Storage System.Component Implementation Standards
Vue Component Structure
Always prefer Vue Single File Components (SFC) with<script setup> and <template> sections over TypeScript files with render functions.
✅ Preferred Approach - Vue SFC:
❌ Avoid - TypeScript files with render functions:
Why Vue SFC is Preferred
- Better Developer Experience: Clear separation of logic, template, and styles
- Improved Readability: Template syntax is more intuitive than render functions
- Better Tooling Support: Vue DevTools, syntax highlighting, and IntelliSense work better
- Easier Maintenance: Future developers can understand and modify components more easily
- Vue 3 Best Practices: Aligns with official Vue 3 recommendations
Table Components
For table implementations, use the shadcn-vue Table components as documented in the Table Design System. Never use raw HTML table elements.Component Communication Patterns
Parent-Child Communication
- Props for Data Down: Pass data from parent to child
- Events for Actions Up: Emit events from child to parent
- v-model for Two-Way: Use for form inputs and controlled components
Sibling Communication
- Through Parent: Lift state up to common parent
- Event Bus: For loosely coupled components
- Shared Store: For persistent shared state
Cross-Feature Communication
- Event Bus: Primary method for feature-to-feature updates
- Shared Services: Common API operations
- Global Store: Application-wide state
Form Architecture
Form Handling Strategy
- Local State First: Keep form data in component state
- Validation Composables: Reuse validation logic
- Service Layer Submission: Process through services
- Error Display Pattern: Consistent error messaging
Form Patterns
- Use VeeValidate with Zod schemas for complex forms
- Implement field-level validation feedback
- Show loading states during submission
- Handle API validation errors gracefully
Routing Architecture
Route Organization
- Feature Modules: Group related routes by feature
- Lazy Loading: Use dynamic imports for route components
- Route Guards: Implement authentication and authorization checks
- Breadcrumb Support: Maintain hierarchical navigation
Dynamic Routes
- Use
[id]notation for dynamic segments - Handle route parameter validation in views
- Implement proper 404 handling for invalid IDs
Error Handling Architecture
Error Boundaries
- View Level: Catch and display page-level errors
- Component Level: Handle component-specific errors
- Global Level: Catch unhandled errors
Error Patterns
- Display user-friendly error messages
- Log technical details for debugging
- Provide recovery actions when possible
- Maintain application stability on errors
Performance Architecture
Code Splitting Strategy
- Route-Based Splitting: Each route loads its own bundle
- Component Lazy Loading: Heavy components load on demand
- Vendor Chunking: Separate third-party libraries
Optimization Patterns
- Use
shallowReffor large objects - Implement virtual scrolling for long lists
- Debounce expensive operations
- Memoize computed values appropriately
Security Architecture
Frontend Security Principles
- Never Trust Client: All validation must happen on backend
- Secure Storage: Never store sensitive data (passwords, API keys, tokens) in localStorage. See Frontend Storage System for proper storage patterns
- XSS Prevention: Sanitize user input, use Vue’s built-in protections
- CSRF Protection: Include tokens in API requests
Authentication Flow
- Token-based authentication (JWT)
- Automatic token refresh
- Secure token storage (httpOnly cookies preferred)
- Route protection via navigation guards
Testing Architecture
Testing Strategy
- Unit Tests: For services, composables, and utilities
- Component Tests: For isolated component behavior
- Integration Tests: For feature workflows
- E2E Tests: For critical user paths
Test Organization
- Mirror source structure in test directories
- Co-locate test files with source files
- Use descriptive test names
- Follow AAA pattern (Arrange, Act, Assert)
Plugin Architecture
Plugin System Design
The application supports runtime plugin loading for extensibility.Plugin Structure
- Entry Point: Each plugin exports a default configuration
- Extension Points: Plugins hook into defined extension points
- Isolation: Plugins run in isolated contexts
- Version Management: Plugins declare compatible versions
Plugin Guidelines
- Plugins cannot modify core functionality
- Use provided APIs and extension points
- Handle errors gracefully
- Document dependencies clearly
Development Workflow
Code Organization Rules
- Feature Cohesion: Keep related code together
- Explicit Imports: No magic globals or auto-imports
- Type Definitions: Colocate types with their usage
- Consistent Naming: Follow established patterns
File Naming Conventions
- Components: PascalCase (e.g.,
UserProfile.vue) - Composables: camelCase with ‘use’ prefix (e.g.,
useAuth.ts) - Services: camelCase with ‘Service’ suffix (e.g.,
userService.ts) - Types: PascalCase for interfaces/types (e.g.,
UserCredentials) - Views: Match route names (e.g.,
index.vue,[id].vue)
Import Order
- External dependencies
- Vue and framework imports
- Internal aliases (@/ imports)
- Relative imports
- Type imports
Anti-Patterns to Avoid
Component Anti-Patterns
- ❌ Using Options API in new components
- ❌ Mixing paradigms (Options + Composition)
- ❌ Direct DOM manipulation
- ❌ Inline styles for layout
- ❌ Business logic in templates
State Management Anti-Patterns
- ❌ Mutating props directly
- ❌ Excessive global state
- ❌ Circular store dependencies
- ❌ Store logic in components
Service Anti-Patterns
- ❌ Using Axios or other HTTP libraries
- ❌ Instance-based service classes
- ❌ Mixing UI concerns in services
- ❌ Inconsistent error handling
General Anti-Patterns
- ❌ Premature optimization
- ❌ Deep component nesting (>3 levels)
- ❌ Tight coupling between features
- ❌ Ignoring TypeScript errors
- ❌ Copy-paste programming
Architecture Decision Records
Why Static Services?
Static service methods ensure:- No instance management complexity
- Predictable behavior
- Easy testing and mocking
- Clear API boundaries
Why Direct Fetch?
Using native fetch() provides:- No external dependencies
- Consistent API across services
- Full control over request/response
- Smaller bundle size
Why Feature-Based Structure?
Feature organization offers:- Better code locality
- Easier feature removal/addition
- Clear ownership boundaries
- Reduced merge conflicts
Migration Guidelines
When refactoring existing code:- Incremental Migration: Update feature by feature
- Test Coverage First: Add tests before refactoring
- Preserve Functionality: No behavior changes during refactor
- Document Changes: Update relevant documentation
- Review Thoroughly: Architecture changes need careful review
Future Considerations
As the application grows, consider:- Micro-frontend architecture for team autonomy
- Module federation for dynamic feature loading
- GraphQL adoption for efficient data fetching
- Server-side rendering for performance
- Progressive Web App capabilities

