GroundNote is a full-stack, offline-first field intelligence demo built to showcase senior-level product + engineering ownership across:
- multimodal capture (text, image, voice),
- AI structuring of unstructured field data,
- resilient offline/online sync,
- production-minded API and frontend architecture,
- polished enterprise UX for operations teams.
The project is intentionally designed as a portfolio-grade reference for modern AI-enabled line-of-business applications.
- AI-assisted field workflows: convert raw observations into structured records (title, summary, category, priority, actions).
- Multimodal ingestion: capture from text, image, and audio endpoints.
- Offline-first UX: local outbox + cache in IndexedDB, with idempotent batch sync.
- Corporate-grade frontend: Angular app shell, authenticated routes, dashboard cards, queue visibility, and clean data-dense screens.
- Pragmatic backend design: Spring Boot API with clear service boundaries, DTOs, validation, centralized exception handling, health endpoints, and OpenAPI docs.
- Java 25
- Spring Boot 4.0.5
- Spring AI 2.0.0-M7
- Spring Data JPA + H2 (in-memory for demo)
- Spring Actuator
- Springdoc OpenAPI (Swagger UI)
- Maven
- Angular 22.0.0-rc.1 (standalone components)
- TypeScript 6
- RxJS
- Dexie (IndexedDB)
- Angular Service Worker (PWA support)
- SCSS design system (tokens + reusable UI primitives)
groundnote/
backend/ # Spring Boot + Spring AI API
frontend/ # Angular PWA app
Key frontend modules:
core/services: API, auth, connectivity, offline store, sync orchestrationfeatures/login: sign-in flowfeatures/notes: dashboard list + note detailfeatures/capture: text/photo/voice capture flowshared/*: icons, stat cards, empty states
Key backend modules:
api: REST controllersservice: orchestration + business logicai: demo and OpenAI-based structuring/transcription implementationsdomain/repository: JPA entities + repositoriesdto: request/response contractsexception: global API exception mapping
flowchart LR
U[Field User] --> FE[Angular PWA]
FE -->|/api via proxy| BE[Spring Boot API]
FE --> IDB[(IndexedDB<br/>Dexie outbox + cache)]
BE --> DB[(H2 Demo DB)]
BE --> AI[AI Structuring Port]
AI --> DEMO[Demo Heuristic Service]
AI --> OPENAI[OpenAI Service<br/>Spring AI]
BE --> MEDIA[(Local Media Storage)]
flowchart TB
subgraph Frontend
APP[App Shell + Routes]
AUTH[AuthService + AuthGuard]
CAP[Capture Feature]
NOTES[Notes Dashboard]
SYNC[SyncService]
STORE[OfflineStoreService]
API[GroundNoteApiService]
end
APP --> AUTH
APP --> CAP
APP --> NOTES
CAP --> STORE
CAP --> API
NOTES --> API
NOTES --> STORE
SYNC --> STORE
SYNC --> API
GroundNote supports two backend AI modes:
-
demoprofile (default)
Uses deterministic heuristics for structuring so the app works without external API keys. -
openaiprofile
Uses Spring AI + OpenAI for richer structuring and audio transcription.
Configuration is in:
backend/src/main/resources/application.ymlbackend/src/main/resources/application-demo.yml
To run OpenAI mode, set:
OPENAI_API_KEY=<your key>- active profile:
openai
- Java 25
- Maven 3.9+
- Node 22.22.3+
- npm (bundled with Node)
cd backend
mvn spring-boot:run -Dspring-boot.run.profiles=demo -Dmaven.test.skip=trueBackend runs on: http://localhost:8080
Useful URLs:
- Swagger UI:
http://localhost:8080/swagger-ui.html - OpenAPI docs:
http://localhost:8080/api-docs - Health:
http://localhost:8080/api/v1/system/health
cd frontend
npm install
npm startFrontend runs on: http://localhost:4200
The Angular dev server proxies /api to http://localhost:8080 via frontend/proxy.conf.json.
The frontend includes a demo auth layer (session-backed):
admin@groundnote.dev/demo1234inspector@groundnote.dev/demo1234
Logout always redirects to /login.
GET /api/v1/notes- list notesGET /api/v1/notes/{id}- get one notePOST /api/v1/notes- create structured note from raw textDELETE /api/v1/notes/{id}- delete note
POST /api/v1/ai/structure- preview structure without persistingPOST /api/v1/ai/capture/textPOST /api/v1/ai/capture/image(multipart/form-data)POST /api/v1/ai/capture/audio(multipart/form-data)
POST /api/v1/sync/batch- idempotent outbox sync byclientId
GET /api/v1/system/health
Frontend stores:
- outbox entries (pending captures),
- notes cache (last known server records),
in IndexedDB via Dexie.
When online, sync pushes pending entries in batch and refreshes server-backed notes.
Backend sync behavior is idempotent using client-generated clientId values to prevent duplicates on retries.
sequenceDiagram
participant User
participant FE as Angular Capture UI
participant IDB as IndexedDB Outbox
participant SYNC as SyncService
participant API as Spring Boot API
participant DB as Database
User->>FE: Capture note (text/image/voice)
alt Offline
FE->>IDB: enqueue(outbox item, clientId)
FE-->>User: "Saved locally"
else Online
FE->>API: create/capture request
API->>DB: persist structured note
API-->>FE: created note
end
Note over SYNC,IDB: Connectivity restored or manual sync
SYNC->>IDB: list pending outbox
SYNC->>API: POST /api/v1/sync/batch
API->>DB: upsert by clientId (idempotent)
API-->>SYNC: synced + failed clientIds
SYNC->>IDB: mark synced / keep failed
SYNC->>API: GET /api/v1/notes
API-->>SYNC: latest notes
SYNC->>IDB: refresh notes cache
SYNC-->>User: Updated dashboard + queue
cd backend
mvn test -Dspring.profiles.active=democd frontend
npm run build -- --configuration=productionThe production Angular build currently passes with bundle budgets configured in frontend/angular.json.
GroundNote includes:
- service worker config (
frontend/ngsw-config.json) - web manifest (
frontend/public/manifest.webmanifest) - icons in
frontend/public/icons/
To test installability behavior, run a production build/serve context (not plain dev mode).
GroundNote includes an optional docker profile with production-oriented building blocks:
| Capability | Implementation |
|---|---|
| Auth + RBAC | JWT login (POST /api/v1/auth/login), roles INSPECTOR, SUPERVISOR, ADMIN, Spring Security on /api/v1/** |
| Database | PostgreSQL 16 + Flyway migrations (backend/src/main/resources/db/migration/) |
| Async AI pipeline | RabbitMQ queue + DLQ, processingStatus on notes (QUEUED → PROCESSING → COMPLETED / FAILED) |
| Observability | JSON logs with traceId (MDC), Prometheus metrics, Grafana datasource provisioning, Sentry DSN hook |
| Media | Local disk (local profile) or MinIO/S3 (s3 profile), MIME/size validation, virus-scan hook |
| Password | Role | |
|---|---|---|
admin@groundnote.dev |
demo1234 |
ADMIN |
supervisor@groundnote.dev |
demo1234 |
SUPERVISOR |
inspector@groundnote.dev |
demo1234 |
INSPECTOR |
The Angular app calls the login API and sends Authorization: Bearer <token> on subsequent requests.
docker compose up -d
cd backend
set JAVA_HOME=C:\Program Files\Java\jdk-25.0.2
mvn spring-boot:run -Dspring-boot.run.profiles=demo,dockerServices: Postgres :5432, RabbitMQ :5672 (UI :15672), MinIO :9000, Prometheus :9090, Grafana :3000.
demo,local(default): H2 in-memory, local media, sync login, RabbitMQ disableddemo,docker: Postgres, Flyway, RabbitMQ workers, MinIO object storage
flowchart LR
FE[Angular PWA] -->|JWT| API[Spring Boot API]
API --> PG[(PostgreSQL)]
API --> Q[RabbitMQ]
Q --> W[Note processing worker]
W --> AI[AI structuring]
API --> S3[(MinIO / S3)]
API --> PROM[Prometheus]
GroundNote is not just CRUD + UI polish. It demonstrates:
- system thinking across product, UX, backend, frontend, and AI integration,
- reliability under poor connectivity constraints,
- practical tradeoffs between demo-mode determinism and real model providers,
- enterprise-friendly UX patterns for operational users.
This project is licensed under the MIT License.