Files
Vaessl/CLAUDE.md
T
kasun 8184db0c46 update CLAUDE.md to reflect the sync/vector package layout
Documents the homebox/, sync/, and vector/ packages as they actually
exist now (the doc still described a since-renamed ai/ package and a
VectorStoreConfig class that was never added - PgVectorStore is
auto-configured from application.yaml). Also flags what's still
pending rather than implying it's done: HomeboxAiSearchProvider is an
unimplemented stub, SearchService doesn't route on aiSearch yet, and
/sync isn't wired to login or the frontend.
2026-07-04 01:11:07 +02:00

9.2 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Vaessl is an AI-powered integration bridge that accepts user text/image inputs, processes them through an LLM pipeline (via LiteLLM), and exports structured data to management systems (Homebox, WikiJS). The backend uses a provider pattern for extensibility. The frontend has a working connection management dashboard.

Commands

Backend (Spring Boot + Gradle, inside backend/)

./gradlew build           # compile and package
./gradlew test            # run all tests
./gradlew test --tests com.vaessl.app.connection.ConnectionServiceTest  # single test class

Frontend (React + Vite, inside frontend/)

npm run dev       # start dev server
npm run build     # TypeScript check + Vite build
npm run lint      # ESLint
npm run test      # Vitest watch mode
npm run test:ui   # Vitest visual dashboard

Environment

Copy .env.local (not committed) into backend/ with:

  • DB_URL, DB_TEST_URL, DB_USERNAME, DB_PASSWORD — PostgreSQL (test container on port 5434)
  • PG_DRIVER_CLASS_NAME — PostgreSQL JDBC driver class
  • OPENAI_KEY, OPENAI_BASE_URL — LiteLLM gateway (provider-agnostic, configured for gpt-4o-mini)
  • FRONTEND_LOCAL_URL, FRONTEND_PUBLIC_URL — allowed CORS origins for the backend

Frontend (optional, defaults to /api):

  • VITE_API_URL — backend base URL used by api/client.ts

Architecture

Backend (backend/src/main/java/com/vaessl/app/)

Package-by-feature layout. Server context path is /api. Main endpoints:

  • POST /api/login — authenticates a service, stores connection ID in session
  • GET /api/connections/status — lists connected services for the current session
  • DELETE /api/connections/{serviceType} — removes a service from the session; invalidates the session if no connections remain
  • POST /api/search — paged search against the requested service; returns 401 if no active session
  • POST /api/sync — triggers a vector-store sync for the requested service; returns 401 if no active session. Not yet triggered on login or called from the frontend — manual/internal trigger only for now.

Eight packages:

shared/ — cross-cutting types used by more than one feature package

  • ServiceType (enum): identifies each integrated app (e.g. HOMEBOX); used across connection/, search/, sync/
  • ServiceProvider (interface): base for ConnectionProvider, SearchProvider, SyncProvider; declares getServiceType()
  • ServiceItem: normalized item shape (id, title, description, extraData) returned by both search and sync fetches
  • Endpoint (enum): API path constants for all external service calls
  • SessionKeys: builds session attribute names of the form {SERVICE_TYPE}_CONNECTION_ID

config/

  • CorsConfig: env-driven allowed origins (FRONTEND_LOCAL_URL, FRONTEND_PUBLIC_URL); allowCredentials(true) is required for session cookies to work cross-origin
  • SessionConfig: JDBC-backed Spring Session with a persistent cookie (SameSite=Lax, HttpOnly)

connection/ — connecting to and persisting service credentials

  • ConnectionProvider interface: extends ServiceProvider; each integrated app implements login() and credential checking
  • ConnectionIdentifiable interface: appUrl()/username()/serviceType(); implemented by SearchRequest and SyncRequest so HomeboxItemClient can resolve the underlying connection regardless of which feature is calling it
  • ConnectionService: auto-discovers providers via Spring injection, dispatches login by ServiceType
  • ConnectionController: stores {serviceType}_CONNECTION_ID in HttpSession after login; reads session attributes to build status responses
  • Entity (ConnectionEntity) uses Single Table Inheritance — one connections table with app-specific nullable columns
  • HomeboxConnectionProvider / HomeboxConnectionEntity: Homebox-specific implementation

homebox/ — shared Homebox API access

  • HomeboxItemClient: fetches a page of items from the Homebox entities API and maps them to ServiceItem; used by both HomeboxSearchProvider (keyword search) and HomeboxSyncProvider (vector-store sync) so the fetch/mapping logic isn't duplicated

vector/ — vectorization

  • EmbeddingService: embeds ServiceItems into pgvector's VectorStore and prunes entries that no longer exist upstream. Document ID is {connectionId}:{itemId}, so re-syncing upserts existing items instead of duplicating them. Deliberately stateless (only field is the injected VectorStore) since it's a singleton bean — the per-sync ID accumulator used for pruning is owned by the caller (HomeboxSyncProvider), not held as instance state
  • PgVectorStore itself is auto-configured by spring-ai-starter-vector-store-pgvector from application.yaml (spring.ai.vectorstore.pgvector.dimensions) — no manual config class in this codebase

search/ — querying connected services (keyword and AI)

  • SearchProvider interface: extends ServiceProvider; each integrated app implements getSearchResults()
  • SearchService: dispatches by ServiceType via a single provider map — does not yet route on SearchRequest.aiSearch; HomeboxSearchProvider and HomeboxAiSearchProvider both currently register for ServiceType.HOMEBOX, so only one wins the registration (known gap, pending AI search work)
  • SearchController: guards with session check before delegating to SearchService
  • SearchRequest: includes aiSearch: boolean (not yet consumed, see above) and implements ConnectionIdentifiable
  • PagedSearchResponse: includes nullable summary field — populated only for AI search results; null for keyword search
  • HomeboxSearchProvider: keyword search; delegates the remote fetch to HomeboxItemClient
  • HomeboxAiSearchProvider: stub onlygetSearchResults throws UnsupportedOperationException; the actual similarity search + summary generation is pending

sync/ — indexing connected services into the vector store

  • SyncProvider interface: extends ServiceProvider; each integrated app implements syncVectorStore()
  • SyncService: dispatches by ServiceType via a provider map, same pattern as SearchService
  • SyncController: session-gated POST /sync; not currently called by the frontend or triggered on login
  • SyncRequest: implements ConnectionIdentifiable
  • HomeboxSyncProvider: pages through the full Homebox catalog via HomeboxItemClient, embedding each page through EmbeddingService.vectorizeData and collecting every item ID seen along the way. Once the full page loop finishes, calls EmbeddingService.deleteStaleVectorEntries once with the complete ID set, removing any previously indexed item no longer present upstream. Order matters — pruning per page instead of once at the end would treat items on other pages as stale and delete them too.

Pending work (not yet implemented): HomeboxAiSearchProvider's actual similarity search + summary generation; SearchService routing two provider maps by aiSearch; triggering /sync on login or from the frontend; a frontend sync button and AI search UI.

exception/GlobalExceptionHandler via @ControllerAdvice

Frontend (frontend/src/)

React 19 + TypeScript + SCSS, Vite 6 build. Package-by-feature under components/.

  • api/client.ts — typed apiFetch wrapper; always sends credentials: 'include' for session cookies; base URL from VITE_API_URL (defaults to /api)
  • api/connections.ts — connection-specific API calls (getStatuses, login, logout)
  • api/searches.ts — search API call (search)
  • types/connection.tsServiceType, LoginRequest, AuthResponse, ConnectionStatus
  • types/search.tsSearchRequest, SearchResponse, PagedSearchResponse
  • components/connections/Dashboard, ConnectModal, ServiceCard (connection management UI)
  • components/search/SearchModal — search dialog; posts to /api/search and renders paged results
  • components/ui/ — shared UI primitives (ActionButton, Modal)

Data & AI

  • PostgreSQL + pgvector (semantic search via embeddings); also used as the Spring Session store (JDBC)
  • LiteLLM as a unified AI proxy; Spring AI OpenAI starter wired to it — OPENAI_BASE_URL points to LiteLLM, not OpenAI directly, keeping the underlying model provider configurable
  • spring-ai-starter-vector-store-pgvector provides PgVectorStore, auto-configured from application.yaml (no manual config class)
  • Embedding dimensions must stay consistent with the configured LiteLLM embedding model — changing models requires re-syncing all indexed items
  • Processing pipeline (Phase 2): stage in DB → LLM inference → refine via UI → export to target app

Testing Strategy

Integration tests spin up a mirrored PostgreSQL container on port 5434 (same schema as production). WireMock mocks external HTTP APIs (Homebox, WikiJS). Do not mock the database in integration tests — the mirrored container strategy exists specifically to catch schema/migration divergence.

Chat Operations

Don't make code suggestions and changes unless explicitly asked. Treat every prompt as a discussion of latest best practice coding approaches.