2026-07-26T20:00:00Z — Fix: JWKS "Read timed out" regression from the Spring Boot 4 upgrade. Symptom: ResourceAccessException: I/O error on GET request for ".../jwks/": Read timed out at runtime against a remote OIDC provider, even though the JWKS URL is reachable via curl/browser. Root cause: Spring Security 7 (Boot 4) changed the default JWKS RestOperations to a RestTemplate with a 500ms connect and 500ms read timeout (NimbusJwtDecoder.RestTemplateWithNimbusDefaultTimeouts); Spring Security 6 used effectively unbounded timeouts. A remote provider's TLS handshake + response routinely exceeds 500ms, so JWKS retrieval times out. Fix: new adapters/auth/JwksHttpClientConfig registers a JwkSetUriJwtDecoderBuilderCustomizer bean that overrides only the HTTP client with sane, configurable timeouts (RestTemplate over SimpleClientHttpRequestFactory; connect default 5000ms, read default 10000ms, tunable via app.security.jwks.connect-timeout-ms / read-timeout-ms, i.e. env APP_SECURITY_JWKS_CONNECT_TIMEOUT_MS / APP_SECURITY_JWKS_READ_TIMEOUT_MS). The rest of Boot's JWT decoder auto-configuration (issuer validation, JWS algorithm discovery) is left intact — the customizer is applied by OAuth2ResourceServerJwtConfiguration.jwtDecoderByJwkKeySetUri, which is the active path since jwk-set-uri is set. Config lives in the adapters layer (framework code permitted); no domain/application/port changes; security invariants unchanged. Tests: JwksHttpClientConfigTest (3 tests) asserting the configured timeouts are applied, that the read timeout is above the pathological 500ms default, and that the customizer applies cleanly to a real builder. .env.example + README env table document the new optional knobs. Backend: ./gradlew clean test → 1427 tests pass, 0 failures. (2026-07-26)
2026-07-26T18:00:00Z — Major: backend upgraded to Spring Boot 4.0.7 (from 3.3.13). This is a breaking major migration; resolved stack is now Spring Framework 7.0.8, Spring Security 7.0.6, Tomcat 11.0.22 (Servlet 6.1 / Jakarta EE 11), Hibernate ORM 7.2.19, Flyway 11.14.1, Jackson 3.1.4. Toolchain: Kotlin 1.9.25 → 2.2.21 (K2 compiler), Gradle 8.11.1 → 8.14.3 (wrapper + api/Dockerfile build image pinned gradle:8.14.3-jdk21), io.spring.dependency-management 1.1.6 → 1.1.7. Java baseline unchanged (21). Changes made:
- Starters (renamed in Boot 4):
spring-boot-starter-web→spring-boot-starter-webmvc;spring-boot-starter-oauth2-resource-server→spring-boot-starter-security-oauth2-resource-server. Test: addedspring-boot-starter-webmvc-test(Boot 4 modularised the slice test auto-config;@WebMvcTest/@AutoConfigureMockMvcmoved toorg.springframework.boot.webmvc.test.autoconfigure). - Flyway: Boot 4 moved Flyway auto-configuration out of the monolithic autoconfigure jar into the dedicated
org.springframework.boot:spring-boot-flywaymodule. Having onlyflyway-coreon the classpath meant migrations silently didn't run, so Hibernate schema validation failed withmissing table [action_items]. Fixed by depending onspring-boot-flyway(pullsflyway-coretransitively); keptflyway-database-postgresql. - Jackson 3 (group
com.fasterxml.jackson→tools.jackson): dependencycom.fasterxml.jackson.module:jackson-module-kotlin→tools.jackson.module:jackson-module-kotlin; migrated alldatabind/module.kotlinimports across 1 adapter, 5 services, and ~20 test files. Annotations (@JsonIgnoreProperties) correctly stay oncom.fasterxml.jackson.annotation.JacksonConfigJackson2ObjectMapperBuilderCustomizer→JsonMapperBuilderCustomizerandDeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS→EnumFeature.FAIL_ON_NUMBERS_FOR_ENUMS(moved totools.jackson.databind.cfg.EnumFeature, applied viabuilder.enable(...)).SecurityConfig401 entry point now usestools.jackson.databind.json.JsonMapper(dropped the now-unnecessaryJavaTimeModule/WRITE_DATES_AS_TIMESTAMPSsince the error body only holds strings/ints). - JSpecify nullness (Spring 7 / Jackson 3): two integration tests adjusted for newly-nullable return types —
JsonNode.get()/traversal switched to non-nullpath(...).values(), andjdbcTemplate.queryForObject(..., Int::class.java)(nowInt?) guarded with!!onCOUNT(*)results. - No application/domain logic, ports, adapters (behaviour), or Flyway migrations changed — the hexagonal boundaries are intact and still ArchUnit-enforced (19 ArchUnit tests pass). Security invariants unchanged (userId scoping, 401/404 behaviour) and covered by the passing data-isolation integration tests.
- Verification:
./gradlew clean test→ BUILD SUCCESSFUL, all 1424 tests pass, 0 failures/errors;./gradlew bootJarsucceeds. README tech-stack line updated to "Kotlin 2.2 + Spring Boot 4.0". CI (./gradlew test, JDK 21, wrapper 8.14.3) needs no change. Follow-ups not done: classic/Jackson-2 compat modules were avoided (full Jackson 3 migration done instead); consider adopting Boot 4 modular actuator/observation modules and virtual threads separately. (2026-07-26)
2026-07-26T14:00:00Z — Security: backend Spring Boot patch bump 3.3.5 → 3.3.13 (api/gradle.properties springBootVersion). Stays within the current 3.3 minor line (3.3.13 is the last 3.3.x on Maven Central), so no breaking changes, but rolls the managed transitive stack forward onto patched releases: Spring Framework 6.1.14 → 6.1.21, Spring Security 6.3.4 → 6.3.10, Tomcat embed 10.1.31 → 10.1.42 (covers the 2025 Tomcat CVEs incl. the actively-exploited CVE-2025-24813 partial-PUT RCE/info-disclosure), plus Jackson 2.17.3 and Logback 1.5.18. Also clears the post-3.3.5 Spring Framework advisories (e.g. CVE-2024-38819/38820, fixed in 3.3.6). No application source, port, adapter, or Flyway migration changes — pure dependency bump; hexagonal boundaries untouched. Verified by the full existing suite (which boots the whole upgraded framework via Spring context + Testcontainers integration tests): ./gradlew clean test → BUILD SUCCESSFUL, all 1424 tests pass. README tech-stack line updated to 3.3.13. Recommendation / not done: the 3.3.x OSS-support line has ended (3.3.13 is the last free patch); staying current long-term needs a planned migration to the supported 3.5.x line (or 4.x, which is a larger Jakarta EE 11 / Spring Framework 7 / Jackson 3 breaking upgrade). That is a dedicated, separately-tested effort and intentionally out of scope for this security patch. (2026-07-26)
2026-07-26T12:00:00Z — Fix: time-dependent backend test in AiCommandTerminalServiceTest. ./gradlew clean test failed on the case parseCommand should return parsed 1-1 entry on success (expected 2026-06-06 but got 2026-07-26). Root cause was a brittle test, not a regression: AiCommandTerminalService.parseAiResponse intentionally treats an AI-supplied 1:1 meeting_date more than ±30 days from today as a hallucination and overrides it with today's date. The test hard-coded meeting_date: "2026-06-06" (valid when authored in early June 2026, but now 50 days in the past), so the service's override kicked in and the assertion failed — a latent time-bomb that would fail on any run more than 30 days after authoring, on any branch including main. Fix: the test now uses a date relative to today (LocalDate.now().minusDays(3)) so it stays inside the 30-day window and verifies the real intent — a valid AI-provided meeting date passes through unchanged — regardless of the wall-clock date. Production code unchanged (the override behavior is correct and remains covered by the should override hallucinated meeting date with today test, which uses a fixed far-past 2024-02-20). Audited the rest of the file for similar drift: the action-item due_date case is safe (due dates are not subject to the 30-day validation), and all other date assertions already use relative today/tomorrow. Backend: full suite passes (./gradlew clean test BUILD SUCCESSFUL). No source, migration, or API changes. (2026-07-26)
2026-07-26T00:00:00Z — Security: critical frontend dependency updates. Bumped Next.js 14.2.18 → 14.2.35 (patches CVE-2025-29927, the middleware authorization-bypass via a crafted x-middleware-subrequest header — directly relevant since src/middleware.ts enforces route auth for all protected pages — plus the December 2025 React Server Components advisories fixed in the 14.2.x line). Bumped next-auth 5.0.0-beta.25 → 5.0.0-beta.32, pulling @auth/core from a vulnerable <=0.41.2 to 0.41.3 and clearing 2 critical advisories relevant to this OIDC login flow: OAuth state/nonce/PKCE check cookies not bound to the issuing provider (GHSA-x445-f3h2-j279) and getToken() throwing on malformed Bearer headers (GHSA-xmf8-cvqr-rfgj). eslint-config-next bumped to 14.2.35 to match. Added __tests__/next-security-version.test.ts — a regression guard asserting next/eslint-config-next stay at or above the patched 14.2.25 security floor so the app can never be regressed onto a middleware-bypass-vulnerable release. npm audit critical count dropped 2 → 0; remaining highs are dev-only ESLint transitive deps (brace-expansion/minimatch) that require a breaking eslint-config-next major and are out of scope. Frontend: 1260 tests pass (was 1258). next build succeeds. No application source, backend, or migration changes. Backend note: Spring Boot 3.3.5 is behind and its OSS-support line has lapsed; moving forward requires a 3.4/3.5 minor jump with breaking changes and a full backend test run (Testcontainers/Docker), so it is deferred to a dedicated, separately-verified task rather than bundled into this security patch. (2026-07-26)
2026-06-30T00:00:00Z — Fix: AI features hidden when only admin team defaults are configured. The frontend gated every AI feature on the user's personal aiEnabled toggle, but aiEnabled is false when AI is provided solely through admin env defaults (AI_DEFAULT_BASE_URL/AI_DEFAULT_MODEL). The backend already exposes a resolved aiAvailable flag (true when user config or admin defaults exist, via AiConfigResolver), so the bug was purely in the frontend gating. Switched all feature-visibility checks from settings.aiEnabled to settings.aiAvailable: AI Command Terminal (FAB + open guard), Triage hint button, 1:1 Prep Assistant + Extract Outcomes (new/detail pages), People page (Narrative button, Insights tab + button, PdpGoalList/KudosList aiEnabled props), and Strategy page (AI Suggestions Panel + StrategyGoalForm SMART Check). The Settings page keeps aiEnabled for the user's own toggle. Net effect: AI features are visible when either personal config or admin defaults are present, and hidden only when neither is. Tests: added admin-defaults visibility cases to triage page test and AiCommandTerminal test; updated existing AI mocks to carry aiAvailable. Frontend: 1258 tests pass (was 1256). next build succeeds. No backend or migration changes. (2026-06-30)
2026-06-29T15:00:00Z — Local LLM provider (Ollama) for dev/open-source AI. Added docker-compose.ai.yml overlay: an ollama server service (CPU-only by default, commented NVIDIA GPU block), a one-shot ollama-init helper that waits for the server and auto-pulls a small model (qwen2.5:1.5b by default, OLLAMA_MODEL/OLLAMA_TAG overridable) — the analog of the authentik bootstrap blueprint — and sets the api service's AI_DEFAULT_BASE_URL=http://ollama:11434/v1 / AI_DEFAULT_MODEL / empty AI_DEFAULT_API_KEY so AI lights up for all users via team defaults. Confirmed the existing OpenAiCompatibleAdapter appends /chat/completions to the base URL, matching Ollama's OpenAI-compatible endpoint, and that AI calls already fail gracefully (no hard dependency / no crash while the model downloads). Opt-in like dev-auth; composes alongside it. .env.example gained an OLLAMA_* section; README gained a "Local AI (Ollama)" quick-start with model/GPU tuning notes. Infra/docs-only change — no application code touched. Verified all three overlays merge via docker compose ... config and the init pull command renders correctly.
2026-06-29T14:00:00Z — Fix frontend→backend proxy breaking under docker. The frontend's runtime proxy (/api/v1/[...path]) forwards to API_BASE_URL server-side; the base compose interpolated ${API_BASE_URL:-http://api:8080} from .env, but .env.example ships API_BASE_URL=http://localhost:8080 (the correct value only for running the frontend OUTSIDE docker). Copied into .env, that value leaked into the frontend container, so the proxy hit localhost:8080 (itself) and every /api/v1/* call failed (e.g. /api/v1/settings). Pinned the frontend container's API_BASE_URL to the in-network service name http://api:8080 in docker-compose.yml (container-to-container hop must never use the host's localhost), and clarified in .env.example that the variable only applies to non-docker frontend dev. Infra/docs-only; no application code touched. Verified API_BASE_URL: http://api:8080 in merged docker compose config.
2026-06-29T13:00:00Z — Fix Auth.js UntrustedHost on containerized login. Added AUTH_TRUST_HOST: "true" to the frontend service in docker-compose.yml. Auth.js v5 refuses to infer the host from request headers in production mode (which the container runs) unless the host is explicitly trusted, so any container-based login — production stack or the dev-auth overlay — failed with UntrustedHost: Host must be trusted. Placed in the base compose since it fixes the latent issue for the production stack too, not just dev-auth. README "Local OAuth (Authentik)" section gained a troubleshooting note. Infra/docs-only; no application code touched. Verified merged config via docker compose ... config.
2026-06-29T12:00:00Z — Local OAuth provider (Authentik) for dev/open-source. Added docker-compose.dev-auth.yml overlay that runs a self-contained authentik stack (postgres + redis + server + worker) and repoints the api/frontend OIDC env at it. Added authentik/blueprints/crewcaptain-dev.yaml — a declarative bootstrap blueprint that auto-provisions a demo manager user (demo / demo12345), a confidential OAuth2/OIDC provider (client_id: crewcaptain), and an application with slug crewcaptain (issuer http://authentik:9000/application/o/crewcaptain/). Issuer host is identical inside the docker network and from the browser via a documented 127.0.0.1 authentik hosts entry, eliminating the internal/external issuer mismatch. .env.example gained an AUTHENTIK_* dev-only section; README gained a "Local OAuth (Authentik)" quick-start. Infra/docs-only change — no application code touched. Verified: docker compose -f docker-compose.yml -f docker-compose.dev-auth.yml config parses, blueprint YAML structure validated. Clearly gated as DEV ONLY (ships well-known committed credentials; must never run in production).
2026-06-29T00:00:00Z — Docs: README API reference brought in sync with controllers after a full controller-vs-README audit. Added 18 previously undocumented endpoints: PUT /persons/{id}/remember-items/{itemId} (update remember item), POST /persons/{personId}/ai-prep, POST /persons/{personId}/ai-trend-radar, two Strategy Goal endpoints (reverse lookup + ai-suggestions), GET /settings/ai-status, and new endpoint tables for Workspaces (6), Triage Queue (3), Gamification (1), Audit Log (1), and AI (5). Also refreshed the stale Database Migrations table (added 15 missing migrations V20250511120002–V20250607120000), relocated orphaned parameter/field blocks (Notifications, Quick Notes, 1:1, persons-list) from under User Settings into their correct sections, removed a stale duplicated Search query-params block, and fixed duplicate numbering in the Contributing list. Documentation-only change — no production code modified, build unaffected. Architecture review confirmed hexagonal/DDD boundaries are clean and ArchUnit-enforced (the Spring Data Page/Pageable leak into ports is documented as an accepted tradeoff in api/ARCHITECTURE.md).
2026-06-28T12:00:00Z — 1:1 Prep Notes Panel: New OneOnOnePrepNotes component surfaces INBOX quick notes assigned to a person directly on the 1:1 entry page. Collapsible panel with "Add to Agenda" (attaches note to entry) and "Dismiss" (archives note) actions. Rendered before the agenda items via prepNotesSlot on both create and edit 1:1 pages. No new backend code or migrations — leverages existing Quick Notes API (listQuickNotes with personId + INBOX filter). Frontend: 1256 tests pass. Build succeeds.
All PRD features implemented plus AI Strategic Trend Radar, Strategy Hub with LLM-powered Link Suggestions, Sticky Notes, Unified Triage Queue, AI Command Terminal, and 1:1 Prep Notes Panel. Docker Compose production file pulls pre-built images from ghcr.io/hechi/crewcaptain/{api,frontend}:latest. AI features: 1:1 Prep Assistant, Performance Narrative Generator, Coaching & Feedback Refinement, Outcome Extractor, Strategic Trend Radar, AI Link Suggestions, Triage Hints, and AI Command Terminal. Strategy Hub provides strategic layer with intelligent PDP goal alignment recommendations. Architecture enforcement: ArchUnit tests validate hexagonal layer boundaries at compile time. Port interfaces split into port/input/ (use case interfaces) and port/output/ (repository + external service interfaces). Domain services isolated in domain/service/. Backend: 1398 tests all pass. Frontend: 1256 tests all pass. Frontend build succeeds.
-
Local LLM provider (Ollama) for dev —
docker-compose.ai.ymloverlay runs anollamaserver + one-shotollama-initthat auto-pulls a small model (qwen2.5:1.5bdefault,OLLAMA_MODEL/OLLAMA_TAGoverridable) and points the api at it viaAI_DEFAULT_BASE_URL=http://ollama:11434/v1+AI_DEFAULT_MODEL(empty API key). Activates AI for all users through team defaults. CPU-only by default with a commented NVIDIA GPU block. Opt-in; composes alongside the dev-auth overlay..env.exampleOLLAMA_* section + README "Local AI (Ollama)" quick-start. Infra/docs-only; no app code or migrations. Verified viadocker compose ... config(all three overlays merge; init pull command renders). (2026-06-29) -
Local OAuth provider (Authentik) for dev —
docker-compose.dev-auth.ymloverlay runs a self-contained authentik stack (authentik-db, authentik-redis, authentik server, authentik-worker) and repoints api/frontend OIDC env athttp://authentik:9000/application/o/crewcaptain/.authentik/blueprints/crewcaptain-dev.yamlauto-provisions a demo user (demo/demo12345), a confidential OAuth2/OIDC provider (client_id: crewcaptain, callbackhttp://localhost:3000/api/auth/callback/oidc), and an application (slugcrewcaptain). Documented127.0.0.1 authentikhosts entry keeps the token issuer identical inside docker and from the browser..env.exampleAUTHENTIK_* section + README "Local OAuth (Authentik)" quick-start. DEV ONLY — well-known committed credentials, never for production. Infra/docs-only; no app code or migrations. Verified viadocker compose ... configand blueprint YAML structure validation. (2026-06-29) -
1:1 Prep Notes Panel — Frontend-only UX enhancement. New
OneOnOnePrepNotescomponent renders a collapsible panel on the 1:1 entry page (both create and edit) showing all INBOX quick notes assigned to the person. Allows one-click "Add to Agenda" (attaches note to the 1:1 entry, adds text as agenda item) and "Dismiss" (archives note). Shows note count badge, relative dates, and sensitive content badges. Automatically hides when no prep notes exist. No new backend code or migrations needed — uses existingGET /api/v1/quick-notes?personId={id}&status=INBOXendpoint.OneOnOneEntryFormextended withprepNotesSlotprop rendered before agenda items. Tests: OneOnOnePrepNotes (14 tests). Frontend: 1256 tests all pass. (2026-06-28) -
AI Admin/Team Defaults — AiConfigResolver component in application layer resolves effective AI config with cascade: user settings > admin env defaults > disabled. Environment variables: AI_DEFAULT_BASE_URL, AI_DEFAULT_API_KEY, AI_DEFAULT_MODEL (configured in application.yml under app.ai.defaults). All 8 AI services (Coaching, Prep, Narrative, OutcomeExtractor, TrendRadar, TriageHint, LinkDiscovery, CommandTerminal) updated to use AiConfigResolver instead of direct isAiConfigured() checks. New GET /api/v1/settings/ai-status endpoint returns {available, source, adminDefaultsConfigured}. UserSettingsResponse extended with aiConfigSource and aiAvailable fields. Frontend settings page shows config source badge ("AI available via team defaults" / "Using your personal AI config"). No new database migrations. .env.example updated. Tests: AiConfigResolver (11 tests), all existing AI service tests updated and pass. Backend: 1398 tests all pass. Frontend: 1242 tests all pass. (2026-06-20)
-
AI Command Terminal — Backend: AiCommandTerminalService with structured JSON prompting for Ollama/OpenAI-compatible LLMs, person directory context injection. POST /api/v1/ai/command (parse natural language → typed command JSON with intent, target_person_id, content, due_date, meeting_date, tags, sensitive flag). GET /api/v1/ai/command/directory (lightweight person id + preferredName list). Supported intents: create_action_item, create_kudo, create_quick_note, create_one_on_one_entry. For 1:1 entries, extracts meeting notes into notesMarkdown and resolves meeting date (defaults to today). UserSettings extended with aiAutoExecuteCommands (boolean, default false) and commandTerminalPrompt (custom system prompt). Flyway migration V20250607120000. Frontend: AiCommandTerminal overlay component mounted in layout.tsx (conditionally visible when AI enabled). Glassmorphism terminal panel with violet accent, slide-up animation, scan-line texture. Cmd/Ctrl+K keyboard shortcut (toggle), Escape to close, backdrop click dismiss. Continuous scrolling chat interface. Two execution modes: Standard (preview card with Confirm & Save button) and Auto-Execute (bypasses preview, shows 10s undo toast). Privacy Mode: warning for sensitive content before execution. Person directory fetched on open for AI context. FAB button (⌘) positioned at bottom-right next to Quick Note FAB. Settings page: "Auto-Execute AI Commands" toggle and "Command Terminal Prompt" textarea. Tests: service (18 tests), controller (7 tests) backend + component (20 tests) frontend. (2026-06-07)
-
Unified Triage Queue — Backend: GET /api/v1/triage endpoint aggregating overdue action items, due-soon items, stale 1:1 reminders, upcoming anniversaries. Sorting by criticality (Overdue > Due Soon > Stale > Informational). Filtering by type, scope (All/Mine), workspace, person. Snooze support (POST /api/v1/triage/persons/{id}/action-items/{id}/snooze with snoozedUntil field, Flyway migration V20250606120000). AI hint generation (POST /api/v1/triage/items/{id}/hint) respecting privacy mode. Frontend: /triage page with full TriageQueueContainer (glassmorphism card, keyboard nav j/k/d/c/s/a/q/r/t/Enter/Escape), TriageItemRow (full InlineActionMenu: Done, Cancel, Snooze 1d/3d/7d, Reassign, Set Due, Add to 1:1, Save as Note), QuickPeekDrawer (person morale, last 1:1, open action items, recent kudos), TriageFilterBar (scope toggle, type dropdown), TriageEmptyState. Set Due Date inline date picker. Toggle Owner (Manager↔Person). Workspace chip on rows. AI hint button (Sparkles icon) with hint pill display. Global Cmd/Ctrl+J shortcut. Sensitive content masking. Navigation link. Middleware auth. Tests: domain (15), service (13), controller (7) backend + page (16) frontend. (2026-06-06)
-
Architecture enforcement — ArchUnit test suite (19 tests), port/input + port/output split, domain/service isolation, ARCHITECTURE.md guide (2026-06-06)
-
Backend project structure — Gradle Kotlin DSL, Spring Boot 3.3.5, Hexagonal/DDD package layout (2026-05-08)
-
Frontend project structure — Next.js 14, React 18, Auth.js, TypeScript strict mode (2026-05-08)
-
Backend Dockerfile — Multi-stage build (gradle:8-jdk21 → eclipse-temurin:21-jre-alpine) (2026-05-08)
-
Frontend Dockerfile — Multi-stage build (node:20-alpine, 3 stages with standalone output) (2026-05-08)
-
Docker Compose — Production stack with db, api, frontend services and health checks (2026-05-08)
-
Docker Compose override — Local dev with volume mounts and exposed ports (2026-05-08)
-
Local development script — dev.sh with backend/frontend commands, dependency checks (2026-05-08)
-
Environment documentation — .env.example with all variables documented (2026-05-08)
-
Person Directory API — Full CRUD, morale tracking, pinned remember items (2026-05-08)
-
OIDC Authentication — JWT validation, user provisioning, userId scoping (2026-05-08)
-
Database migrations — Users, persons, pinned_remember_items tables via Flyway (2026-05-08)
-
Backend tests — Domain unit tests, application service tests, controller slice tests, property tests, integration tests with Testcontainers (2026-05-08)
-
Frontend components — PersonCard, FilterBar, MoraleIndicator, Pagination, PersonForm, RememberItemsList, EmptyState (2026-05-08)
-
Frontend pages — People list, person detail, create person (2026-05-08)
-
Frontend tests — Component tests, page tests, API client tests (2026-05-08)
-
Frontend Auth.js integration — OIDC provider config, SessionProvider, middleware, sign-in page, route handler (2026-05-08)
-
README.md — Updated documentation reflecting current project state (2026-05-08)
-
1:1 Entry Management Backend — Series config (cadence + template), entry CRUD, agenda items, sensitive flag, at-a-glance last 1:1 date (2026-05-08)
-
1:1 Database migrations — one_on_one_series, one_on_one_entries, agenda_items tables (2026-05-08)
-
1:1 Backend tests — Domain unit tests, application service tests, controller slice tests, property tests, integration tests (2026-05-08)
-
1:1 Entry Management Frontend — TypeScript types, API client, components (timeline, entry editor, series config, Markdown editor, agenda items, sensitive toggle), pages (2026-05-08)
-
1:1 Frontend tests — Component tests, page tests, API client tests (2026-05-08)
-
Frontend branding redesign — CSS design tokens, Inter font, Navigation component, brand colors across all components/pages (2026-05-09)
-
Cyberpunk-lite dark theme redesign — Dark-first UI, JetBrains Mono headings, electric cyan/neon violet accents, glassmorphism cards, glow effects, morale indicators with neon borders, updated DESIGN.md v2.0 (2026-05-09)
-
Action Items Backend API — Full CRUD, status transitions (OPEN→DONE, OPEN→CANCELED), owner type (MANAGER/PERSON), due dates, originating 1:1 entry link, per-person and cross-person listing, overdue filtering, data isolation (2026-05-10)
-
Action Items Database migration — action_items table with indexes (2026-05-10)
-
Action Items Backend tests — Domain unit tests (20 tests), application service tests (15 tests), controller slice tests (15 tests), integration tests with data isolation verification (15 tests) (2026-05-10)
-
Action Items Frontend — TypeScript types, API client (8 functions), components (ActionItemCard, ActionItemForm, ActionItemList, ActionItemStatusBadge), person detail "Action Items" tab with inline create/edit, status filter, complete/cancel/delete (2026-05-10)
-
Action Items Frontend tests — Component tests (ActionItemCard 14, ActionItemForm 8, ActionItemList 9, ActionItemStatusBadge 3), API client tests (15) (2026-05-10)
-
PDP Goal Tracking Backend API — Full CRUD, status transitions (ACTIVE→ACHIEVED/PAUSED/DROPPED, PAUSED→ACTIVE), progress updates with sensitive flag, per-person listing with status filter, data isolation (2026-05-10)
-
PDP Goal Database migrations — pdp_goals and pdp_updates tables with indexes (2026-05-10)
-
PDP Goal Backend tests — Domain unit tests (PdpGoal 22 tests, PdpUpdate 4 tests), application service tests (18 tests), controller slice tests (17 tests), integration tests with data isolation verification (17 tests) (2026-05-10)
-
PDP Goal Frontend — TypeScript types, API client (12 functions), components (PdpGoalCard, PdpGoalForm, PdpGoalList, PdpGoalStatusBadge), person detail "PDP Goals" tab with inline create/edit, status filter, achieve/pause/drop/resume actions (2026-05-10)
-
PDP Goal Frontend tests — Component tests (PdpGoalCard 18, PdpGoalForm 8, PdpGoalList 9, PdpGoalStatusBadge 4), API client tests (19), page integration tests (10) (2026-05-10)
-
Kudos / Recognition Backend API — Create, get, list (per-person + cross-person), delete. Date, Markdown text, optional tags. Data isolation enforced. (2026-05-10)
-
Kudos Database migration — kudos table with indexes (user_id, user_id+person_id, user_id+date) (2026-05-10)
-
Kudos Backend tests — Domain unit tests (8 tests), application service tests (10 tests), controller slice tests (12 tests), integration tests with data isolation verification (10 tests) (2026-05-10)
-
Kudos Frontend — TypeScript types, API client (5 functions), components (KudosCard, KudosForm, KudosList), person detail "Kudos" tab with inline create form and delete (2026-05-10)
-
Kudos Frontend tests — Component tests (KudosCard 8, KudosForm 9, KudosList 9), API client tests (14) (2026-05-10)
-
Quick Notes Backend API — Create, get, update, list, delete, assign-to-person, attach, convert, archive. Markdown text, optional person assignment, sensitive flag, status workflow (INBOX→ATTACHED/CONVERTED/ARCHIVED). Data isolation enforced. (2026-05-10)
-
Quick Notes Database migration — quick_notes table with indexes (user_id, user_id+status, user_id+person_id, user_id+created_at) (2026-05-10)
-
Quick Notes Backend tests — Domain unit tests (16 tests), application service tests (18 tests), controller slice tests (17 tests), integration tests with data isolation verification (15 tests) (2026-05-10)
-
Quick Notes Frontend — TypeScript types, API client (9 functions), components (QuickNoteCard with person picker + 1:1 entry picker, QuickNoteForm, QuickNoteList), dedicated Quick Notes page with status filter, pagination, person assignment, and 1:1 attachment, Navigation link (2026-05-10)
-
Quick Notes Frontend tests — Component tests (QuickNoteCard 16, QuickNoteForm 11, QuickNoteList 9), API client tests (17) (2026-05-10)
-
Quick Notes 1:1 Attachment — Backend schema migration (attached_entry_id FK), domain model updated, attach endpoint requires entryId, validates entry exists and belongs to user, adds note text as agenda item to the 1:1 entry, frontend entry picker UI (2026-05-10)
-
Quick Notes Action Item Conversion — Convert endpoint requires personId, creates actual action item with note text as title, assigns to person's action item list, frontend person picker for conversion (2026-05-10)
-
Dashboard Backend API — GET /api/v1/dashboard endpoint with configurable dueSoonDays and anniversaryLookaheadDays parameters. DashboardService aggregates overdue items, due-soon items, stale 1:1 reminders, and upcoming anniversaries. All queries scoped by userId. (2026-05-10)
-
Dashboard Backend tests — DashboardService unit tests (13 tests), DashboardController slice tests (8 tests) (2026-05-10)
-
Dashboard Frontend — TypeScript types, API client (getDashboard with options), components (OverdueActionItems, DueSoonActionItems, StaleOneOnOnes, UpcomingAnniversaries), dedicated Dashboard page with grid layout, alert summary, empty states, person links (2026-05-10)
-
Dashboard Frontend tests — Component tests (OverdueActionItems 8, DueSoonActionItems 8, StaleOneOnOnes 10, UpcomingAnniversaries 8), API client tests (8), page tests (10) (2026-05-10)
-
Navigation updated — Dashboard link added as first nav item, home page redirects to /dashboard (2026-05-10)
-
Sensitive Content Encryption — AES-256-GCM application-level encryption for sensitive text fields at rest. EncryptionPort interface in application layer, AesGcmEncryptionAdapter in adapters layer. Integrated into persistence adapters (OneOnOneEntry, QuickNote, PdpUpdate). Graceful fallback when no key configured. Legacy unencrypted data support. (2026-05-10)
-
In-App Notification Scheduling — Hourly scheduled task generates notifications for all users. Notification types: ACTION_ITEM_OVERDUE, ACTION_ITEM_DUE_SOON, STALE_ONE_ON_ONE, UPCOMING_ANNIVERSARY. 24-hour deduplication window prevents duplicate notifications. REST API: list (paginated), unread count, mark as read, mark all as read. Frontend: NotificationBell with unread badge in navigation, NotificationPanel dropdown, NotificationItem with type-specific icons and deep links, dedicated /notifications page with pagination and unread filter. (2026-05-10)
-
Full-Text Search — GET /api/v1/search endpoint with PostgreSQL full-text search (to_tsvector/to_tsquery with prefix matching). Searches across persons, 1:1 entries, quick notes, action items, PDP goals, PDP updates, and kudos. Type filtering, pagination, relevance ranking. Sensitive content excluded from search (encrypted fields not searchable, sensitive snippets hidden in results). Frontend: dedicated /search page with search input, type filter chips, SearchResultCard component with type badges and deep links, pagination, URL state sync. Navigation link added. Deep links navigate to exact location: 1:1 entry page, person detail with correct tab pre-selected (action-items, pdp-goals, kudos). (2026-05-10)
-
Per-Person Markdown Export — GET /api/v1/persons/{id}/export endpoint. Aggregates all person data (profile, pinned remember items, morale, 1:1 entries, action items, PDP goals with updates, kudos) and formats as structured Markdown. Optional dateFrom/dateTo query parameters for date range filtering. Sensitive content marked but not exposed. Returns text/markdown with Content-Disposition attachment header. Frontend: Export button on person detail page triggers download as {name}.md file. (2026-05-10)
-
Gamification Backend API — GET /api/v1/gamification/stats endpoint. GamificationService computes 1:1 streaks (consecutive weeks with meetings), achievement milestones (13 types across 1:1s, action items, PDP goals, kudos, and streaks), activity heatmap (configurable days window, default 90), and PDP progress summary (active/achieved/paused/dropped with completion percentage). All queries scoped by userId. (2026-05-10)
-
Gamification Backend tests — Domain unit tests (GamificationStats 8 tests), application service tests (GamificationService 18 tests), controller slice tests (GamificationController 8 tests) (2026-05-10)
-
Gamification Frontend — TypeScript types, API client (getGamificationStats), components (ProgressRing with animated SVG arc and glow, StreakCounter with monospace readout, AchievementBadge with Lucide SVG icons and category colors, ActivityHeatmap contribution graph, CompletionAnimation with checkmark glow burst). Dashboard integration with gamification stats section above existing grid. (2026-05-10)
-
Gamification Frontend tests — Component tests (ProgressRing 10, StreakCounter 8, AchievementBadge 10, ActivityHeatmap 9, CompletionAnimation 6), API client tests (8) (2026-05-10)
-
User Settings Backend API — GET/PUT /api/v1/settings endpoint. UserSettings domain aggregate with validation (threshold ranges). UserSettingsService for get/update. JPA persistence adapter. Flyway migration for user_settings table. Notification scheduler respects per-user notification toggles and threshold settings. (2026-05-10)
-
User Settings Backend tests — Domain unit tests (UserSettings 15 tests), application service tests (UserSettingsService 7 tests), controller slice tests (UserSettingsController 11 tests) (2026-05-10)
-
User Settings Frontend — TypeScript types, API client (getUserSettings, updateUserSettings), Settings page with theme selector, threshold inputs, notification toggles, achievement visibility toggle, save with success/error feedback. ThemeProvider context for app-wide theme management. Navigation link added. (2026-05-10)
-
User Settings Frontend tests — ThemeProvider tests (7), Settings page tests (14), API client tests (7), Navigation test for settings link (1) (2026-05-10)
-
Light Theme — Full CSS light theme via [data-theme="light"] selector. Clean surfaces (#F8FAFB base), teal/purple accents, proper WCAG contrast, subtle shadows instead of glows, light scrollbar styling. Toggled via Settings page. (2026-05-10)
-
Dashboard respects settings — Achievement section visibility controlled by showAchievements setting. Dashboard fetches user settings on load and passes dueSoonDays/anniversaryLookaheadDays as query params to the dashboard API. (2026-05-10)
-
Automatic Token Refresh — Auth.js jwt callback captures refresh_token and expires_at on login, proactively refreshes access token 60s before expiry using OIDC token endpoint discovery. SessionProvider polls session every 4 minutes and on window focus. SessionRefreshGuard component detects unrecoverable refresh failures and triggers re-authentication. offline_access scope added to OIDC authorization request. (2026-05-10)
-
Middleware Auth Coverage — Expanded middleware matcher to protect all authenticated routes (/dashboard, /quick-notes, /search, /settings, /notifications) in addition to /people. Eliminates client-side loading flash for authenticated users. (2026-05-11)
-
Build Fixes — Fixed duplicate fontFamily in page.tsx, lucide-react LucideIcon type in AchievementBadge, search page prerender with Suspense layout. (2026-05-11)
-
GIN Indexes for Full-Text Search — Per-table immutable wrapper functions (persons_search_vector, one_on_one_entries_search_vector, quick_notes_search_vector, action_items_search_vector, pdp_goals_search_vector, pdp_updates_search_vector, kudos_search_vector) with expression-based GIN indexes. Search queries use the same functions enabling index utilization. Flyway migration V20250510120009. (2026-05-10)
-
Review Packet Generator Backend API — GET /api/v1/persons/{id}/review-packet endpoint with required dateFrom/dateTo parameters. ReviewPacketService aggregates all person data within date range, computes summary statistics (1:1 count, action item completion rate, PDP goal progress, kudos tag summary), and formats as structured Markdown via ReviewPacketFormatter domain service. Sensitive content excluded. All queries scoped by userId. (2026-05-10)
-
Review Packet Generator Backend tests — Domain unit tests (ReviewPacketSummary 10 tests, ReviewPacketFormatter 20 tests), application service tests (ReviewPacketService 10 tests), query validation tests (GenerateReviewPacketQuery 3 tests), controller slice tests (ReviewPacketController 10 tests) (2026-05-10)
-
Review Packet Generator Frontend — API client function (generateReviewPacket), ReviewPacketModal component with date range picker, validation, and generating state. Integrated into person detail page with "Review Packet" button next to Export button. Downloads as {name}-review-packet.md. (2026-05-10)
-
Review Packet Generator Frontend tests — Component tests (ReviewPacketModal 12 tests), API client tests (6 tests) (2026-05-10)
-
Bulk Import (CSV) Backend — POST /api/v1/persons/import endpoint accepting multipart CSV. CsvParser domain service with quoted field support, CsvPersonRow validation, PersonBulkImportService with max 500 rows, per-row error reporting, partial success support. Data isolation enforced (userId scoping). (2026-05-10)
-
Bulk Import (CSV) Backend tests — Domain unit tests (CsvParser 15 tests), application service tests (PersonBulkImportService 12 tests), controller slice tests (PersonBulkImportController 10 tests) (2026-05-10)
-
Bulk Import (CSV) Frontend — TypeScript types (BulkImportResponse), API client (importPersonsCsv with FormData), CsvImportModal component with file validation, CSV preview table, import progress, success/error result display. "Import CSV" button on People list page. (2026-05-10)
-
Bulk Import (CSV) Frontend tests — Component tests (CsvImportModal 14 tests), API client tests (8 tests) (2026-05-10)
-
Dashboard gamification card consistency — Redesigned using stat-card UX pattern: glassmorphism card shell, label pinned at top, flex-grow content area. StreakCounter fills width with border-top separator for secondary stats. ActivityHeatmap columns flex to fill card width with 12px cells. PDP ring glow no longer clipped (overflow:visible + inner padding). (2026-05-11)
-
Soft-Delete + Restore — DELETE /api/v1/persons/{id} now soft-deletes (sets deleted_at timestamp). New endpoints: POST /api/v1/persons/{id}/restore (restore from trash), GET /api/v1/persons/trash (list deleted persons, paginated). Domain model updated with softDelete()/restore() methods. All existing queries exclude soft-deleted records via WHERE deleted_at IS NULL. Flyway migration V20250510120010 adds deleted_at column with partial indexes. Frontend: Trash page with restore buttons, "Trash" button on People list page. Full test coverage: domain (4 tests), service (5 tests), controller (7 tests), integration (13 tests), frontend page (8 tests), API client (7 tests). (2026-05-11)
-
Audit Log Backend API — GET /api/v1/audit-log endpoint with entityType and action filters, pagination. AuditLogService records entries on create/update/delete/restore across all entities (Person, 1:1 Entry, Action Item, PDP Goal, Kudos, Quick Note, User Settings). AuditLogEntry domain model with factory methods. Flyway migration V20250511120000 creates audit_log table with indexes. Data isolation enforced (userId scoping). (2026-05-11)
-
Audit Log Backend tests — Domain unit tests (AuditLogEntry 17 tests), application service tests (AuditLogService 8 tests), controller slice tests (AuditLogController 9 tests) (2026-05-11)
-
Audit Log Frontend — TypeScript types, API client (getAuditLog with filters), dedicated /audit-log page with entity type filter, action filter, pagination, relative timestamps, action badges with color coding, entity type badges. Navigation link in user menu. Middleware auth coverage. (2026-05-11)
-
Audit Log Frontend tests — Page tests (10 tests), API client tests (7 tests) (2026-05-11)
-
Permanent Delete from Trash — DELETE /api/v1/persons/{id}/permanent endpoint removes a soft-deleted person permanently, cascading to all child tables. Migration V20250511120001 adds ON DELETE CASCADE to FK constraints on action_items, pdp_goals, kudos, and quick_notes (previously unconstrained). Audit log entry recorded. Frontend: "Delete Forever" button on Trash page with inline confirmation UI (Yes, Delete / Cancel). userId scoping enforced. Full test coverage: domain (AuditLogEntry 1 new test), service (PersonService 4 new tests), controller (PersonController 4 new tests), integration (PersonSoftDelete 3 new tests including cascade verification), frontend page (TrashPage 7 new tests), API client (2 new tests). (2026-05-11)
-
Workspaces — Lightweight organizational containers for grouping people. Backend: Workspace domain aggregate with name (max 100), description (max 500), displayOrder. WorkspaceService with CRUD + person assignment. WorkspaceController (POST/GET/PUT/DELETE /api/v1/workspaces, PUT /api/v1/workspaces/persons/{id}/workspace). Flyway migration V20250511120002 creates workspaces table and adds workspace_id FK to persons (nullable, ON DELETE SET NULL). PersonRepository updated with workspace filter. AuditLogEntry extended with WORKSPACE entity type. Frontend: TypeScript types, API client (7 functions), WorkspaceSelector component, WorkspaceForm component, WorkspaceList component, WorkspaceAssignment component (inline dropdown on person detail page with auto-save), dedicated /workspaces page with CRUD and delete confirmation. Navigation link in user menu. Middleware auth coverage. Full test coverage: domain (18 tests), service (15 tests), controller (15 tests), frontend components (WorkspaceSelector 5, WorkspaceForm 8, WorkspaceList 6, WorkspaceAssignment 7), page (11 tests), API client (8 tests). (2026-05-11)
-
Landing Page — Modern, high-converting landing page with cyberpunk-lite dark theme. Hero section with animated HUD visual motif (compass rings), badge with "Self-hosted · Privacy-first · Open Source", 6 feature cards with glassmorphism (1:1 Management, PDP Goal Tracking, Action Items, People Directory, Quick Notes Inbox, Dashboard & Insights), 3-step deployment guide (Clone & Configure, Docker Compose Up, Start Leading), privacy section with AES-256/Self-Hosted/AGPL badges, final CTA section, and footer with links. Fully responsive, accessible (WCAG AA), respects prefers-reduced-motion. Authenticated users redirect to dashboard. Jest CSS mock added for test compatibility. Frontend tests: LandingPage component (18 tests), HomePage page (5 tests). (2026-05-11)
-
GitLab CI/CD Pipeline — .gitlab-ci.yml with test stage (all branches: backend ./gradlew test with DinD for Testcontainers, frontend npm test) and build stage (main only: Docker image build + push to GitLab Container Registry). Images tagged with commit SHA and latest. Gradle/npm caching. (2026-05-11)
-
Docker Compose production registry — docker-compose.yml uses pre-built images from reg.root-base.de/poxy/crewcaptain/{api,frontend}:latest. Build directives moved to docker-compose.override.yml for local dev. DB port no longer exposed in production compose. (2026-05-12)
-
Runtime API proxy — Replaced build-time Next.js rewrites with a runtime API route handler (/api/v1/[...path]/route.ts). API_BASE_URL is now read at request time from environment variables, enabling runtime configuration without rebuilding the image. (2026-05-13)
-
Prometheus Metrics — /actuator/prometheus endpoint secured with bearer token (METRICS_TOKEN). Micrometer + Prometheus registry. Custom 1:1 metrics (total entries, entries last 7 days). Separate security filter chain for actuator endpoints. JVM, HTTP, HikariCP metrics included. Health endpoint remains unauthenticated. (2026-05-13)
-
Landing Page Screenshot Showcase — Interactive tabbed gallery section between Features and How It Works. Four screenshots: Dashboard overview, Action Items, Person Detail, and Search. Accessible tab navigation with keyboard support (ArrowLeft/ArrowRight), ARIA roles (tablist/tab/tabpanel), glassmorphism styling consistent with cyberpunk-lite theme. ScreenshotShowcase component with 18 tests. (2026-05-14)
-
Fix Session Refresh Page Reloads — Removed SessionRefreshGuard (caused full-page redirects on transient token refresh failures). Created useStableToken hook (ref-based token access, stable getToken() reference for useCallback deps). Updated all 12 pages to use useStableToken instead of token-in-deps pattern. Improved token refresh: cached OIDC discovery endpoint, added fetch timeouts, reduced refresh buffer from 60s to 30s, refetchInterval set to 3 minutes with refetchOnWindowFocus enabled. Requires authentik offline_access scope mapping for refresh tokens. (2026-05-14)
-
Cyberpunk Dropdown Styling — All dropdown buttons and native select elements restyled with cyberpunk-lite aesthetic per DESIGN.md. Global CSS classes:
.dropdown-trigger(glassmorphism background, glow border on hover, monospace font, neon accent on active),.dropdown-panel(frosted glass overlay, glowing border, entrance animation),.dropdown-item(monospace font, cyan highlight on hover),.dropdown-item--danger(magenta alert styling). Native<select>elements globally styled with custom appearance (removed browser chrome), cyan chevron indicator, glass background, glow on hover/focus. Updated: Navigation user menu, NotificationPanel, FilterBar, WorkspaceSelector, WorkspaceAssignment, ActionItemForm, QuickNoteCard pickers, audit-log page filters, person detail morale select. Light theme overrides included. Respects prefers-reduced-motion. (2026-05-14) -
Inline Action Items in 1:1 Entry Page — New OneOnOneActionItems component rendered between agenda items and notes inside the 1:1 entry form (via actionItemsSlot prop). Quick-add form (title + optional due date) auto-links new action items to the current 1:1 entry via originatingEntryId. Shows open action items for the person as a review section, highlights items created in this session separately, and allows marking items done with a single click. Also available on the create 1:1 page (without entry linking, for reviewing open items and creating new ones). Backend: added originatingEntryId query parameter to GET /api/v1/persons/{personId}/action-items endpoint. Frontend: 15 component tests, 1 create page test. Backend: 2 new tests (controller + service). (2026-05-15)
-
Cyberpunk HUD Loading Screen — New LoadingScreen component with animated HUD-style spinner (dual rotating rings with neon cyan/violet glow, pulsing core), scan-line CRT overlay, glitch text animation on "LOADING" label, and optional status message. Replaces all plain "Loading..." text across 10 pages (home, dashboard, people list, person detail, create person, trash, 1:1 entry, new 1:1, quick notes, search, settings, workspaces). Fully aligned with DESIGN.md v2.0 (dark-first, neon accents, glassmorphism, monospace typography, glow effects). Respects prefers-reduced-motion. Light theme overrides included. Accessible (role="status", aria-label). Frontend: 12 component tests. (2026-05-15)
-
Disable Next.js Telemetry — Added NEXT_TELEMETRY_DISABLED=1 environment variable to both build and runtime stages of the frontend Dockerfile. Prevents anonymous usage data from being sent to Vercel during image builds and at runtime. (2026-05-15)
-
Self-Assigned Quick Notes ("My Notes") — Managers can create personal quick notes (not linked to any person) via a new
selfAssignedboolean flag. Invariant: selfAssigned and personId are mutually exclusive — assigning to a person clears selfAssigned. Backend: Flyway migration (self_assigned column + partial index), domain model updated with markSelfAssigned() method and init invariant, service/controller/DTOs extended with selfAssigned field and filter, POST /assign-self endpoint for marking existing notes as self-assigned. Frontend: "My Notes" page at /my-notes accessible via user dropdown menu, dedicated create form (auto-sets selfAssigned=true), status filter (All/Active/Archived), archive/delete actions. "Assign to Me" button on QuickNoteCard in the main Quick Notes page (visible for unassigned INBOX notes). "Me" badge displayed on self-assigned notes. API client updated with selfAssigned query parameter and assignQuickNoteToSelf function. Full test coverage: domain (5 tests), service (7 tests), controller (6 tests), integration (6 tests), frontend page (12 tests), QuickNoteCard (5 new tests), API client (3 new tests), Navigation (1 new test). (2026-05-15) -
Quick Note Overlay — Global floating overlay accessible from any page via Ctrl+Shift+Q keyboard shortcut or floating action button (bottom-right corner). Cyberpunk glassmorphism design with neon glow border, scan-line texture, slide-up entrance animation, and backdrop blur. Supports Ctrl+Enter to save. Shows success feedback then auto-closes after 800ms. Stays open on error for retry. Dismissible via Escape, backdrop click, or cancel button. Respects prefers-reduced-motion. Only renders when authenticated. Mounted in root layout for global availability. Frontend: QuickNoteOverlay component (24 tests). (2026-05-16)
-
AI-Powered 1:1 Prep Assistant — Optional per-user AI integration for generating 1:1 agenda suggestions. Backend: Flyway migration (ai_enabled, ai_api_base_url, ai_api_key, ai_model_name, ai_privacy_mode columns on user_settings), UserSettings domain updated with AI fields and validation (requires URL+model when enabled), AiClientPort interface (application layer), OpenAiCompatibleAdapter (supports Ollama, LiteLLM, OpenAI, vLLM), AiPrepService synthesizes context (last 2 entries, open action items, active PDP goals with updates, recent kudos) respecting privacy mode (excludes sensitive content), AiPrepController (POST /api/v1/persons/{id}/ai-prep). Frontend: Settings page AI section (enable toggle, API URL, API key masked, model name, privacy mode toggle — conditionally rendered), AiPrepAssistant component on 1:1 entry detail page (generate button, loading pulse, suggestion cards with one-click add, glow burst animation, prefers-reduced-motion support). Tests: domain (12 new), application service (13 tests), controller slice (4 tests), frontend component (11 tests). (2026-05-17)
-
AI Performance Narrative Generator — Generate LLM-powered performance review narratives from aggregated person data within a configurable date range. Backend: Flyway migration (ai_writing_style column on user_settings), AiWritingStyle enum (NARRATIVE, BULLET_POINTS, CONCISE), AiNarrativeService aggregates kudos (with tags), PDP goals (with status and non-sensitive updates), 1:1 outcomes, and action item completion stats, builds structured prompt with style-specific instructions, calls AiClientPort. AiNarrativeController (POST /api/v1/persons/{id}/ai-narrative). Privacy mode respected — sensitive content excluded. Frontend: AiNarrativeModal component with date range picker, pulse animation during generation, result displayed in editable textarea with copy-to-clipboard. "✨ AI Narrative" button on person detail page (conditionally visible when aiEnabled=true). Writing style selector added to Settings page. Tests: application service (16 tests), controller slice (6 tests), frontend component (17 tests), API client (6 tests). (2026-05-17)
-
AI Coaching & Feedback Refinement — AI-powered coaching tools with customizable prompts. Backend: Flyway migration (kudos_refinement_prompt, pdp_optimization_prompt, agenda_prep_prompt, narrative_prompt TEXT columns on user_settings), UserSettings domain updated with 4 prompt fields and effectiveXxxPrompt() methods (returns custom or default), AiCoachingService (refineKudos using SBI framework, optimizePdpGoal using SMART criteria), AiCoachingController (POST /api/v1/ai/refine-kudos, POST /api/v1/ai/optimize-pdp-goal). Existing AiPrepService and AiNarrativeService now use user's custom prompts via effectiveAgendaPrepPrompt() and effectiveNarrativePrompt(). Frontend: KudosForm "✦ Refine" button with comparison view (Apply/Keep Original), PdpGoalForm "✦ SMART Check" button with comparison view, Settings page "AI Prompts" section with 4 customizable textareas and "Reset to Default" buttons. Tests: domain (10 tests), application service (11 tests), controller slice (9 tests), frontend component (6 new tests). (2026-05-18)
-
AI Outcome Extractor — Post-meeting productivity tool that extracts action items and decisions from 1:1 notes using the configured LLM. Backend: Flyway migration (outcome_extractor_prompt TEXT column on user_settings), UserSettings domain updated with outcomeExtractorPrompt field and effectiveOutcomeExtractorPrompt() method, AiOutcomeExtractorService (extractOutcomes parses notes via LLM, returns structured action items with owner type and suggested due dates + decisions; applyOutcomes bulk-creates ActionItem entities with originatingEntryId and appends decisions to entry outcomesMarkdown), AiOutcomeExtractorController (POST /extract-outcomes, POST /apply-outcomes on /api/v1/persons/{personId}/one-on-one-entries/{entryId}). Privacy: refuses sensitive entries when aiPrivacyMode=ON. Frontend: OutcomeExtractionModal component with owner-type grouping (cyan=Manager, violet=Person), editable titles, duplicate detection (pre-unchecked with warning badge), Sync All button. "✨ Extract Outcomes" button on 1:1 entry detail page (conditionally visible when AI enabled, notes non-empty, and not blocked by privacy mode). Settings page: Outcome Extractor Prompt textarea. Tests: application service (22 tests), controller slice (9 tests), frontend component (18 tests), API client (5 tests). (2026-05-18)
-
UI Consistency Fixes — Aligned visual styling across all person detail tabs. 1:1 Action Items quick-add: fixed date picker/input/button height mismatch (explicit 36px height). PDP Goals & Kudos: moved "add" buttons to left with same filled primary style as 1:1s and Action Items tabs. PDP Goals form: moved SMART Check AI button inline with Description label (matching Kudos Refine button pattern). Kudos form: added uppercase text-transform and letter-spacing to Date, Recognition, and Tags labels (matching Action Items and PDP Goals). Morale section: fixed dropdown height mismatch with input/button (explicit 36px), added uppercase labels. At a Glance section: added uppercase text-transform to sub-labels. (2026-05-18)
-
AI Strategic Trend Radar — Diagnostic tool analyzing 90 days of team member data to surface long-term patterns. Backend: Flyway migration (trend_radar_prompt TEXT column on user_settings), UserSettings domain updated with trendRadarPrompt field and effectiveTrendRadarPrompt() method, AiTrendRadarService aggregates metadata (meeting count, action item stats, PDP updates, kudos tag distribution, non-sensitive outcomes), builds statistical summary prompt, calls AiClientPort, parses JSON response with confidence scoring. AiTrendRadarController (POST /api/v1/persons/{id}/ai-trend-radar). Four dimensions: MORALE, WORK_GROWTH_BALANCE, RECOGNITION, MEETING_EFFICACY. Confidence scoring: Low (<40%), Moderate (40-75%), High (>75%). Minimum 2 meetings required (returns InsufficientData otherwise). Privacy mode respected. Frontend: AiTrendRadarResponse TypeScript types, generateTrendRadar API client function, TrendRadarInsights component with glassmorphism cards, neon confidence gauges, dimension icons, empty/loading/error/insufficient states. New "✦ Insights" tab on Person Detail page (conditionally visible when AI enabled). Settings page: Trend Radar Prompt textarea. Tests: application service (16 tests), controller slice (6 tests), frontend component (12 tests), API client (6 tests). (2026-05-19)
-
Pinned Remember Items Redesign — Replaced up/down arrow buttons with modern card-based UI and drag-and-drop reordering. Each remember item now renders as a note card with glassmorphism styling, GripVertical icon for drag handle, and trash icon for deletion. Supports drag-and-drop reordering with visual feedback (dashed border on drop target, opacity change when dragging). Retains full keyboard accessibility (Tab to focus, Arrow Up/Down to reorder). Updated input form with dashed border container and helper text. 17 tests covering drag-drop interactions, keyboard navigation, and all existing functionality. (2026-05-24)
-
Authentication Session Management Improvements — Fixed bug where users were left in "zombie" state when token refresh failed. Created SessionErrorHandler component that detects RefreshAccessTokenError and triggers automatic sign-out with redirect to sign-in page. Implemented retry logic in auth.ts with exponential backoff (1s, 2s, 4s delays) for transient failures (network errors, 5xx). Reduced session refetchInterval from 3 minutes to 2 minutes to better match 5-minute access token expiry in authentik. Added proper error classification (retryable vs non-retryable) to avoid unnecessary retries on permanent auth errors (4xx). Frontend: 11 new SessionErrorHandler component tests. All 1128 frontend tests passing. (2026-05-24)
-
Strategy Hub — "My Strategy" Hub & Goal Alignment feature. Backend: StrategyGoal domain aggregate with status transitions (ACTIVE→ACHIEVED/DROPPED), StrategyGoalPdpGoalLink for many-to-many relationships. Flyway migrations for strategy_goals table and strategy_goal_pdp_goal_links table with GIN index for full-text search. JPA entities and repositories with user-scoped queries. Repository adapters with AES-256-GCM encryption for sensitive fields. StrategyGoalService with CRUD and audit logging. StrategyGoalLinkService with alignment scoring and gap analysis. StrategyGoalController with REST endpoints (/api/v1/strategy-goals, /links, /alignment, /gap-analysis). Search integration with STRATEGY_GOAL type. Frontend: TypeScript types, API client functions, StrategyGoalCard component with contributors badge, StrategyGoalStatusBadge, StrategyGoalForm, Strategy Hub page at /strategy with full CRUD, gap analysis panel, stats bar, and filtering. Navigation link added. All backend tests pass (1263 total), all frontend tests pass (1128 total). (2026-05-24)
-
Strategy Goals FTS + Encryption Integration Tests — New integration test suite verifying: GIN index presence (idx_strategy_goals_fts), IMMUTABLE search vector function, functional search behavior, userId scoping, encryption/search trade-off (non-sensitive searchable; sensitive excluded/unsearchable), and correct encryption at rest with decryption on read. README updated to document trade-off. (2026-05-25)
-
Landing Page AI Features Update — Added dedicated AI features section with 6 capability cards (Generate Agenda, Extract Outcomes, Performance Narrative, Kudos Refinement, SMART Goal Check, Full Control) with privacy-first messaging and purple/violet accent theming. Updated screenshot showcase from 6 to 11 tabs: added 1:1 with AI, Kudos AI, PDP SMART Check, Review Narrative, Quick Capture, and AI Settings screenshots. Replaced old 1:1 Session tab with updated AI-enhanced version. Updated Quick Notes feature card to "Quick Capture" highlighting the global floating button. New CSS for AI section with glassmorphism cards, purple glow hover effects, and responsive grid. All screenshots copied to frontend/public/screenshots/. Frontend tests: LandingPage (21 tests), ScreenshotShowcase (22 tests). (2026-05-18)
-
Sticky Notes — Transformed Pinned Remember Items into visual sticky-note cards. Backend: Flyway migration adds
color,tag,sensitivecolumns. Domain model extended withStickyNoteColorenum (CYAN, PURPLE, GREEN, AMBER, PINK, SLATE). NewUpdateRememberItemCommandandPUT /{id}/remember-items/{itemId}endpoint. MarkdownExportFormatter masks sensitive notes. Frontend: NewStickyNotesGridcomponent with card grid, inline composer/editor, 6 color picker, tag field, sensitive toggle, starter templates (Family, Link, Docs, Life event, Manager), drag-and-drop + keyboard reorder, delete with 10s undo toast, truncation at 100 chars.PersonCardshows up to 2 non-sensitive note previews. 5 new integration tests, 2 new service tests, 18 new frontend component tests. (2026-06-02)
- (none)
- GitLab CI uses Docker-in-Docker for both Testcontainers (backend tests) and image builds — avoids needing shell executors or Kaniko
- Images tagged with both commit SHA and
latest— SHA for traceability, latest for easy deployment references only: mainrestricts build stage — feature branches only run tests, no wasted build time- Gradle and npm caches keyed by branch slug — balances cache freshness with reuse
| ID | Description | Severity | Status |
|---|---|---|---|
| 001 | Backend tests require Java 21 explicitly (system default may differ) | Low | Open |
| 002 | docker-compose.yml exposes db port 5432 (should only be in override) | Low | Fixed |
| 003 | FullStackIntegrationTest Property 14 (invalid morale status) has intermittent failure with edge-case strings | Low | Open |
| 004 | Changing ENCRYPTION_KEY caused 500 errors on all 1:1 entries (including non-sensitive) | High | Fixed |
| 005 | Access token expired without automatic refresh, requiring manual re-login | Medium | Fixed |
| 008 | Authentication session error handling — added SessionErrorHandler component to auto-redirect on token refresh failures, implemented exponential backoff retry logic (3 attempts), reduced refetchInterval to 2min | Medium | Fixed |
| 007 | Pages unexpectedly refresh during editing due to session refetch cascading into data re-fetches | High | Fixed |
| 006 | Docker healthcheck fails — spring-boot-starter-actuator missing, /actuator/health returns 404 | High | Fixed |
| 009 | Strategy page "Manage Links" button icon not displaying in some browsers — replaced emoji with Lucide Link2 icon component | Low | Fixed |
| 010 | AI Link Suggestions panel always visible on Strategy page — now conditionally renders only when aiEnabled=true in user settings | Low | Fixed |
| 011 | Strategy goal card target date emoji (🎯) not displaying in some browsers — replaced with Lucide Target icon component | Low | Fixed |
| 012 | Spider web view first linked goal missing connection line — fixed positioning calculation inconsistency | Low | Fixed |
| 013 | Spider web view goals overlapping with connection lines — increased vertical spacing and adjusted line endpoints to node edges | Low | Fixed |
- (Feature backlog complete — all PRD features implemented)
- (All planned features implemented)
- Workspace is a separate domain aggregate (not embedded in Person) — keeps Person focused on individual data
- workspace_id on persons is nullable (opt-in) — if no workspaces exist, everything works as before
- ON DELETE SET NULL for workspace_id FK — when a workspace is deleted, persons become unassigned rather than deleted
- Workspace list endpoint returns a flat list (not paginated) — workspaces are lightweight and few per user (typically <10)
- displayOrder field for future drag-and-drop reordering — auto-incremented on creation
- Workspace filter is additive to existing tag/morale filters — all filters can be combined
- Assign-person-to-workspace endpoint is under /api/v1/workspaces/persons/{id}/workspace (not /api/v1/persons/{id}/workspace) — keeps workspace operations grouped
- WorkspaceSelector component renders nothing when no workspaces exist — zero UI overhead for users who don't use workspaces
- Audit log is a separate table (not event sourcing) — simple append-only log for traceability, not a full event store
- AuditLogService is injected directly into application services (not via AOP/interceptors) — explicit, testable, and visible in the code
- Audit log entries use ON DELETE CASCADE for user_id and ON DELETE SET NULL for person_id — audit entries survive person deletion but are cleaned up when a user is removed
- Factory methods on AuditLogEntry for each entity type — keeps audit log creation consistent and DRY across services
- Audit log is read-only from the API (GET only, no DELETE/PUT) — audit entries are immutable once created
- Summary field is capped at 500 chars — prevents unbounded growth while providing useful context
- RESTORE action type added alongside CREATE/UPDATE/DELETE — captures soft-delete restore operations distinctly
- Soft-delete at Person level only (not per sub-entity) — when a Person is soft-deleted, their associated data (1:1 entries, action items, PDP goals, kudos) remains in the database but becomes inaccessible since all queries go through the person. This avoids complex cascading soft-delete logic while still providing safety.
deleted_atcolumn with partial indexes (WHERE deleted_at IS NULL and WHERE deleted_at IS NOT NULL) — efficient filtering for both active and trash queries- Soft-delete uses UPDATE (not INSERT into a separate archive table) — simpler implementation, restore is just clearing the timestamp
- Restore returns the restored Person — allows the frontend to immediately display the restored record without a separate fetch
- Trash endpoint is under /api/v1/persons/trash (not a separate /api/v1/trash) — keeps it scoped to the persons resource
- Hard delete (deleteByIdAndUserId) is preserved in the repository for future "permanent delete from trash" feature
- UserSettings is a separate domain aggregate (not embedded in User) — keeps the User aggregate focused on identity
- Settings table uses user_id as PK (1:1 relationship with users) — no separate settings ID needed
- Default settings returned when no row exists (no need to pre-create settings for every user)
- Notification scheduler reads user settings to respect notification toggles — disabled types are skipped entirely
- Theme is stored as a string enum (DARK/LIGHT) — extensible for future themes
- Light theme uses CSS custom properties override via [data-theme="light"] attribute on — zero JS overhead for theme switching
- ThemeProvider uses React context for app-wide theme state — settings page updates propagate immediately
- GIN indexes use per-table immutable wrapper functions (not generated columns) — PostgreSQL's to_tsvector is STABLE not IMMUTABLE, so we wrap it in IMMUTABLE plpgsql functions that pin the 'english' config
- Expression-based GIN indexes (not stored tsvector columns) — avoids schema changes to entities, no trigger maintenance, queries must use the same function call to hit the index
- Review packet is a separate endpoint from export (/review-packet vs /export) — different use case (summary vs raw dump), different required parameters (dateFrom/dateTo required vs optional), different output format (executive summary + statistics vs raw data)
- ReviewPacketSummary.compute() is a companion factory method — keeps statistics computation logic in the domain layer, testable without framework dependencies
- ReviewPacketFormatter is a domain service (object) — pure function, no state, no framework dependencies, same pattern as MarkdownExportFormatter
- Date range is required for review packets (unlike export where it's optional) — a review packet without a date range is meaningless
- CSV bulk import uses a domain service (CsvParser object) for parsing — pure function, no framework dependencies, testable in isolation
- CsvPersonRow.parse() returns a sealed class (CsvParseResult) — explicit success/failure handling without exceptions for expected validation errors
- Bulk import uses partial success model — valid rows are imported even when some rows fail, with per-row error reporting
- Max 500 rows per import — prevents accidental large imports from overwhelming the system
- Tags in CSV use pipe separator (|) instead of comma — avoids ambiguity with CSV field delimiters
- Import endpoint uses multipart/form-data (not JSON) — standard approach for file uploads, no base64 encoding overhead
- Java 21 is required for backend development (use SDKMAN:
sdk install java 21-tem) - Node.js 20+ required for frontend
- Docker required for running Testcontainers-based integration tests
- Copy
.env.exampleto.envbefore running./dev.sh backend - Copy
.env.exampleto.env.localfor frontend-specific overrides - The
next.config.mjsis used instead ofnext.config.ts(Next.js 14.x doesn't support TS config) - JetBrains Mono font loaded via Google Fonts CDN alongside Inter
- Notification scheduler runs every hour by default; configure via
NOTIFICATION_CRONenv var
- Backend: All 1227+ tests pass — domain (including UserSettings AI validation), application (including AiTrendRadarService 16 tests), controller slice (including AiTrendRadarController 6 tests), integration, encryption adapter, property, full-text search GIN index tests (last run: 2026-05-19)
- Frontend: 1111 total — component tests (including TrendRadarInsights 12 tests), page tests, API client tests (including ai-trend-radar 6 tests), auth token refresh tests, middleware tests, Navigation test (last run: 2026-05-19)
- E2E: No tests yet (Playwright configured)
- Property 14 test (invalid morale status) has an intermittent failure with certain generated strings — may need tighter string filtering or a different approach to testing invalid enum values
- Light mode toggle not yet implemented — currently dark-only. Should this be added as a user preference? → RESOLVED: Implemented as part of Settings page
- Notification polling interval (60s) is hardcoded in the frontend — should this be configurable?
- Should notifications be auto-dismissed after a certain age (e.g., 30 days)?
staleOneOnOneDayssetting is stored but not used by the dashboard — the stale 1:1 logic uses cadence-based intervals instead of a fixed threshold. Should the setting override cadence-based logic, or is it only for notifications?- GitHub publication prep — Added AGPL-3.0
LICENSE(matching the README badge),.github/workflows/ci.yml(GitHub Actions mirror of the GitLab pipeline: test on all branches/PRs, build + push images to GHCR on main),CONTRIBUTING.md, andSECURITY.md(single best-effort maintainer policy). Extended.gitignoreto exclude local AI agent tooling (.opencode/,.agents/,.continue/,.kiro/,opencode.json,skills-lock.json). Switched productiondocker-compose.ymland README to publicghcr.io/hechi/crewcaptain/{api,frontend}:latestimages. Fixed README clone URL to the GitHub repo. Local gituser.emailset to world@greenstation.de for future commits. Docs/config only — no application code changed. (2026-06-30) - README disclaimer — Added a top-of-README notice that the project was built with the help of AI (Kiro) and is maintained by a single developer in limited spare time (best-effort, as-is, no warranty). (2026-06-30)
- All-in-one demo stack — Added
docker-compose.full-demo.yml, a single self-contained Compose file that runs the entire stack (CrewCaptain API + frontend from GHCR, PostgreSQL, preconfigured authentik for login, and local Ollama for AI) with baked-in demo secrets and zero repo checkout. The authentik bootstrap blueprint is inlined as a Composeconfig(no bind mount needed). Validated withdocker compose config. Documented as a "Try it in one command" section at the top of the README Quick Start. Demo-only; ships well-known secrets and is clearly marked not for production. (2026-06-30)