Commit Graph

121 Commits

Author SHA1 Message Date
Hartmut a16c41e739 fix(dashboard): show skeleton instead of default layout until hydration completes
Root cause: useDashboardLayout initialised React state with createDefaultDashboardLayout()
(1 widget), so the wrong default rendered during the ~100–500ms window while React Query
fetched the user session and DB layout after login. On reload within staleTime the cache
hit resolved instantly, masking the bug.

Fix: add isHydrated boolean state that becomes true only once localStorage OR DB
hydration has settled; DashboardClient renders a GridLayoutSkeleton until then.
Also adds router.refresh() in the sign-in handler to bust the Next.js Router Cache
so the post-login navigation always lands on a fresh server component tree.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:20:50 +02:00
Hartmut 3e8df09cd8 feat(web): overbooking and vacation conflict warnings in AllocationModal
- New ConflictWarningPanel component: amber box with per-day overbooking
  table (capacity / already booked / new / overage) and sky-blue info box
  for vacation overlap. Overbooking section has an 'I understand' checkbox
  that must be ticked before Save is enabled; vacation overlap is
  informational only.
- AllocationModal: fires allocation.checkConflicts reactively when
  resourceId, dates and hoursPerDay are all set. Shows ConflictWarningPanel
  between form body and footer. Passes allowOverbooking: true to the
  createAssignment mutation when the user acknowledges. Acknowledgment
  resets whenever key fields change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:15:37 +02:00
Hartmut 472d87c829 feat(web): add error boundaries, loading skeletons, render fixes and tree-shaking
- Add error.tsx to all 13 route groups: admin, allocations, analytics, dashboard, estimates, notifications, projects, reports, resources, roles, staffing, timeline, vacations
- Add loading.tsx to 9 routes that were missing them: admin, analytics, dashboard, estimates, notifications, reports, roles, staffing, vacations
- ResourceDetail: wrap vacationStart in useMemo to stabilize query key, remove dead windowEnd variable
- node-renderer.ts: replace barrel import (import * as THREE) with named imports for tree-shaking
- next.config.ts: add framer-motion and @capakraken/shared to optimizePackageImports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 08:35:28 +02:00
Hartmut b103e79e92 feat(ux): prevent wizard close on backdrop click
AnimatedModal: add disableBackdropClose prop (default false, no impact
on existing consumers). When true, overlay onClick is removed.

ProjectWizard: remove handleBackdropClick — backdrop click no longer
closes the wizard. Only the X button and Cancel close it.

EstimateWizard already had no backdrop-click handler; no change needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 16:09:22 +02:00
Hartmut 4accee95a4 refactor(dispo): clean up validate UI in NewImportModal
- Extract EMPTY_VALIDATE_INPUT as module constant (prevents new object on every render)
- Extract IssueList component + ISSUE_STYLES map (eliminates blocker/warning copy-paste)
- Extract ReadinessIssue type from ReadinessReport
- Reuse buildValidateInput in handleSubmit (single source for path mapping)
- Guard setValidateInput(null) in onChange — only resets when not already null
- Remove unnecessary `as ReadinessReport` cast (tRPC infers the type)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 16:06:58 +02:00
Hartmut f89da5d93b fix(ui): use amber-950/30 for pending approvals dark background (not gray-800)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 10:34:49 +02:00
Hartmut 813b21d1a0 fix(ui): add dark mode styles to amber warning boxes app-wide
Amber alert boxes were missing dark: variants, rendering as muddy
dark-orange in dark mode with near-unreadable text. Fixed in:
- VacationClient (Pending Approvals banner)
- VacationModal (conflict warning)
- ResourceDetail (load error)
- SkillMatrixUpload (replace warning)
- AllocationModal (open demand toggle)
- ProjectWizard (budget bar, post-creation warnings)

Pattern: bg-amber-50 → dark:bg-amber-950/30, border-amber-200 →
dark:border-amber-800, text-amber-7/800 → dark:text-amber-300/400

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 10:28:35 +02:00
Hartmut 8f464f2150 feat(dispo): add dry-run Validate button to New Import modal
Adds a "Validate" button that calls the existing `validateImportBatch`
tRPC query before staging. Shows a readiness report inline:
- Green/amber/red status line based on canCommitWithStrictSourceData
- Record counts (resources, projects, assignments, vacations)
- Blocker issues in red with resolution hints
- Warnings in amber with resolution hints
- Fallback assumptions listed in gray

Also fixes a pre-existing bug where handleSubmit mapped wrong filePaths
keys to API fields (keys are resources/projects/assignments, not the
API field names).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 09:32:23 +02:00
Hartmut bdb55f23d3 fix(insights): suppress AI credentials warning during loading state
The warning was showing briefly on every page load because
`!aiConfigQuery.data?.configured` evaluated to true while the query
was still in-flight (data === undefined). Guard with `!isLoading` so
the amber box only appears after the query resolves with configured=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 09:05:00 +02:00
Hartmut a9ad1ed8b6 feat(G-08): chapter field uses live datalist from resource.chapters
All chapter text inputs now show autocomplete suggestions from the
database (distinct chapter values from active resources) via HTML
<datalist> wired to trpc.resource.chapters:

- ResourceModal: chapter input
- RateCardsClient: rate card line chapter input
- EffortRulesClient: effort rule chapter input
- ExperienceMultipliersClient: replaces hardcoded CHAPTER_PRESETS
  with live data, falls back to presets when no data available

Also revert blueprintRolePresetsInputSchema to z.array(z.unknown())
to restore compatibility with StaffingRequirement[] call sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 08:10:36 +02:00
Hartmut 4a49ec4f05 fix(sanity): resolve 15 gaps from sanity check audit (G-01 through G-15)
- G-01: ProjectWizard renders blueprint fieldDefs with DynamicFieldInput component
- G-02: Blueprint rolePresets validated via RolePresetsSchema in wizard; API keeps loose schema
- G-03: ProjectWizard step 2/3 validation (role, hoursPerDay, headcount required)
- G-04: EstimateWizard validates baseCurrency and demand line cost rates
- G-05: Project lifecycle transition guards with ALLOWED_TRANSITIONS map
- G-06: Blueprint validator extended for minLength/maxLength/pattern and DATE range checks
- G-07: assertBlueprintDynamicFields merges global blueprint fieldDefs into validation
- G-08: (tracked — chapter managed dropdown; deferred to backend ticket)
- G-09: JSDoc added to lcrCents/ucrCents clarifying LCR/UCR terminology
- G-10: Dispo route redirect already in place — closed as done
- G-11: packages/ui empty by design — closed as documented
- G-12: @deprecated JSDoc added to CreateAllocationSchema and UpdateAllocationSchema
- G-13: ProjectWizard review step enhanced with blueprint name, field values, skills, assignments
- G-14: ProjectWizard handleSubmit collects per-item warnings instead of silent swallowing
- G-15: Vacation cancel reverses usedDays entitlement for APPROVED ANNUAL/OTHER vacations

Tests: all 1575 passing (1 pre-existing failure in insights-summary unrelated to these changes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 00:11:12 +02:00
Hartmut f5e41f7efe fix(ux): improve empty-state messaging for tickets #49 and #57
#49 — upgrade MyVacationsClient account-not-linked inline text to a
prominent centred amber card with icon, heading, and admin-action guidance.

#57 — replace vague AI suggestions hint with actionable copy explaining
the Step 3 dependency before the user wonders why the list is empty.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-03 17:51:14 +02:00
Hartmut 9241f22993 fix(ux): resolve tickets #58 #60 — wizard review labels and assignment CTA clarity
#58: Split the merged "Type: BD / INT" field in the wizard review step into
separate "Order Type", "Allocation Type", and "Status on create" rows so
users can clearly distinguish commercial classification from lifecycle status.

#60: Relabel FillOpenDemandModal staging CTA from "+ Add to Plan" to
"+ Queue Assignment" and the proceed CTA from "Review (N)" to
"Review Queued (N)" to make the staged/non-final nature of the action clear.
Also correct the project detail Assignments label from "N active" to
"N planned" and update the tooltip to include PROPOSED in the definition.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-03 16:17:16 +02:00
Hartmut 2da29c8191 fix(ux): resolve tickets #59 #66 #67 — project feedback and demand summary
#66: Project detail "Open Demands" summary incorrectly counted COMPLETED
demands as open. Fix: add `status !== "COMPLETED"` to the activeDemands
filter in /projects/[id]/page.tsx.

#59/#67: Project creation and edit had two bugs:
1. Both invalidated `project.list` but the page queries `project.listWithCosts`
   — the list never refreshed after a save.
2. Success toasts were either absent (ProjectModal) or mounted inside the
   wizard component that unmounts before the toast finishes.
Fix: correct invalidation key to listWithCosts; add optional onSuccess prop
to both ProjectWizard and ProjectModal; ProjectsClient wires onSuccess to a
persistent SuccessToast rendered outside the modals.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-03 16:05:29 +02:00
Hartmut 65db330a4d fix(ux): resolve tickets #55 #56 — resource modal stability and success feedback
#55: Add SuccessToast after new resource is created. ResourceModal gains an
optional onSuccess(displayName) prop; ResourcesClient wires it to a toast
that auto-dismisses after 2.5 s.

#56: Fix useFocusTrap stale-closure bug. Focusable elements are now queried
dynamically inside handleKeyDown (not captured once at mount), so Tab
navigation stays correct as the form re-renders. Initial focus is deferred
via requestAnimationFrame so the browser layout is stable before focus() fires.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-03 15:43:12 +02:00
Hartmut 0d0707264d feat(admin): hard-delete resources (admin-only)
Adds a transactional hard-delete procedure behind adminProcedure that
removes a resource's assignments and vacations first, then the record
itself, and writes an audit log entry.  The ResourceModal exposes a
"Delete Resource" button (edit mode, ADMIN role only) with an inline
confirm step before the mutation fires.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-03 15:23:30 +02:00
Hartmut 3979d342c8 fix(ux): resolve tickets #51 #53 #54 from gitlooper sweep
- #51: Add permanent redirect /login → /auth/signin in next.config.ts
  so users/testers who type the common alias land on the correct auth page
- #53: Add "Allocations → New Planning Entry" link to empty states of
  ProjectDemandsTable and ProjectAssignmentsTable; add shortcut link in
  demands table header for canEdit users
- #54: Track confirmed dropdown selection in ResourcePersonPicker —
  green ring + checkmark icon shown when user picks from suggestions;
  cleared on any manual keypress so free-text is clearly unconfirmed

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-03 12:27:43 +02:00
Hartmut 5a8dc6c166 fix: resolve 3 UX bugs from gitlooper ticket sweep (#45 #47 #48)
- #47: Remove misleading asterisk from Budget (EUR) label in project
  wizard — budget is optional per canGoNext() logic
- #48: Parse Zod validation JSON in wizard submit error handler so users
  see "Responsible person is required" instead of raw JSON array
- #45: Expose isEntriesError from timeline query context; TimelineView
  now renders an explicit error message instead of a silent empty canvas
  when the getEntriesView query fails

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-03 11:25:42 +02:00
Hartmut bf8577dbaf feat(auth): proactive session expiry redirect across all delivery paths
- Split auth config into auth.config.ts (edge-safe, no argon2) and auth-edge.ts
  for middleware use; auth.ts now spreads the shared config
- Middleware wraps with auth() to redirect unauthenticated requests to /auth/signin
  before any page render; passes through /auth/, /api/, /invite/ paths
- SessionGuard client component watches useSession() and redirects on
  status=unauthenticated, closing the SPA navigation gap
- QueryCache + MutationCache in TRPCProvider redirect on UNAUTHORIZED tRPC errors
  without retrying; SessionProvider polls session state every 5 minutes
- Middleware tests updated for async auth wrapper and auth-edge mock

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-03 10:42:10 +02:00
Hartmut e7e525df49 fix(timeline): clear multi-select on drag start and lock in SSE edge-case coverage
- useTimelineDrag: onProjectBarMouseDown and single-alloc drag path now reset
  multiSelectRef + multiSelectState before starting a new drag, so the
  FloatingActionBar is dismissed immediately when an unrelated drag begins
- FloatingActionBar.test.tsx: 4 regression tests for the null-render guard
  (count=0) and all three label variants
- useTimelineSSE.test.ts: 2 new tests — tab hides during pending reconnect
  timer (clears timer, resyncs on next open) and first-ever connection fails
  before any open (retry open still resyncs correctly)
- assistant-tools-user-admin-inventory-read.test.ts: add isActive to expected
  findMany select shape (already in production, test was stale)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-02 21:16:10 +02:00
Hartmut 8d9e26872b fix(timeline): stabilize popovers on internal scroll + expand test coverage
B-1: useViewportPopover — ignoreScrollContainers option; scroll events
originating inside the timeline canvas no longer close point-anchor popovers
B-2: AllocationPopover, DemandPopover, NewAllocationPopover — thread
scrollContainerRef through so horizontal timeline scroll is ignored
B-3: AllocationPopover — staleTime 0 so SSE reconnect triggers immediate refetch
B-4: useViewportPopover.test.ts — 6 new tests (scroll close, ignore container,
resize close, style clamping)
B-5: AllocationPopover.test.tsx — loading state + happy-path tests added

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-02 20:49:08 +02:00
Hartmut 41eb722369 feat: user invite flow, deactivate/delete, favicon, dashboard loading fix, admin full-width
- Invite flow: admin can invite users by email with role selection; accept-invite page
  sets password and creates the account; 72-hour token expiry; E2E tests
- User deactivate/reactivate/delete: new tRPC procedures + UI buttons; deactivation
  revokes all active sessions immediately; delete cascades vacation/broadcast records;
  isActive field added via migration 20260402000000_user_isactive
- Auth: block login for inactive users with audit entry
- Favicon: SVG favicon + ICO/PNG fallbacks (16, 32, 180, 192, 512px); manifest updated
- Dashboard: GridLayout dynamic-import loading skeleton prevents blank dark area
  on first login before react-grid-layout chunk is cached
- Admin users: remove max-w-5xl constraint so table uses full page width
- Dev: docker container restart workflow documented in LEARNINGS.md; Prisma generate
  must run inside the container after schema changes (named node_modules volume)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-02 20:19:26 +02:00
Hartmut e5ecea81c5 fix(auth): resolve MFA post-activation login failures — tickets #38 #40 #41
#41 (critical): Replace plain Error throws in authorize() with CredentialsSignin
subclasses (MfaRequiredError / MfaRequiredSetupError / InvalidTotpError).
Auth.js v5 forwards CredentialsSignin.code to the client via SignInResponse.code;
plain throws become CallbackRouteError and the message is never visible.
Signin page now checks result.code ?? result.error for exact code matching.

#38: MfaPromptBanner converted to fully client-side component via
trpc.user.getMfaStatus.useQuery() — disappears immediately after MFA enable
without requiring page reload. Snooze key remains userId-scoped via useSession().
Server-side prisma.user.findUnique call removed from (app)/layout.tsx.

#40: NEXTAUTH_URL default fallback removed from docker-compose.yml.
The variable is now required (:?) — docker compose up fails with a descriptive
error if the value is missing, preventing silent localhost redirect bugs.

Tests: auth.test.ts (5), MfaPromptBanner.test.ts (7), reset-password.test.ts (6)
All new tests green. pnpm --filter @capakraken/web exec tsc --noEmit clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-02 00:20:47 +02:00
Hartmut 435c871e1f security: implement tickets #28-#35 + architecture decision #30
#28 - TOTP rate limiting (verifyTotp): added totpRateLimiter (10 req/30s),
  throws TOO_MANY_REQUESTS before DB hit; 16 unit tests including rate-limit
  exceeded + userId key isolation.

#29 - /api/reports/allocations role check: only ADMIN/MANAGER/CONTROLLER may
  access; returns 403 otherwise; 9 unit tests (401 unauthenticated, 403 for
  USER/VIEWER, 200 for allowed roles + xlsx format).

#31 - pgAdmin credentials moved out of docker-compose.yml into env vars;
  PGADMIN_PASSWORD is now required (:?) to prevent accidental plaintext
  exposure in committed files.

#34 - Server-side HTML sanitization for comment bodies via stripHtml():
  strips all tags + decodes safe entities before persistence; 16 unit tests
  covering passthrough, injection patterns, entity decoding.

#35 - MFA setup prompt banner (MfaPromptBanner): shown to ADMIN/MANAGER users
  without TOTP enabled; user-scoped localStorage snooze (7 days); links to
  /account/security; accessibility role=alert; 7 structural unit tests.

#33 - Auth anomaly alerting cron (/api/cron/auth-anomaly-check): detects
  HIGH_GLOBAL_FAILURE_RATE and CONCENTRATED_FAILURES in 30-minute window;
  CRITICAL notification to ADMINs; fail-closed via verifyCronSecret;
  10 unit tests.

#32 - MFA enforcement policy: added requireMfaForRoles field to SystemSettings
  schema + Prisma migration; auth.ts blocks login with MFA_REQUIRED_SETUP
  signal if role is enforced but TOTP not set up; signin page redirects to
  /account/security?mfa_required=1; settings schema + view model updated;
  11 unit tests.

#30 - API keys architecture decision documented in LEARNINGS.md; no code
  written — product decision required before implementation.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-01 23:25:06 +02:00
Hartmut bfdf0a82da security/platform: close audit findings #19–#26
Tests, CSP nonce middleware, SSRF guard, perf-route hardening,
Docker env isolation, migration runbook, RBAC E2E coverage.

Tickets resolved:
- #19: MfaSetup.test.ts — static source tests confirming local QR rendering
- #20: ssrf-guard.test.ts (16 tests) + webhook-procedure-support mock fix
- #21: /api/perf route.test.ts (5 tests) — header-only auth, fail-closed
- #22: middleware.ts (nonce-based CSP) + middleware.test.ts (6 tests);
       layout.tsx async + nonce prop; CSP removed from next.config.ts
- #23: Active-session registry enforcement verified (already in codebase)
- #24: docker-compose.yml REDIS_URL hardcoded (no host-env substitution)
- #25: docker-compose.yml REDIS_URL + docs/developer-runbook.md created
- #26: e2e/dev-system/rbac-data-access.spec.ts (12 tests, 3 roles × 4 procedures)

Quality gates: tsc clean, api 1447/1447, web 189/189 passing.
Turbo concurrency capped at 2 (package.json) to prevent OOM under
parallel test runs.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-01 22:14:20 +02:00
Hartmut 0e119cfe73 security: close audit findings #19–#23 and harden Docker setup (#24)
#19 MFA QR code: render locally via qrcode package, remove external qrserver.com request
#20 Webhook SSRF: add ssrf-guard.ts with DNS-verified IP blocklist; enforce on create/update/test/dispatch
#21 /api/perf: fail-closed when CRON_SECRET missing; remove query-string token auth
#22 CSP: remove unsafe-eval and unsafe-inline from script-src in production builds
#23 Active session registry: forward jti into session object; validate against ActiveSession on every tRPC request

#24 Docker: add missing packages/application to Dockerfile.dev; fix pnpm-lock.yaml glob;
    run db:migrate:deploy on container start so a fresh checkout boots without manual steps

Also: fix pre-existing TS error in e2e/allocations.spec.ts (args.length literal type overlap)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-01 18:19:21 +02:00
Hartmut fd75628e9d fix(allocations): recover from fully filtered empty state 2026-04-01 15:16:57 +02:00
Hartmut 7df751d5eb fix(allocations): expand grouped rows by default 2026-04-01 15:13:24 +02:00
Hartmut a71bbeb640 fix(timeline): stabilize overlay lifecycle 2026-04-01 14:41:03 +02:00
Hartmut fa5e654739 fix(timeline): harden project view interactions 2026-04-01 14:10:28 +02:00
Hartmut 2855567456 test(web): cover timeline project row layout 2026-04-01 09:29:43 +02:00
Hartmut 85744d1879 test(web): cover timeline render helper edges 2026-04-01 09:26:44 +02:00
Hartmut 1f71b345ee test(web): cover allocation visual state helpers 2026-04-01 09:24:38 +02:00
Hartmut f70ce9480d test(web): cover timeline drag math guards 2026-04-01 09:23:45 +02:00
Hartmut 403d59ad73 fix(web): stabilize timeline hover date matching 2026-04-01 09:15:24 +02:00
Hartmut 4edf3a32ac fix(web): keep segmented timeline allocations actionable 2026-04-01 08:54:15 +02:00
Hartmut 8c5be51251 feat(platform): checkpoint current implementation state 2026-04-01 07:42:03 +02:00
Hartmut 7908ab6d05 feat(web): strengthen report builder explainability 2026-03-31 23:07:36 +02:00
Hartmut 8cb34a1c9b feat(web): expand chargeability export explainability 2026-03-31 23:06:39 +02:00
Hartmut 160ba99b5c refactor(insights): share workbook export and ai defaults 2026-03-31 22:53:53 +02:00
Hartmut 05eeaab3f7 chore(settings): align default ai model handling 2026-03-31 22:52:29 +02:00
Hartmut 7ace137d16 feat(dashboard): tighten explainability detail views 2026-03-31 22:50:47 +02:00
Hartmut a36bca7ca7 refactor(admin): split system settings into section modules 2026-03-30 20:04:06 +02:00
Hartmut a19d2cbae0 refactor(settings): adopt environment-only runtime secret flow 2026-03-30 19:55:06 +02:00
Hartmut dd71e8f80b fix(comment): align mention audience with entity visibility 2026-03-30 18:50:36 +02:00
Hartmut 27b0e38b93 fix(web): portal remaining overlay menus 2026-03-30 14:20:05 +02:00
Hartmut ea2efabd7f fix(web): portal autocomplete overlays 2026-03-30 14:14:15 +02:00
Hartmut f0bea6235d fix(web): reuse project combobox in timeline popovers 2026-03-30 13:34:59 +02:00
Hartmut 9268a38df4 fix(web): restore comment typing and portal combobox menus 2026-03-30 13:32:51 +02:00
Hartmut 5b60cf5553 fix(web): portal skill tag suggestions 2026-03-30 13:29:28 +02:00