Files
roa2web-service-auto/.claude/rules/auto-build-memory.md
Claude Agent 67b0082df0 docs: Restructure styling documentation and add theme toggle docs
- Simplify CLAUDE.md from ~460 to ~145 lines with imports
- Add Theme System section to css-design-system.md (3 modes: auto/light/dark)
- Document theme toggle UI, localStorage persistence, CSS priority order
- Add paths: frontmatter to authentication.md and company-period.md
- Update DESIGN_TOKENS.md Dark Mode section with toggle documentation
- Clean auto-build-memory.md header (remove non-existent auto-sync reference)
- Remove non-existent plugin from settings.json

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 14:58:24 +00:00

15 KiB

Learned Patterns & Gotchas

Last updated: 2025-12-24 Maintained: Manually (add new patterns/gotchas as discovered)

This file contains insights learned during feature implementations. Claude Code auto-loads this file to prevent repeating past mistakes.


Patterns

Unified Vue SPA with Module Isolation via Error Boundaries

Discovered: 2025-12-22 (feature: unified-app) 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.

Example (src/modules/reports/ReportsLayout.vue):

<template>
  <ErrorBoundary module-name="Rapoarte">
    <router-view />
  </ErrorBoundary>
</template>

<script setup>
import ErrorBoundary from '@shared/components/ErrorBoundary.vue'
</script>

Tags: vue, spa, error-boundary, module-isolation, architecture


Dual API Proxy Pattern in Vite for Microservices

Discovered: 2025-12-22 (feature: unified-app) 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.

Example (vite.config.js:38-62):

proxy: {
  '/api/reports': {
    target: 'http://localhost:8001',
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/api\/reports/, '/api'),
    configure: (proxy) => {
      proxy.on('proxyReq', (proxyReq, req) => {
        if (req.headers.authorization) {
          proxyReq.setHeader('Authorization', req.headers.authorization);
        }
      });
    }
  },
  '/api/data-entry': {
    target: 'http://localhost:8003',
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/api\/data-entry/, '/api')
  }
}

Tags: vite, proxy, microservices, api, configuration


Pinia Store Factory Pattern for Shared Stores

Discovered: 2025-12-22 (feature: unified-app) 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.

Example (src/shared/stores/auth.js:21-32):

export function createAuthStore(apiService) {
  return defineStore('auth', () => {
    const accessToken = ref(localStorage.getItem('access_token'))
    // ... state

    const login = async (credentials) => {
      const response = await apiService.post('/auth/login', credentials)
      // ... handle response
    }

    return { login, logout, isAuthenticated, currentUser }
  })
}

Tags: pinia, stores, factory-pattern, module-isolation, vue


Module-Specific Shared Store Instances

Discovered: 2025-12-22 (feature: unified-app) 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.

Example (src/modules/reports/stores/sharedStores.js:1-18):

import { createAuthStore } from '@shared/stores/auth'
import { createCompaniesStore } from '@shared/stores/companies'
import { createAccountingPeriodStore } from '@shared/stores/accountingPeriod'
import api from '@reports/services/api'

// Create instances with Reports API service
export const useAuthStore = createAuthStore(api)
export const useCompanyStore = createCompaniesStore(api, useAuthStore)
export const useAccountingPeriodStore = createAccountingPeriodStore(api)

// All reports components import from this file, not directly from @shared

Tags: pinia, stores, module-isolation, api, architecture


Vite Alias Strategy for Module Organization

Discovered: 2025-12-22 (feature: unified-app) 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.

Example (vite.config.js:19-26):

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']
}

Tags: vite, aliases, imports, module-organization, architecture


IIS URL Rewrite Rules for SPA with Multiple API Backends

Discovered: 2025-12-22 (feature: unified-app) 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.

Example (public/web.config:5-28):

<rewrite>
  <rules>
    <rule name="Proxy Reports API" stopProcessing="true">
      <match url="^api/reports/(.*)" />
      <action type="Rewrite" url="http://localhost:8001/api/{R:1}" />
    </rule>
    <rule name="Proxy Data Entry API" stopProcessing="true">
      <match url="^api/data-entry/(.*)" />
      <action type="Rewrite" url="http://localhost:8003/api/{R:1}" />
    </rule>
    <rule name="SPA Fallback" stopProcessing="true">
      <match url=".*" />
      <conditions logicalGrouping="MatchAll">
        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
      </conditions>
      <action type="Rewrite" url="/index.html" />
    </rule>
  </rules>
</rewrite>

Tags: iis, deployment, spa, microservices, proxy


Vue Watcher for Auto-Loading Dependent Data

Discovered: 2025-12-24 (feature: unified-app-ux) 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.

Example (src/App.vue:88-100):

watch(
  () => companyStore.selectedCompany,
  async (newCompany, oldCompany) => {
    if (newCompany && newCompany.id_firma && newCompany !== oldCompany) {
      console.log('[App] Company changed via watch, loading periods for:', newCompany.id_firma)
      await periodStore.loadPeriods(newCompany.id_firma)
      console.log('[App] Periods auto-loaded successfully')
    }
  },
  { immediate: true }
)

Tags: vue, watch, reactive, auto-load, ux


Axios Request Interceptor for JWT Token Injection

Discovered: 2025-12-24 (feature: unified-app-ux) 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.

Example (src/App.vue:61-68):

authApi.interceptors.request.use(config => {
  const token = localStorage.getItem('access_token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

Tags: axios, jwt, authentication, interceptor, api


Pinia Store Factory with Lazy Instantiation

Discovered: 2025-12-24 (feature: unified-app-ux) 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.

Example (src/shared/stores/accountingPeriod.js:52-64):

const getStorageKey = () => {
  try {
    const authStore = useAuthStore();
    const companyStore = useCompanyStore();
    const username = authStore.user?.username;
    const companyId = companyStore.selectedCompany?.id_firma;
    if (!username || !companyId) return null;
    return `selected_period_${username}_${companyId}`;
  } catch (e) {
    // Stores not yet initialized, skip localStorage
    return null;
  }
};

Tags: pinia, stores, lazy-initialization, try-catch, timing


Gotchas

Import Path Hell: Default vs Named Exports

Discovered: 2025-12-22 (feature: unified-app) 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').

Tags: javascript, imports, exports, build-errors, migration


Pinia Store Factory Pattern Not Auto-Exported

Discovered: 2025-12-22 (feature: unified-app) 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.

Tags: pinia, stores, factory-pattern, imports, architecture


Sed Command Quote Mismatch in Bulk Find-Replace

Discovered: 2025-12-22 (feature: unified-app) 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.

Tags: sed, regex, scripting, find-replace, migration


Circular Reference in API Wrapper

Discovered: 2025-12-22 (feature: unified-app) 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.

Tags: javascript, naming, scope, imports, build-errors


CSS Import Paths Breaking Build in Unified Structure

Discovered: 2025-12-22 (feature: unified-app) 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.

Tags: css, imports, build-errors, migration, paths


Module Component Utilities Not Copied During Migration

Discovered: 2025-12-22 (feature: unified-app) 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.

Tags: migration, dependencies, file-structure, build-errors


Vite Build Transform Count is Progress Indicator

Discovered: 2025-12-22 (feature: unified-app) 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!

Tags: vite, build, debugging, progress-tracking, developer-experience


Menu Structure Mismatch: Flat Array vs Nested Sections

Discovered: 2025-12-24 (feature: unified-app-ux) 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.

Tags: vue, data-structure, component-contract, v-for, ux


TypeError: useAuthStore is not a function - Store Timing Issue

Discovered: 2025-12-24 (feature: unified-app-ux) 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.

Tags: pinia, stores, timing, initialization, error-handling


Missing Auth Token in API Requests Causes 500 Errors

Discovered: 2025-12-24 (feature: unified-app-ux) 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.

Tags: axios, jwt, authentication, api, interceptor


Period Auto-Load Never Triggered Despite Handler Exists

Discovered: 2025-12-24 (feature: unified-app-ux) 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).

Tags: vue, watch, reactive, auto-load, ux


Memory Statistics

  • Total Patterns: 9
  • Total Gotchas: 11
  • Last Session: 2025-12-24 (unified-app-ux)
  • Sessions Recorded: 2