feat: Migrate to ultrathin monolith architecture
Consolidate 3 separate applications (reports-app, data-entry-app, telegram-bot) into a unified
architecture with single backend and frontend:
Backend Changes:
- Unified FastAPI backend at backend/ with modular structure
- Modules: reports, data_entry, telegram in backend/modules/
- Centralized config.py and main.py with all routers registered
- Single worker mode (--workers 1) for Telegram bot compatibility
- Shared Oracle connection pool and JWT authentication
- Unified requirements.txt and environment configuration
Frontend Changes:
- Single Vue.js SPA with module-based routing
- Unified frontend at src/ with modules in src/modules/{reports,data-entry}/
- Shared components and stores in src/shared/
- Error boundaries for module isolation
- Dual API proxy in Vite for module communication
Infrastructure:
- New unified startup scripts: start-prod.sh, start-test.sh, start-backend.sh
- Environment templates: .env.dev.example, .env.test.example, .env.prod.example
- Updated deployment scripts for Windows IIS
- Simplified SSH tunnel management
Documentation:
- Comprehensive CLAUDE.md with architecture overview
- Module-specific docs in docs/{data-entry,telegram}/
- Architecture decision records in docs/ARCHITECTURE-DECISIONS.md
- Deployment guides consolidated in deployment/windows/docs/
This migration reduces complexity, improves maintainability, and enables easier
deployment while maintaining all existing functionality.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,115 +0,0 @@
|
||||
{
|
||||
"gotchas": [
|
||||
{
|
||||
"id": "got_20251222_182000",
|
||||
"timestamp": "2025-12-22T18:20:00Z",
|
||||
"title": "Import Path Hell: Default vs Named Exports",
|
||||
"problem": "Build failed with 'apiService is not exported' errors even though the module exports default api. Legacy code was using import { apiService } from 'api.js' which doesn't work with export default api.",
|
||||
"solution": "Changed all imports from import { apiService } to import api, then updated all references from apiService.get to api.get. Also renamed imports to avoid conflicts (e.g., import apiClient from 'api').",
|
||||
"context": "Encountered during build when migrating stores that expected named exports but API services used default exports",
|
||||
"tags": ["javascript", "imports", "exports", "build-errors", "migration"],
|
||||
"feature": "unified-app"
|
||||
},
|
||||
{
|
||||
"id": "got_20251222_182001",
|
||||
"timestamp": "2025-12-22T18:20:01Z",
|
||||
"title": "Pinia Store Factory Pattern Not Auto-Exported",
|
||||
"problem": "Build failed with 'useCompanyStore is not exported by companies.js' because the shared stores are factory functions (createCompaniesStore), not direct exports (useCompanyStore).",
|
||||
"solution": "Created module-specific sharedStores.js files that instantiate the factory functions with the module's API service, then export the actual store instances. Components import from module's sharedStores.js, not from @shared directly.",
|
||||
"context": "Shared stores were designed as factories but modules were trying to import them as if they were regular stores",
|
||||
"tags": ["pinia", "stores", "factory-pattern", "imports", "architecture"],
|
||||
"feature": "unified-app"
|
||||
},
|
||||
{
|
||||
"id": "got_20251222_182002",
|
||||
"timestamp": "2025-12-22T18:20:02Z",
|
||||
"title": "Sed Command Quote Mismatch in Bulk Find-Replace",
|
||||
"problem": "Bulk sed commands using single quotes in pattern didn't match imports using double quotes, and vice versa. Commands like sed 's|from '@/stores/'|...' didn't replace from \"@/stores/\" lines.",
|
||||
"solution": "Always use the quote style that matches the target files. For Vue/JS files with ESLint using double quotes, use double quotes in sed patterns. Better yet: use find -exec with separate sed for each file to handle both quote styles.",
|
||||
"context": "Spent significant time debugging why sed replacements weren't working during mass import path updates",
|
||||
"tags": ["sed", "regex", "scripting", "find-replace", "migration"],
|
||||
"feature": "unified-app"
|
||||
},
|
||||
{
|
||||
"id": "got_20251222_182003",
|
||||
"timestamp": "2025-12-22T18:20:03Z",
|
||||
"title": "Circular Reference in API Wrapper",
|
||||
"problem": "receiptsStore.js failed to build with 'Identifier api has already been declared' because it imported api and then declared const api = { ... } wrapper object using the same name.",
|
||||
"solution": "Renamed the import to apiClient (import apiClient from 'api') and used it in the wrapper: const api = { get: (url) => apiClient.get('/receipts${url}') }. This keeps the wrapper name 'api' for internal use while avoiding the conflict.",
|
||||
"context": "Store was creating a scoped API wrapper for DRY principle but shadowed the import name",
|
||||
"tags": ["javascript", "naming", "scope", "imports", "build-errors"],
|
||||
"feature": "unified-app"
|
||||
},
|
||||
{
|
||||
"id": "got_20251222_182004",
|
||||
"timestamp": "2025-12-22T18:20:04Z",
|
||||
"title": "CSS Import Paths Breaking Build in Unified Structure",
|
||||
"problem": "Build failed with 'Unable to resolve @import \"../../../../../shared/frontend/styles/layout/header.css\"' because the CSS files were copied but their import paths still pointed to old shared/frontend location.",
|
||||
"solution": "Commented out the problematic @import statements in main.css since those styles are already imported in App.vue. Alternatively, could have updated paths to use @shared alias or relative paths from new location.",
|
||||
"context": "CSS files from reports-app referenced shared styles using relative paths that became invalid in unified structure",
|
||||
"tags": ["css", "imports", "build-errors", "migration", "paths"],
|
||||
"feature": "unified-app"
|
||||
},
|
||||
{
|
||||
"id": "got_20251222_182005",
|
||||
"timestamp": "2025-12-22T18:20:05Z",
|
||||
"title": "Module Component Utilities Not Copied During Migration",
|
||||
"problem": "Build failed with 'Could not resolve ../utils/exportUtils' because views referenced utils/ and components/ directories that weren't copied during initial migration (only views and stores were copied).",
|
||||
"solution": "Copied the entire utils/ and components/ directories from source apps to module directories. These supporting files are essential dependencies of the views and must be migrated together.",
|
||||
"context": "Initial migration focused on views/stores but missed the supporting utilities and components they depend on",
|
||||
"tags": ["migration", "dependencies", "file-structure", "build-errors"],
|
||||
"feature": "unified-app"
|
||||
},
|
||||
{
|
||||
"id": "got_20251222_182006",
|
||||
"timestamp": "2025-12-22T18:20:06Z",
|
||||
"title": "Vite Build Transform Count is Progress Indicator",
|
||||
"problem": "Hard to tell if build is making progress when fixing import issues. Each fix revealed new errors, causing frustration.",
|
||||
"solution": "Watch the 'transforming... ✓ N modules transformed' count - it increases with each successful fix even if build ultimately fails. Going from 200→573→1490→1492 modules meant we were getting close to success. Use this as encouragement!",
|
||||
"context": "During iterative import path fixing, the transform count showed we were making real progress toward a successful build",
|
||||
"tags": ["vite", "build", "debugging", "progress-tracking", "developer-experience"],
|
||||
"feature": "unified-app"
|
||||
},
|
||||
{
|
||||
"id": "got_20251224_001000",
|
||||
"timestamp": "2025-12-24T00:10:00Z",
|
||||
"title": "Menu Structure Mismatch: Flat Array vs Nested Sections",
|
||||
"problem": "Hamburger menu appeared completely empty (no menu items visible) even though enabledMenuItems computed property returned data. Used .flatMap() to create flat array [{item1}, {item2}] but SlideMenu component expected nested structure [{title: 'Section', items: [...]}, ...].",
|
||||
"solution": "Removed .flatMap() transformation and returned the nested structure directly from getEnabledMenuSections(). Component's v-for=\"section in menuItems\" now properly iterates over sections, then v-for=\"item in section.items\" shows all items.",
|
||||
"context": "User reported 'MENIUL HAMBURGER ARE IN CONTINUARE TEXT ALB PE FUNDAL ALB' and provided screenshot showing menu with only user profile at bottom, no menu sections/items visible",
|
||||
"tags": ["vue", "data-structure", "component-contract", "v-for", "ux"],
|
||||
"feature": "unified-app-ux"
|
||||
},
|
||||
{
|
||||
"id": "got_20251224_001001",
|
||||
"timestamp": "2025-12-24T00:10:01Z",
|
||||
"title": "TypeError: useAuthStore is not a function - Store Timing Issue",
|
||||
"problem": "Period store threw 'TypeError: useAuthStore is not a function' when trying to call useAuthStore() in getStorageKey() function. Stores were passed as factory parameters but weren't callable in that context/timing.",
|
||||
"solution": "Wrap store access in try-catch with lazy instantiation. Call useAuthStore() inside the function that needs it, not at module level. Return null if stores aren't ready yet. This allows graceful degradation when stores haven't been initialized by Pinia yet.",
|
||||
"context": "accountingPeriod.js tried to access auth/company stores to generate localStorage key for persisting user's period selection",
|
||||
"tags": ["pinia", "stores", "timing", "initialization", "error-handling"],
|
||||
"feature": "unified-app-ux"
|
||||
},
|
||||
{
|
||||
"id": "got_20251224_001002",
|
||||
"timestamp": "2025-12-24T00:10:02Z",
|
||||
"title": "Missing Auth Token in API Requests Causes 500 Errors",
|
||||
"problem": "Backend returned 500 Internal Server Error when frontend tried to load accounting periods. Console showed no Authorization header in requests even though user was logged in and JWT token existed in localStorage.",
|
||||
"solution": "Add axios request interceptor to automatically inject token: authApi.interceptors.request.use(config => { const token = localStorage.getItem('access_token'); if (token) config.headers.Authorization = `Bearer ${token}`; return config; }). Place this AFTER creating axios instance but BEFORE making any API calls.",
|
||||
"context": "App.vue created authApi axios instance with only baseURL and Content-Type, but didn't configure automatic token injection from localStorage",
|
||||
"tags": ["axios", "jwt", "authentication", "api", "interceptor"],
|
||||
"feature": "unified-app-ux"
|
||||
},
|
||||
{
|
||||
"id": "got_20251224_001003",
|
||||
"timestamp": "2025-12-24T00:10:03Z",
|
||||
"title": "Period Auto-Load Never Triggered Despite Handler Exists",
|
||||
"problem": "Period dropdown stayed on 'Selectare perioada' placeholder even after manually selecting company. handleCompanyChanged() function existed and logged messages, but periods.value and selectedPeriod.value remained empty. No automatic loading occurred.",
|
||||
"solution": "Add Vue watch() on companyStore.selectedCompany to automatically call periodStore.loadPeriods() when company changes. Handler alone isn't enough - need reactive watcher with { immediate: true } to handle both initial load and subsequent changes. Watch triggers for ALL company changes (auto-select on login + manual selection).",
|
||||
"context": "User explicitly reported 'dar si dupa ce aleg o firma MARIUS M AUTO, AR TREBUI SA SE SELECTEZE AUTOMAT ULTIMA LUNA, SI NU SE SELECTEAZA' after manually selecting company",
|
||||
"tags": ["vue", "watch", "reactive", "auto-load", "ux"],
|
||||
"feature": "unified-app-ux"
|
||||
}
|
||||
],
|
||||
"updated": "2025-12-24T00:10:00Z"
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
{
|
||||
"patterns": [
|
||||
{
|
||||
"id": "pat_20251222_182000",
|
||||
"timestamp": "2025-12-22T18:20:00Z",
|
||||
"title": "Unified Vue SPA with Module Isolation via Error Boundaries",
|
||||
"description": "Consolidate multiple Vue apps into a single SPA using lazy-loaded modules wrapped in error boundaries. Each module has its own layout component with ErrorBoundary wrapper to prevent crashes from propagating across modules.",
|
||||
"context": "Implemented while unifying Reports App and Data Entry App into single deployment",
|
||||
"example": {
|
||||
"file": "src/modules/reports/ReportsLayout.vue",
|
||||
"lines": "1-7",
|
||||
"snippet": "<template>\n <ErrorBoundary module-name=\"Rapoarte\">\n <router-view />\n </ErrorBoundary>\n</template>\n\n<script setup>\nimport ErrorBoundary from '@shared/components/ErrorBoundary.vue'\n</script>"
|
||||
},
|
||||
"tags": ["vue", "spa", "error-boundary", "module-isolation", "architecture"],
|
||||
"feature": "unified-app",
|
||||
"usageCount": 0
|
||||
},
|
||||
{
|
||||
"id": "pat_20251222_182001",
|
||||
"timestamp": "2025-12-22T18:20:01Z",
|
||||
"title": "Dual API Proxy Pattern in Vite for Microservices",
|
||||
"description": "Configure Vite dev server to proxy multiple backend microservices under different paths. Allows unified frontend to communicate with separate backend services while maintaining CORS and authentication.",
|
||||
"context": "Needed to route /api/reports to port 8001 and /api/data-entry to port 8003 from single frontend",
|
||||
"example": {
|
||||
"file": "vite.config.js",
|
||||
"lines": "38-62",
|
||||
"snippet": "proxy: {\n '/api/reports': {\n target: 'http://localhost:8001',\n changeOrigin: true,\n rewrite: (path) => path.replace(/^\\/api\\/reports/, '/api'),\n configure: (proxy) => {\n proxy.on('proxyReq', (proxyReq, req) => {\n if (req.headers.authorization) {\n proxyReq.setHeader('Authorization', req.headers.authorization);\n }\n });\n }\n },\n '/api/data-entry': {\n target: 'http://localhost:8003',\n changeOrigin: true,\n rewrite: (path) => path.replace(/^\\/api\\/data-entry/, '/api')\n }\n}"
|
||||
},
|
||||
"tags": ["vite", "proxy", "microservices", "api", "configuration"],
|
||||
"feature": "unified-app",
|
||||
"usageCount": 0
|
||||
},
|
||||
{
|
||||
"id": "pat_20251222_182002",
|
||||
"timestamp": "2025-12-22T18:20:02Z",
|
||||
"title": "Pinia Store Factory Pattern for Shared Stores",
|
||||
"description": "Create shared Pinia stores as factory functions that accept API service instances. Each module instantiates the shared stores with its own API service, ensuring proper module isolation while sharing store logic.",
|
||||
"context": "Auth, companies, and accounting period stores needed to work with both Reports (port 8001) and Data Entry (port 8003) APIs",
|
||||
"example": {
|
||||
"file": "src/shared/stores/auth.js",
|
||||
"lines": "21-32",
|
||||
"snippet": "export function createAuthStore(apiService) {\n return defineStore('auth', () => {\n const accessToken = ref(localStorage.getItem('access_token'))\n // ... state\n\n const login = async (credentials) => {\n const response = await apiService.post('/auth/login', credentials)\n // ... handle response\n }\n\n return { login, logout, isAuthenticated, currentUser }\n })\n}"
|
||||
},
|
||||
"tags": ["pinia", "stores", "factory-pattern", "module-isolation", "vue"],
|
||||
"feature": "unified-app",
|
||||
"usageCount": 0
|
||||
},
|
||||
{
|
||||
"id": "pat_20251222_182003",
|
||||
"timestamp": "2025-12-22T18:20:03Z",
|
||||
"title": "Module-Specific Shared Store Instances",
|
||||
"description": "Instantiate shared store factories in each module's dedicated file to ensure proper API service binding. Prevents import confusion and ensures each module uses its own API base URL.",
|
||||
"context": "Needed to prevent modules from accidentally using wrong API service when importing shared stores",
|
||||
"example": {
|
||||
"file": "src/modules/reports/stores/sharedStores.js",
|
||||
"lines": "1-18",
|
||||
"snippet": "import { createAuthStore } from '@shared/stores/auth'\nimport { createCompaniesStore } from '@shared/stores/companies'\nimport { createAccountingPeriodStore } from '@shared/stores/accountingPeriod'\nimport api from '@reports/services/api'\n\n// Create instances with Reports API service\nexport const useAuthStore = createAuthStore(api)\nexport const useCompanyStore = createCompaniesStore(api, useAuthStore)\nexport const useAccountingPeriodStore = createAccountingPeriodStore(api)\n\n// All reports components import from this file, not directly from @shared"
|
||||
},
|
||||
"tags": ["pinia", "stores", "module-isolation", "api", "architecture"],
|
||||
"feature": "unified-app",
|
||||
"usageCount": 0
|
||||
},
|
||||
{
|
||||
"id": "pat_20251222_182004",
|
||||
"timestamp": "2025-12-22T18:20:04Z",
|
||||
"title": "Vite Alias Strategy for Module Organization",
|
||||
"description": "Use Vite path aliases to create clear module boundaries: @shared for shared code, @reports and @data-entry for module-specific code. Makes imports explicit and prevents accidental cross-module dependencies.",
|
||||
"context": "Needed clear import paths when consolidating two apps with different import patterns",
|
||||
"example": {
|
||||
"file": "vite.config.js",
|
||||
"lines": "19-26",
|
||||
"snippet": "resolve: {\n alias: {\n '@': fileURLToPath(new URL('./src', import.meta.url)),\n '@shared': fileURLToPath(new URL('./src/shared', import.meta.url)),\n '@reports': fileURLToPath(new URL('./src/modules/reports', import.meta.url)),\n '@data-entry': fileURLToPath(new URL('./src/modules/data-entry', import.meta.url))\n },\n dedupe: ['vue', 'vue-router', 'pinia', 'primevue']\n}"
|
||||
},
|
||||
"tags": ["vite", "aliases", "imports", "module-organization", "architecture"],
|
||||
"feature": "unified-app",
|
||||
"usageCount": 0
|
||||
},
|
||||
{
|
||||
"id": "pat_20251222_182005",
|
||||
"timestamp": "2025-12-22T18:20:05Z",
|
||||
"title": "IIS URL Rewrite Rules for SPA with Multiple API Backends",
|
||||
"description": "Configure IIS web.config to proxy different API paths to different backend ports while serving SPA for all other routes. Enables single IIS site to route to multiple microservices.",
|
||||
"context": "Production deployment needs unified frontend to communicate with both backend services through IIS",
|
||||
"example": {
|
||||
"file": "public/web.config",
|
||||
"lines": "5-28",
|
||||
"snippet": "<rewrite>\n <rules>\n <rule name=\"Proxy Reports API\" stopProcessing=\"true\">\n <match url=\"^api/reports/(.*)\" />\n <action type=\"Rewrite\" url=\"http://localhost:8001/api/{R:1}\" />\n </rule>\n <rule name=\"Proxy Data Entry API\" stopProcessing=\"true\">\n <match url=\"^api/data-entry/(.*)\" />\n <action type=\"Rewrite\" url=\"http://localhost:8003/api/{R:1}\" />\n </rule>\n <rule name=\"SPA Fallback\" stopProcessing=\"true\">\n <match url=\".*\" />\n <conditions logicalGrouping=\"MatchAll\">\n <add input=\"{REQUEST_FILENAME}\" matchType=\"IsFile\" negate=\"true\" />\n </conditions>\n <action type=\"Rewrite\" url=\"/index.html\" />\n </rule>\n </rules>\n</rewrite>"
|
||||
},
|
||||
"tags": ["iis", "deployment", "spa", "microservices", "proxy"],
|
||||
"feature": "unified-app",
|
||||
"usageCount": 0
|
||||
},
|
||||
{
|
||||
"id": "pat_20251224_001000",
|
||||
"timestamp": "2025-12-24T00:10:00Z",
|
||||
"title": "Vue Watcher for Auto-Loading Dependent Data",
|
||||
"description": "Use Vue watch() to automatically trigger data loading when dependent selections change. Watch company selection changes to auto-load accounting periods, ensuring UI stays synchronized without manual intervention.",
|
||||
"context": "Users manually selected company but period dropdown stayed on placeholder instead of auto-selecting current period",
|
||||
"example": {
|
||||
"file": "src/App.vue",
|
||||
"lines": "88-100",
|
||||
"snippet": "watch(\n () => companyStore.selectedCompany,\n async (newCompany, oldCompany) => {\n if (newCompany && newCompany.id_firma && newCompany !== oldCompany) {\n console.log('[App] Company changed via watch, loading periods for:', newCompany.id_firma)\n await periodStore.loadPeriods(newCompany.id_firma)\n console.log('[App] Periods auto-loaded successfully')\n }\n },\n { immediate: true }\n)"
|
||||
},
|
||||
"tags": ["vue", "watch", "reactive", "auto-load", "ux"],
|
||||
"feature": "unified-app-ux",
|
||||
"usageCount": 0
|
||||
},
|
||||
{
|
||||
"id": "pat_20251224_001001",
|
||||
"timestamp": "2025-12-24T00:10:01Z",
|
||||
"title": "Axios Request Interceptor for JWT Token Injection",
|
||||
"description": "Add axios request interceptor to automatically inject JWT Bearer token from localStorage into all API requests. Eliminates manual token handling in every API call and prevents 401/500 authentication errors.",
|
||||
"context": "API requests were failing with 500 errors because JWT token wasn't being sent in Authorization header",
|
||||
"example": {
|
||||
"file": "src/App.vue",
|
||||
"lines": "61-68",
|
||||
"snippet": "authApi.interceptors.request.use(config => {\n const token = localStorage.getItem('access_token')\n if (token) {\n config.headers.Authorization = `Bearer ${token}`\n }\n return config\n})"
|
||||
},
|
||||
"tags": ["axios", "jwt", "authentication", "interceptor", "api"],
|
||||
"feature": "unified-app-ux",
|
||||
"usageCount": 0
|
||||
},
|
||||
{
|
||||
"id": "pat_20251224_001002",
|
||||
"timestamp": "2025-12-24T00:10:02Z",
|
||||
"title": "Pinia Store Factory with Lazy Instantiation",
|
||||
"description": "When store factories need to access other stores, use lazy instantiation with try-catch to avoid timing issues. Access stores inside functions (not at module level) and gracefully handle cases where stores aren't ready yet.",
|
||||
"context": "Period store tried to access auth/company stores for localStorage key generation but got 'useAuthStore is not a function' error",
|
||||
"example": {
|
||||
"file": "src/shared/stores/accountingPeriod.js",
|
||||
"lines": "52-64",
|
||||
"snippet": "const getStorageKey = () => {\n try {\n const authStore = useAuthStore();\n const companyStore = useCompanyStore();\n const username = authStore.user?.username;\n const companyId = companyStore.selectedCompany?.id_firma;\n if (!username || !companyId) return null;\n return `selected_period_${username}_${companyId}`;\n } catch (e) {\n // Stores not yet initialized, skip localStorage\n return null;\n }\n};"
|
||||
},
|
||||
"tags": ["pinia", "stores", "lazy-initialization", "try-catch", "timing"],
|
||||
"feature": "unified-app-ux",
|
||||
"usageCount": 0
|
||||
}
|
||||
],
|
||||
"updated": "2025-12-24T00:10:00Z"
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
{
|
||||
"session_id": "ses_20251222_182000",
|
||||
"timestamp": "2025-12-22T18:20:00Z",
|
||||
"feature": "unified-app",
|
||||
"duration": "2h 30m",
|
||||
"insights_saved": [
|
||||
{"type": "pattern", "id": "pat_20251222_182000", "title": "Unified Vue SPA with Module Isolation via Error Boundaries"},
|
||||
{"type": "pattern", "id": "pat_20251222_182001", "title": "Dual API Proxy Pattern in Vite for Microservices"},
|
||||
{"type": "pattern", "id": "pat_20251222_182002", "title": "Pinia Store Factory Pattern for Shared Stores"},
|
||||
{"type": "pattern", "id": "pat_20251222_182003", "title": "Module-Specific Shared Store Instances"},
|
||||
{"type": "pattern", "id": "pat_20251222_182004", "title": "Vite Alias Strategy for Module Organization"},
|
||||
{"type": "pattern", "id": "pat_20251222_182005", "title": "IIS URL Rewrite Rules for SPA with Multiple API Backends"},
|
||||
{"type": "gotcha", "id": "got_20251222_182000", "title": "Import Path Hell: Default vs Named Exports"},
|
||||
{"type": "gotcha", "id": "got_20251222_182001", "title": "Pinia Store Factory Pattern Not Auto-Exported"},
|
||||
{"type": "gotcha", "id": "got_20251222_182002", "title": "Sed Command Quote Mismatch in Bulk Find-Replace"},
|
||||
{"type": "gotcha", "id": "got_20251222_182003", "title": "Circular Reference in API Wrapper"},
|
||||
{"type": "gotcha", "id": "got_20251222_182004", "title": "CSS Import Paths Breaking Build in Unified Structure"},
|
||||
{"type": "gotcha", "id": "got_20251222_182005", "title": "Module Component Utilities Not Copied During Migration"},
|
||||
{"type": "gotcha", "id": "got_20251222_182006", "title": "Vite Build Transform Count is Progress Indicator"}
|
||||
],
|
||||
"files_created": [
|
||||
"package.json",
|
||||
"vite.config.js",
|
||||
"src/router/index.js",
|
||||
"src/config/menu.js",
|
||||
"src/config/features.js",
|
||||
"src/App.vue",
|
||||
"src/main.js",
|
||||
"src/shared/components/ErrorBoundary.vue",
|
||||
"src/modules/reports/ReportsLayout.vue",
|
||||
"src/modules/data-entry/DataEntryLayout.vue",
|
||||
"src/modules/reports/services/api.js",
|
||||
"src/modules/data-entry/services/api.js",
|
||||
"src/modules/reports/stores/sharedStores.js",
|
||||
"src/modules/data-entry/stores/sharedStores.js",
|
||||
"public/web.config",
|
||||
".env.example",
|
||||
"index.html"
|
||||
],
|
||||
"files_migrated": [
|
||||
"src/modules/reports/views/*.vue (6 files)",
|
||||
"src/modules/reports/stores/*.js (5 files)",
|
||||
"src/modules/reports/components/** (dashboard cards, layout)",
|
||||
"src/modules/reports/utils/*.js",
|
||||
"src/modules/data-entry/views/receipts/*.vue (2 files)",
|
||||
"src/modules/data-entry/components/ocr/*.vue (3 files)",
|
||||
"src/modules/data-entry/stores/receiptsStore.js",
|
||||
"src/shared/components/** (LoginView, CompanySelector, PeriodSelector, AppHeader, SlideMenu)",
|
||||
"src/shared/stores/** (auth.js, companies.js, accountingPeriod.js)",
|
||||
"src/shared/styles/**",
|
||||
"src/assets/css/** (complete CSS system from reports-app)"
|
||||
],
|
||||
"summary": "Successfully consolidated Reports App and Data Entry App into single unified SPA. Implemented module isolation via error boundaries, dual API proxy configuration, and lazy loading. Build completed successfully with 1492 modules transformed, generating 12M dist with proper code splitting.",
|
||||
"build_stats": {
|
||||
"modules_transformed": 1492,
|
||||
"bundle_size": "12M",
|
||||
"chunks_generated": 63,
|
||||
"largest_chunks": [
|
||||
"vendor-export.js (704KB)",
|
||||
"vendor-primevue.js (524KB)",
|
||||
"vendor-charts.js (207KB)"
|
||||
]
|
||||
},
|
||||
"next_steps": [
|
||||
"Test dev server with npm run dev",
|
||||
"Update App.vue to use module-specific store instances",
|
||||
"Deploy dist/ to IIS and verify API proxies",
|
||||
"Test module switching and error boundary isolation",
|
||||
"Create unified-app README documentation"
|
||||
]
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
{
|
||||
"session_id": "20251224-001000-unified-app-ux",
|
||||
"timestamp": "2025-12-24T00:10:00Z",
|
||||
"feature": "unified-app-ux",
|
||||
"branch": "feature/ab-unified-app",
|
||||
"summary": "Fixed critical UX issues: period auto-selection after company change and empty hamburger menu display",
|
||||
"goals": [
|
||||
"Fix period dropdown auto-selection when company is manually selected",
|
||||
"Fix empty hamburger menu (no menu items visible)",
|
||||
"Ensure JWT token is sent in all API requests",
|
||||
"Resolve Pinia store timing issues"
|
||||
],
|
||||
"outcomes": {
|
||||
"success": true,
|
||||
"patterns_learned": 3,
|
||||
"gotchas_encountered": 4,
|
||||
"files_modified": 4,
|
||||
"tests_created": 3,
|
||||
"commits": 1
|
||||
},
|
||||
"key_files": [
|
||||
"src/App.vue",
|
||||
"src/shared/stores/accountingPeriod.js",
|
||||
"src/shared/stores/companies.js",
|
||||
"src/shared/components/layout/AppHeader.vue"
|
||||
],
|
||||
"commits": [
|
||||
{
|
||||
"hash": "287b9a9",
|
||||
"message": "fix: Implement complete auto-selection and fix hamburger menu display",
|
||||
"files_changed": 4,
|
||||
"insertions": 97,
|
||||
"deletions": 68
|
||||
}
|
||||
],
|
||||
"technical_insights": [
|
||||
"Vue watch() with { immediate: true } is essential for reactive data loading when dependent selections change",
|
||||
"Axios request interceptors must be configured AFTER creating instance but BEFORE making API calls",
|
||||
"Pinia store factories accessing other stores require lazy instantiation with try-catch to avoid timing issues",
|
||||
"Component data structure contracts must be honored - flatMap() broke nested section structure expected by SlideMenu",
|
||||
"Handler functions alone aren't enough for reactive updates - need watchers to trigger automatic data loading"
|
||||
],
|
||||
"problems_solved": [
|
||||
{
|
||||
"problem": "Period dropdown stayed on placeholder after company selection",
|
||||
"root_cause": "Missing Vue watcher to trigger loadPeriods() and missing JWT token in API requests",
|
||||
"solution": "Added watch() on companyStore.selectedCompany + axios request interceptor for JWT token",
|
||||
"files": ["src/App.vue"]
|
||||
},
|
||||
{
|
||||
"problem": "Hamburger menu completely empty (no menu items)",
|
||||
"root_cause": "enabledMenuItems used flatMap() creating flat array but SlideMenu expected nested sections",
|
||||
"solution": "Removed flatMap() and returned nested structure directly from getEnabledMenuSections()",
|
||||
"files": ["src/App.vue"]
|
||||
},
|
||||
{
|
||||
"problem": "TypeError: useAuthStore is not a function",
|
||||
"root_cause": "Period store tried to access stores before Pinia initialization",
|
||||
"solution": "Wrapped store access in try-catch with lazy instantiation in getStorageKey()",
|
||||
"files": ["src/shared/stores/accountingPeriod.js"]
|
||||
},
|
||||
{
|
||||
"problem": "API requests returning 500 errors",
|
||||
"root_cause": "No Authorization header with JWT token in requests",
|
||||
"solution": "Added axios request interceptor to inject Bearer token from localStorage",
|
||||
"files": ["src/App.vue"]
|
||||
}
|
||||
],
|
||||
"patterns_added": [
|
||||
"pat_20251224_001000 - Vue Watcher for Auto-Loading Dependent Data",
|
||||
"pat_20251224_001001 - Axios Request Interceptor for JWT Token Injection",
|
||||
"pat_20251224_001002 - Pinia Store Factory with Lazy Instantiation"
|
||||
],
|
||||
"gotchas_added": [
|
||||
"got_20251224_001000 - Menu Structure Mismatch: Flat Array vs Nested Sections",
|
||||
"got_20251224_001001 - TypeError: useAuthStore is not a function - Store Timing Issue",
|
||||
"got_20251224_001002 - Missing Auth Token in API Requests Causes 500 Errors",
|
||||
"got_20251224_001003 - Period Auto-Load Never Triggered Despite Handler Exists"
|
||||
],
|
||||
"testing": {
|
||||
"approach": "Playwright E2E tests",
|
||||
"tests_created": [
|
||||
"tests/e2e/unified-app/manual-company-select-test.spec.js - Verified period auto-selection",
|
||||
"tests/e2e/unified-app/hamburger-menu-visual-test.spec.js - Checked for white-on-white issues",
|
||||
"tests/e2e/unified-app/hamburger-menu-items-test.spec.js - Verified menu sections/items visible"
|
||||
],
|
||||
"all_tests_passed": true,
|
||||
"cleanup": "All test files deleted per user request after verification"
|
||||
},
|
||||
"user_feedback": [
|
||||
"dar si dupa ce aleg o firma MARIUS M AUTO, AR TREBUI SA SE SELECTEZE AUTOMAT ULTIMA LUNA, SI NU SE SELECTEAZA",
|
||||
"MENIUL HAMBURGER ARE IN CONTINUARE TEXT ALB PE FUNDAL ALB",
|
||||
"iata un screenshot cu meniul hamburger complet blank",
|
||||
"commit cu toate fix-urile",
|
||||
"sterge toate testele si rapoartele si screenshot-urile"
|
||||
],
|
||||
"lessons_learned": [
|
||||
"Always use Vue watchers for reactive data dependencies, not just event handlers",
|
||||
"Component props must match expected data structure - verify with component source",
|
||||
"Pinia stores must be lazily instantiated when accessing other stores to avoid timing issues",
|
||||
"Axios interceptors are critical for automatic JWT token injection - don't rely on manual headers",
|
||||
"Test automation helps verify fixes but manual testing with real scenarios catches UX issues"
|
||||
],
|
||||
"next_steps": [],
|
||||
"tags": ["vue", "pinia", "axios", "ux", "auto-selection", "authentication", "watchers", "stores"]
|
||||
}
|
||||
Reference in New Issue
Block a user