35 KiB
Feature: Unified App - Pragmatic Monolith Consolidation
Overview
Consolidate the two separate frontend applications (Reports App and Data Entry App) into a single unified SPA with both modules accessible from one menu. This pragmatic monolith approach maintains module isolation while simplifying deployment and providing a unified user experience with a single build and single IIS site.
Status: Implementation-Ready Complexity: Medium Estimated Effort: 2.5 days
Problem Statement
Current Issues
- Two separate frontend deployments in IIS (
/roa2web/and/data-entry/) - Complex deployment process (2 builds, 2 IIS configurations)
- CSS conflicts when using shared components (white text on white background)
- No unified navigation between modules
- Duplicate setup and configuration code
Benefits of Unification
- Simplified deployment (single build, single IIS site)
- Unified user experience with single menu
- Shared component consistency
- Module isolation preserved through error boundaries
- Optimized bundle loading via lazy loading
User Stories
Primary Users
- As a user, I want to access both Reports and Data Entry modules from a single menu so that I can navigate seamlessly between functionalities.
- As a developer, I want simplified deployment with a single build process so that I can reduce deployment complexity and maintenance overhead.
- As a user, I want the app to remain functional even if one module encounters an error so that my work is not interrupted.
- As an administrator, I want a single IIS site configuration so that I can manage deployment more easily.
Functional Requirements
Core Requirements
1. Unified Navigation
- Single top-level menu with sections for Reports and Data Entry modules
- Menu structure:
- Rapoarte (Reports) section with Dashboard, Invoices, Bank/Cash, Trial Balance
- Introduceri Date (Data Entry) section with Receipts List, New Receipt
- Sistem section with Telegram Bot, Cache Stats
- Active route highlighting
- Responsive slide menu (mobile + desktop)
2. Module Isolation
- Error boundary per module (Reports, Data Entry)
- Bug in one module does not crash the other
- Independent lazy loading per module
- Separate stores per module (no cross-contamination)
3. URL Structure
/login → Login page
/ → Redirect to /reports/dashboard
/reports/dashboard → Dashboard (Reports module)
/reports/invoices → Invoices view
/reports/bank-cash → Bank/Cash register view
/reports/trial-balance → Trial Balance view
/reports/telegram → Telegram Bot management
/reports/cache-stats → Cache statistics
/data-entry → Receipts list (Data Entry module)
/data-entry/create → Create new receipt
/data-entry/:id → View receipt details
/data-entry/:id/edit → Edit receipt
4. Shared Components Integration
- Use existing shared components from
shared/frontend/:LoginView.vue- Login pageAppHeader.vue- Top navigation barSlideMenu.vue- Sidebar menuCompanySelector.vue- Company dropdownPeriodSelector.vue- Accounting period selector
- Maintain consistent styling and behavior across modules
5. Authentication & Authorization
- Unified JWT authentication
- Single login flow for both modules
- Preserve existing auth store factory pattern
- Session persistence across module navigation
Secondary Requirements
1. Feature Flags
- Ability to enable/disable modules via configuration
- Feature flag configuration in
src/config/features.js - UI hides disabled module menu items
2. Loading States
- Module-level loading spinners
- Skeleton screens for lazy-loaded routes
- Progress indication during module switching
3. Analytics & Monitoring
- Track module switching events
- Error boundary triggers logged
- Performance metrics per module
Technical Requirements
Project Structure
.
├── public/
│ └── favicon.ico
├── src/
│ ├── main.js # App entry point
│ ├── App.vue # Root component with unified menu
│ │
│ ├── router/
│ │ └── index.js # Unified router with lazy loading
│ │
│ ├── modules/
│ │ ├── reports/ # ISOLATED REPORTS MODULE
│ │ │ ├── ReportsLayout.vue # Error boundary wrapper
│ │ │ ├── views/ # Reports views (migrated)
│ │ │ │ ├── DashboardView.vue
│ │ │ │ ├── InvoicesView.vue
│ │ │ │ ├── BankCashRegisterView.vue
│ │ │ │ ├── TrialBalanceView.vue
│ │ │ │ ├── TelegramView.vue
│ │ │ │ └── CacheStatsView.vue
│ │ │ ├── stores/ # Module-specific stores
│ │ │ │ ├── dashboard.js
│ │ │ │ ├── invoices.js
│ │ │ │ ├── treasury.js
│ │ │ │ ├── trialBalance.js
│ │ │ │ └── cacheStore.js
│ │ │ └── services/ # Module API services
│ │ │ └── api.js # Reports API client
│ │ │
│ │ └── data-entry/ # ISOLATED DATA ENTRY MODULE
│ │ ├── DataEntryLayout.vue # Error boundary wrapper
│ │ ├── views/ # Data Entry views (migrated)
│ │ │ └── receipts/
│ │ │ ├── ReceiptsListView.vue
│ │ │ └── ReceiptCreateView.vue
│ │ ├── components/ # Module-specific components
│ │ │ └── ocr/
│ │ │ ├── OCRUploadZone.vue
│ │ │ ├── OCRPreview.vue
│ │ │ └── OCRConfidenceIndicator.vue
│ │ ├── stores/ # Module-specific stores
│ │ │ └── receiptsStore.js
│ │ └── services/ # Module API services
│ │ └── api.js # Data Entry API client
│ │
│ ├── shared/ # SHARED COMPONENTS & LOGIC
│ │ ├── components/ # Shared UI components
│ │ │ ├── layout/
│ │ │ │ ├── AppHeader.vue
│ │ │ │ └── SlideMenu.vue
│ │ │ ├── LoginView.vue
│ │ │ ├── CompanySelector.vue
│ │ │ ├── PeriodSelector.vue
│ │ │ └── ErrorBoundary.vue # NEW: Error boundary component
│ │ ├── stores/ # Shared stores (factories)
│ │ │ ├── auth.js # Auth store factory
│ │ │ ├── companies.js # Companies store factory
│ │ │ └── accountingPeriod.js # Period store factory
│ │ └── styles/ # Shared CSS
│ │ ├── login.css
│ │ ├── layout/
│ │ │ ├── header.css
│ │ │ └── navigation.css
│ │ └── shared.css
│ │
│ ├── config/
│ │ ├── menu.js # Unified menu configuration
│ │ └── features.js # Feature flags
│ │
│ └── assets/
│ └── css/ # Global CSS (from reports-app)
│ ├── core/ # Design tokens
│ ├── components/ # Reusable UI patterns
│ ├── patterns/ # Interactive patterns
│ ├── layout/ # Page structure
│ ├── utilities/ # Utility classes
│ └── vendor/ # PrimeVue overrides
│
├── vite.config.js # Unified Vite config (root)
├── package.json # Merged dependencies (root)
├── .env.example # Environment template (root)
└── README.md # Unified app documentation (root)
Files to Create
| File | Purpose |
|---|---|
package.json |
Merged dependencies from both apps |
vite.config.js |
Dual proxy config, lazy loading, base path / |
src/main.js |
App initialization, PrimeVue setup, global components |
src/App.vue |
Root component with unified menu |
src/router/index.js |
Unified router with lazy loading |
src/config/menu.js |
Menu configuration |
src/config/features.js |
Feature flags |
src/shared/components/ErrorBoundary.vue |
Error boundary component |
src/modules/reports/ReportsLayout.vue |
Reports error boundary wrapper |
src/modules/data-entry/DataEntryLayout.vue |
Data Entry error boundary wrapper |
.env.example |
Environment variables template |
README.md |
Documentation |
Files to Migrate
From reports-app/frontend/
Views (→ modules/reports/views/):
- DashboardView.vue
- InvoicesView.vue
- BankCashRegisterView.vue
- TrialBalanceView.vue
- TelegramView.vue
- CacheStatsView.vue
LoginView.vue(use shared)
Stores (→ modules/reports/stores/):
- dashboard.js
- invoices.js
- treasury.js
- trialBalance.js
- cacheStore.js
auth.js(use shared)companies.js(use shared)accountingPeriod.js(use shared)
Services (→ modules/reports/services/):
- api.js (adapt for
/api/reports/prefix)
CSS (→ assets/css/):
- Copy entire
src/assets/css/directory structure - All design tokens, components, patterns, utilities
From data-entry-app/frontend/
Views (→ modules/data-entry/views/receipts/):
- ReceiptsListView.vue
- ReceiptCreateView.vue
LoginView.vue(use shared)
Components (→ modules/data-entry/components/):
- ocr/OCRUploadZone.vue
- ocr/OCRPreview.vue
- ocr/OCRConfidenceIndicator.vue
Stores (→ modules/data-entry/stores/):
- receiptsStore.js
auth.js(use shared)companies.js(use shared)accountingPeriod.js(use shared)
Services (→ modules/data-entry/services/):
- api.js (adapt for
/api/data-entry/prefix)
CSS (→ merge with assets/css/):
- main.css (merge with reports-app main.css)
- Any component-specific styles
From shared/frontend/
Components (→ shared/components/):
- LoginView.vue
- layout/AppHeader.vue
- layout/SlideMenu.vue
- CompanySelector.vue
- PeriodSelector.vue
Stores (→ shared/stores/):
- auth.js (factory)
- companies.js (factory)
- accountingPeriod.js (factory)
Styles (→ shared/styles/):
- login.css
- layout/header.css
- layout/navigation.css
Dependencies
Unified package.json (root directory)
Merge all dependencies from both apps:
From reports-app:
- axios: ^1.6.2
- chart.js: ^4.5.0
- date-fns: ^2.30.0
- jspdf: ^3.0.1
- jspdf-autotable: ^5.0.2
- pinia: ^2.1.7
- primeicons: ^6.0.1
- primevue: ^3.46.0
- qrcode.vue: ^3.6.0
- vue: ^3.4.0
- vue-chartjs: ^5.3.2
- vue-router: ^4.2.5
- xlsx: ^0.18.5
From data-entry-app:
- All duplicates already covered
- Additional: @primevue/themes: ^4.0.0 (optional, may remove)
DevDependencies:
- @playwright/test: ^1.54.2
- @vitejs/plugin-vue: ^5.0.0
- eslint: ^8.56.0
- eslint-plugin-vue: ^9.20.0
- prettier: ^3.1.1
- vite: ^5.0.10
API Routing
Vite Dev Server Proxy
// vite.config.js
server: {
port: 3000,
proxy: {
'/api/reports': {
target: 'http://localhost:8001',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/reports/, '/api')
},
'/api/data-entry': {
target: 'http://localhost:8003',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/data-entry/, '/api')
},
'/uploads': {
target: 'http://localhost:8003',
changeOrigin: true
}
}
}
IIS Production Proxy
<!-- web.config URL rewrite rules -->
<rule name="Proxy Reports API">
<match url="^api/reports/(.*)" />
<action type="Rewrite" url="http://localhost:8001/api/{R:1}" />
</rule>
<rule name="Proxy Data Entry API">
<match url="^api/data-entry/(.*)" />
<action type="Rewrite" url="http://localhost:8003/api/{R:1}" />
</rule>
<rule name="Proxy Uploads">
<match url="^uploads/(.*)" />
<action type="Rewrite" url="http://localhost:8003/uploads/{R:1}" />
</rule>
Build Configuration
Vite Config - Lazy Loading & Bundle Splitting
// vite.config.js
export default defineConfig({
plugins: [vue(), htmlTimestampPlugin()],
base: process.env.NODE_ENV === 'production' ? '/' : '/',
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@shared': fileURLToPath(new URL('./src/shared', import.meta.url)),
'@reports': fileURLToPath(new URL('./src/modules/reports', import.meta.url)),
'@data-entry': fileURLToPath(new URL('./src/modules/data-entry', import.meta.url))
},
dedupe: ['vue', 'vue-router', 'pinia', 'primevue']
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
// Core vendors
'vendor-core': ['vue', 'vue-router', 'pinia'],
'vendor-primevue': ['primevue/config', 'primevue/button', 'primevue/datatable'],
'vendor-utils': ['axios', 'date-fns'],
// Charts (reports only)
'vendor-charts': ['chart.js', 'vue-chartjs'],
// Excel/PDF exports
'vendor-export': ['xlsx', 'jspdf', 'jspdf-autotable'],
// Module-specific chunks (lazy loaded)
// Reports module loaded via dynamic import
// Data Entry module loaded via dynamic import
},
entryFileNames: `assets/[name].[hash].js`,
chunkFileNames: `assets/[name].[hash].js`,
assetFileNames: `assets/[name].[hash].[ext]`
}
}
}
})
Expected Build Output:
dist/
├── index.html
├── assets/
│ ├── vendor-core.[hash].js (~150KB) - Vue, Router, Pinia
│ ├── vendor-primevue.[hash].js (~200KB) - PrimeVue components
│ ├── vendor-utils.[hash].js (~80KB) - Axios, date-fns
│ ├── vendor-charts.[hash].js (~150KB) - Chart.js (lazy)
│ ├── vendor-export.[hash].js (~200KB) - XLSX, jsPDF (lazy)
│ ├── reports.[hash].js (~150KB) - Reports module (lazy)
│ ├── data-entry.[hash].js (~100KB) - Data Entry module (lazy)
│ ├── main.[hash].js (~50KB) - App shell
│ └── main.[hash].css (~80KB) - Global CSS
Design Decisions
Approach: Pragmatic Monolith
Why NOT Micro-frontends (Module Federation / Single-SPA)?
| Micro-Frontend Criterion | ROA2WEB Reality | Justification |
|---|---|---|
| 20+ developers on separate teams | 1 developer | Overkill - no team coordination needed |
| Deploy multiple times per day | Weekly deploys | No benefit from independent deployments |
| Millions of users, high scale | 1-5 concurrent users | No scaling justification |
| Different frameworks per team | Vue 3 only | No technical diversity |
| Independent release cycles | Single release cycle | Adds unnecessary complexity |
Pragmatic Monolith Benefits:
- Simple build process (single
npm run build) - Shared dependencies (smaller bundle size)
- Error boundaries provide 50-70% isolation (close to separate apps)
- Lazy loading prevents loading unused modules
- Feature flags allow disabling modules without redeploy
Error Boundary Implementation
ErrorBoundary.vue Component:
<template>
<div v-if="error" class="module-error">
<div class="error-icon">⚠️</div>
<h3>{{ moduleName }} a întâmpinat o eroare</h3>
<p class="error-message">{{ error.message }}</p>
<div class="error-actions">
<button @click="retry" class="btn btn-primary">Reîncearcă</button>
<button @click="goHome" class="btn btn-secondary">Mergi la Dashboard</button>
</div>
</div>
<slot v-else />
</template>
<script setup>
import { ref, onErrorCaptured } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
moduleName: { type: String, required: true }
})
const router = useRouter()
const error = ref(null)
onErrorCaptured((err, instance, info) => {
error.value = err
console.error(`[${props.moduleName}] Error caught:`, err, info)
return false // Prevent error from propagating
})
const retry = () => {
error.value = null
window.location.reload()
}
const goHome = () => {
error.value = null
router.push('/reports/dashboard')
}
</script>
Usage in Module Layouts:
<!-- ReportsLayout.vue -->
<template>
<ErrorBoundary module-name="Reports">
<router-view />
</ErrorBoundary>
</template>
<!-- DataEntryLayout.vue -->
<template>
<ErrorBoundary module-name="Data Entry">
<router-view />
</ErrorBoundary>
</template>
Lazy Loading Strategy
Router Configuration:
const routes = [
{
path: '/login',
name: 'Login',
component: LoginView,
meta: { requiresAuth: false }
},
{
path: '/reports',
component: () => import('@/modules/reports/ReportsLayout.vue'),
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/modules/reports/views/DashboardView.vue'),
meta: { requiresAuth: true, title: 'Dashboard - ROA Reports' }
},
{
path: 'invoices',
name: 'Invoices',
component: () => import('@/modules/reports/views/InvoicesView.vue'),
meta: { requiresAuth: true, title: 'Facturi - ROA Reports' }
},
// ... more routes
]
},
{
path: '/data-entry',
component: () => import('@/modules/data-entry/DataEntryLayout.vue'),
children: [
{
path: '',
name: 'ReceiptsList',
component: () => import('@/modules/data-entry/views/receipts/ReceiptsListView.vue'),
meta: { requiresAuth: true, title: 'Lista Bonuri' }
},
// ... more routes
]
},
{
path: '/',
redirect: '/reports/dashboard'
}
]
Menu Configuration
config/menu.js:
export const menuSections = [
{
title: 'Rapoarte',
items: [
{ to: '/reports/dashboard', icon: 'pi pi-home', label: 'Dashboard' },
{ to: '/reports/invoices', icon: 'pi pi-file', label: 'Facturi' },
{ to: '/reports/bank-cash', icon: 'pi pi-money-bill', label: 'Casa și Banca' },
{ to: '/reports/trial-balance', icon: 'pi pi-calculator', label: 'Balanță de Verificare' }
]
},
{
title: 'Introduceri Date',
items: [
{ to: '/data-entry', icon: 'pi pi-list', label: 'Lista Bonuri' },
{ to: '/data-entry/create', icon: 'pi pi-plus', label: 'Bon Nou' }
]
},
{
title: 'Sistem',
items: [
{ to: '/reports/telegram', icon: 'pi pi-telegram', label: 'Telegram Bot' },
{ to: '/reports/cache-stats', icon: 'pi pi-chart-bar', label: 'Statistici Cache' }
]
}
]
Feature Flags
config/features.js:
export const features = {
reports: {
enabled: true,
modules: {
dashboard: true,
invoices: true,
bankCash: true,
trialBalance: true,
telegram: true,
cacheStats: true
}
},
dataEntry: {
enabled: true,
modules: {
receipts: true,
ocr: true
}
}
}
export function isFeatureEnabled(module, subModule = null) {
if (!features[module]?.enabled) return false
if (subModule && !features[module]?.modules?.[subModule]) return false
return true
}
CSS Architecture
Preserve Reports App CSS System:
- Use complete CSS structure from
reports-app/frontend/src/assets/css/ - Design tokens in
core/tokens.css - Component patterns in
components/ - PrimeVue overrides in
vendor/primevue-overrides.css
Merge Data Entry CSS:
- Extract unique data-entry styles
- Integrate into existing pattern system
- Ensure no conflicts with reports CSS
Shared Styles:
- Import from
src/shared/styles/for LoginView, AppHeader, SlideMenu - Maintain consistency across modules
Acceptance Criteria
Functionality
- User can log in and access both Reports and Data Entry modules from unified menu
- All Reports views (Dashboard, Invoices, Bank/Cash, Trial Balance, Telegram, Cache Stats) work correctly
- All Data Entry views (Receipts List, Create Receipt, Edit Receipt) work correctly
- Navigation between modules preserves authentication and selected company/period
- Error in Reports module does not crash Data Entry module (and vice versa)
- Logout works correctly and clears all stores
Performance
- Initial page load < 2 seconds on 3G connection
- Module switching (Reports ↔ Data Entry) < 500ms (already loaded) or < 1s (first load)
- Build produces separate chunks for each module (reports.js, data-entry.js)
- Total bundle size ≤ sum of current apps (no regression)
- Lighthouse score ≥ 90 for Performance
Build & Deployment
npm run buildsucceeds without errors- Build output contains expected chunks (vendor-core, reports, data-entry)
- IIS deployment with single site works correctly
- API proxy routes correctly to both backends (8001, 8003)
- Production build works on Windows IIS
- Cache busting works (new builds force reload)
Error Handling
- Error boundary catches component errors
- Error boundary displays user-friendly message
- User can retry or navigate away from error
- Console logs error details for debugging
- Error in one module doesn't affect the other
Responsive Design
- Works on mobile (375px width)
- Works on tablet (768px width)
- Works on desktop (1920px width)
- Slide menu works on mobile
- Company/Period selectors work on mobile
Testing
- E2E tests pass for login flow
- E2E tests pass for Reports module navigation
- E2E tests pass for Data Entry module navigation
- E2E tests pass for module switching
- E2E tests verify error boundary isolation
Out of Scope
The following are explicitly NOT included in this implementation:
Backend Changes
- No changes to
reports-app/backend/(port 8001) - No changes to
data-entry-app/backend/(port 8003) - Backends remain separate microservices
Shared Database
- Each backend keeps its own database (Oracle for Reports, SQLite for Data Entry)
- No database consolidation
API Consolidation
- APIs remain at separate ports (8001, 8003)
- No unified API gateway (using IIS/Vite proxy instead)
Telegram Bot
- Telegram bot remains in
reports-app/telegram-bot/ - No changes to bot architecture
Advanced Features
- No server-side rendering (SSR)
- No progressive web app (PWA) features
- No offline mode
- No real-time updates (WebSockets)
Future Enhancements
- Module-level permissions (show/hide based on user role)
- Module analytics dashboard
- A/B testing framework
- Module versioning
- Micro-frontend migration (if team grows to 20+)
Risks and Mitigations
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| CSS conflicts between modules | Medium | Medium | Use CSS modules or scoped styles; test thoroughly; maintain design token system from reports-app |
| Large bundle size | Medium | Medium | Implement aggressive code splitting; lazy load modules; use tree shaking; monitor with webpack-bundle-analyzer |
| Error boundary not catching all errors | Low | High | Test error scenarios thoroughly; add global error handler; log errors to monitoring service |
| Deployment complexity on IIS | Medium | High | Document IIS proxy configuration; create deployment scripts; test on staging first |
| Store contamination between modules | Low | High | Keep stores module-scoped; use namespaced modules; test isolation |
| PrimeVue theme conflicts | Low | Medium | Use single PrimeVue theme (saga-blue from reports-app); override in vendor CSS |
| Breaking existing E2E tests | High | Medium | Update test selectors; test both modules independently; add new tests for unified navigation |
| Shared component changes breaking both modules | Medium | High | Version shared components; add component tests; minimize changes to shared code |
Implementation Plan
Phase 1: Project Setup (0.5 days)
Tasks:
- Create directory structure in root (
.) - Create
package.jsonwith merged dependencies - Create
vite.config.jswith dual proxy and lazy loading - Create
src/main.jswith PrimeVue setup - Create
.env.examplewith environment variables - Copy
shared/frontend/tosrc/shared/ - Copy
reports-app/frontend/src/assets/css/tosrc/assets/css/
Verification:
npm installsucceedsnpm run devstarts dev server on port 3000- No build errors
Phase 2: Module Migration (1 day)
Tasks:
- Create module structure (
modules/reports/,modules/data-entry/) - Migrate Reports views to
modules/reports/views/ - Migrate Reports stores to
modules/reports/stores/ - Migrate Reports services, adapt API base URL to
/api/reports/ - Migrate Data Entry views to
modules/data-entry/views/ - Migrate Data Entry components to
modules/data-entry/components/ - Migrate Data Entry stores to
modules/data-entry/stores/ - Migrate Data Entry services, adapt API base URL to
/api/data-entry/ - Merge CSS from data-entry-app into main CSS system
Verification:
- All views render without errors
- API calls route to correct backend
- No import errors
Phase 3: Routing & Navigation (0.5 days)
Tasks:
- Create unified router in
src/router/index.js - Configure lazy loading for module layouts
- Add authentication guard
- Create
config/menu.jswith unified menu structure - Create
App.vuewith AppHeader and SlideMenu integration - Create
ReportsLayout.vuewith error boundary - Create
DataEntryLayout.vuewith error boundary - Create
ErrorBoundary.vuecomponent
Verification:
- Navigation works between all routes
- Lazy loading chunks load correctly
- Error boundary catches and displays errors
- Menu highlights active route
Phase 4: Error Boundaries & Resilience (0.25 days)
Tasks:
- Test error boundary with intentional errors
- Verify module isolation (error in one doesn't crash other)
- Add global error handler
- Test feature flags (enable/disable modules)
- Add loading states for lazy-loaded routes
Verification:
- Error boundary displays user-friendly message
- User can retry or navigate away
- Other module continues working
- Feature flags hide disabled modules
Phase 5: Build & Deployment (0.25 days)
Tasks:
- Run production build:
npm run build - Verify bundle splitting (check
dist/assets/) - Test production build locally:
npm run preview - Create IIS web.config with URL rewrite rules
- Deploy to staging IIS site
- Test all routes and API calls on staging
- Document deployment process
Verification:
- Build succeeds without errors
- Separate chunks generated (vendor-core, reports, data-entry)
- IIS deployment works
- API proxy routes correctly
- All features work in production
Testing Strategy
Unit Tests
- Store actions and mutations
- Service API methods
- Utility functions
- Error boundary component
Integration Tests
- Router navigation
- Store integration with components
- API service integration
- Error boundary with child components
E2E Tests (Playwright)
Critical Paths:
-
Login Flow
- Navigate to /login
- Enter credentials
- Verify redirect to /reports/dashboard
-
Reports Module Navigation
- Navigate to each Reports view
- Verify data loads
- Test filters and actions
-
Data Entry Module Navigation
- Navigate to receipts list
- Navigate to create receipt
- Verify form validation
-
Module Switching
- Navigate from Reports to Data Entry
- Verify state persistence (company, period)
- Navigate back to Reports
-
Error Isolation
- Trigger error in Reports module
- Verify Data Entry still works
- Retry error module
-
Logout
- Logout from any module
- Verify redirect to login
- Verify stores cleared
Performance Tests
- Lighthouse audit (target: 90+ Performance)
- Bundle size analysis (webpack-bundle-analyzer)
- Load time measurement (initial load, module switching)
Rollback Plan
If Deployment Fails
Option 1: Keep Both Apps Running (Zero Downtime)
- Leave existing
/roa2web/and/data-entry/sites running - Add new unified app at
/unified/for testing - Switch over when stable
Option 2: Quick Rollback (15 minutes)
- Keep backup of current IIS configuration
- Keep backup of current builds in
dist-backup/ - Restore IIS sites from backup
- Restore builds from backup
Git Strategy
- Create feature branch:
feature/unified-app-pragmatic-monolith - Tag last stable version before merge:
v1.0-pre-unified - Can revert to tag if needed:
git reset --hard v1.0-pre-unified
Monitoring
- Check IIS logs:
C:\inetpub\logs\LogFiles\ - Check application errors in browser console
- Monitor backend logs (both 8001 and 8003)
- Track user feedback in first 48 hours
Documentation Updates
Files to Create/Update
-
README.md (in root directory)
- Project overview
- Setup instructions
- Development commands
- Deployment guide
- Architecture overview
-
CLAUDE.md (root)
- Update architecture diagram
- Update deployment instructions
- Mark old apps as archived
- Document new root structure
-
DEPLOYMENT_GUIDE.md
- Update IIS configuration
- Update build process
- Update URL structure
- Add rollback instructions
-
docs/ARCHITECTURE_SCHEMA.md
- Update architecture diagrams
- Document module structure
- Add error boundary architecture
-
deployment/windows/README.md
- Update deployment steps
- Update IIS configuration
- Update proxy rules
Post-Implementation Tasks
Archiving Old Apps
After successful deployment and 1 week of stability:
-
Archive old frontends:
mv reports-app/frontend reports-app/frontend-archived mv data-entry-app/frontend data-entry-app/frontend-archived -
Update start scripts to use root directory:
# ./start-test.sh (update frontend path to root) # ./start-data-entry.sh (update frontend path to root) -
Update CI/CD pipelines (if any)
-
Document migration in CHANGELOG.md
Monitoring & Optimization
First Week:
- Monitor error logs daily
- Track bundle load times
- Gather user feedback
- Fix critical bugs
First Month:
- Optimize bundle sizes
- Add performance monitoring
- Consider further code splitting
- Evaluate feature flag usage
Dependencies on Other Work
Prerequisites
- None - can start immediately
Blocking
- No other work blocked by this
Nice to Have (Not Required)
- Updated E2E tests (can do after deployment)
- Performance monitoring setup (can add later)
- Analytics integration (can add later)
Success Metrics
Deployment Success
- ✅ Single IIS site running instead of 2
- ✅ Single build command instead of 2
- ✅ Zero downtime during deployment
User Experience
- ✅ 100% feature parity with old apps
- ✅ < 2s initial load time
- ✅ < 500ms module switching (cached)
- ✅ Zero user-reported bugs in first week
Technical
- ✅ Bundle size ≤ sum of old apps
- ✅ Error isolation working (test scenarios)
- ✅ All E2E tests passing
- ✅ Lighthouse score ≥ 90
Open Questions
-
PrimeVue Theme Standardization: Use
saga-blue(reports-app) orlara-light-blue(data-entry-app)?- Recommendation: Use
saga-blue(reports-app is primary)
- Recommendation: Use
-
Feature Flag Storage: Config file or environment variables?
- Recommendation: Config file for simplicity, env vars for production override
-
Module Activation Strategy: All modules active by default or opt-in?
- Recommendation: All active by default (can disable via config)
-
Monitoring Solution: Console logs only or add Sentry/similar?
- Recommendation: Console logs for MVP, add monitoring later
-
Progressive Enhancement: Load Reports first (most used) or parallel?
- Recommendation: Load on-demand (lazy loading), whichever user navigates to first
Estimated Complexity
Medium Complexity - Justification:
Factors Increasing Complexity:
- Merging two apps with different structures
- Ensuring error isolation between modules
- CSS conflicts and PrimeVue theme differences
- IIS proxy configuration complexity
- Testing both modules thoroughly
Factors Decreasing Complexity:
- No backend changes required
- Shared components already exist
- Clear architectural pattern (pragmatic monolith)
- Lazy loading well-supported by Vue Router
- Both apps use same tech stack (Vue 3, PrimeVue)
Time Estimate: 2.5 days (as per implementation plan)
References
Related Documents
IMPLEMENTATION_PLAN_UNIFIED_APP.md- Initial plan (this spec expands on it)CLAUDE.md- Project documentation (update after implementation)docs/ONBOARDING_CSS.md- CSS system guidedocs/CSS_PATTERNS.md- Available CSS patternsdocs/ARCHITECTURE_SCHEMA.md- Current architecture (update after)
Key Files to Reference During Implementation
reports-app/frontend/vite.config.js- Proxy config, build settings (reference for new rootvite.config.js)reports-app/frontend/src/router/index.js- Router patterns (reference for newsrc/router/index.js)reports-app/frontend/src/App.vue- AppHeader/SlideMenu integration (reference for newsrc/App.vue)data-entry-app/frontend/src/App.vue- Alternative integration patternshared/frontend/components/layout/AppHeader.vue- Header API (copy tosrc/shared/components/layout/)shared/frontend/components/layout/SlideMenu.vue- Menu API (copy tosrc/shared/components/layout/)
Handover Notes
This specification is implementation-ready. All technical decisions have been made. The implementation plan provides a clear path with verification steps at each phase.
Critical Files to Create First (in root directory):
package.json- Merged dependenciesvite.config.js- Dual proxy configsrc/router/index.js- Unified router with lazy loadingsrc/shared/components/ErrorBoundary.vue- Error isolation
After Implementation:
- Archive old frontends after 1 week of stability
- Update all documentation (CLAUDE.md, DEPLOYMENT_GUIDE.md)
- Consider adding monitoring/analytics
- Optimize bundle sizes based on real usage
Specification Version: 1.0 Created: 2025-12-22 Status: Ready for Implementation Estimated Effort: 2.5 days