Commit Graph

265 Commits

Author SHA1 Message Date
Hartmut b3da8817dc refactor(web): extract render functions from TimelineProjectPanel into dedicated module
Move renderOpenDemandRow, renderProjectUtilOverlay, and renderProjectDragHandles
(534 lines) to timelineProjectRenderers.tsx. TimelineProjectPanel: 1230 -> 687 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 09:05:47 +02:00
Hartmut d1d33aa810 refactor(web): extract ReportResultsPanel and nav icons from monolithic components
Extract ReportResultsPanel (293 lines) from ReportBuilder (1231→1044 lines)
and move 38 inline icon components from AppShell (937→833 lines) to nav-icons.tsx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 08:58:31 +02:00
Hartmut 17f2de5f48 refactor(web): decompose AllocationsClient and UsersClient into focused subcomponents
AllocationsClient (1364→962 lines): extracted AllocationRow, AllocationGroupedBody,
OpenDemandsPanel, and AllocationBatchDialogs.
UsersClient (1338→895 lines): extracted UserEditModal and UserCreateModal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 08:49:50 +02:00
Hartmut 85e1bcc06f refactor(web): decompose ProjectWizard into step components
Extract each wizard step into its own file under project-wizard/:
StepBar, DynamicFieldInput, Step1Identity, ResourcePersonPicker,
Step2Timeline, Step3Staffing, Step4Suggestions, Step5Review.
Main file reduced from 1,385 to 112 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 08:30:33 +02:00
Hartmut f3fa902773 fix(web): make invalidation hooks async with Promise.all and fix cross-view staleness
- useInvalidateTimeline and useInvalidatePlanningViews now return
  Promise.all instead of fire-and-forget void calls
- Timeline mutations now use useInvalidatePlanningViews to also
  invalidate allocation list views, preventing stale data
- AllocationsClient sequential awaits replaced with single
  invalidatePlanningViews() call (parallel invalidation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 08:24:33 +02:00
Hartmut f18777c365 refactor(web): split TimelineContext into data, view, and display contexts
Reduces unnecessary re-renders by separating the monolithic 20+ property
context into TimelineDataContext, TimelineViewContext, and
TimelineDisplayContext. Panel components now subscribe only to the
slices they need.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 08:17:58 +02:00
Hartmut 7eac5816d6 feat(web): add error boundaries to uncovered route groups
Root, auth, invite, and setup routes now have error.tsx files,
ensuring every Next.js page route has error boundary coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 08:13:51 +02:00
Hartmut 4469fc42af fix(build): resolve Next.js build failures from invalid route exports
- Extract detectAuthAnomalies + THRESHOLDS from route.ts to detect.ts
  (Next.js rejects non-standard exports from route files)
- Add explicit RenderResult return type to test-utils customRender
- Skip ESLint during next build (runs separately via pnpm lint)
- Revert test file exclusions from tsconfig (breaks eslint parser)
- Update route.test.ts imports to match new file structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 07:40:00 +02:00
Hartmut d8aac21e2d test(e2e): add axe-core accessibility fixture and smoke spec
Adds @axe-core/playwright with a shared fixture providing an `axe`
helper. New a11y.spec.ts runs WCAG 2.1 AA checks on signin, dashboard,
timeline, allocations, resources, and projects pages. Currently reports
violations as warnings — upgrade to hard failures after fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 07:20:10 +02:00
Hartmut c794e82464 test(web): add 23 edge-case tests across UI components and lib utils
Covers: aria-sort/aria-labelledby attributes, non-Error throws in
ErrorBoundary, NaN/MAX_SAFE_INTEGER in formatCents, invalid dates,
carriage returns in CSV, self-closing HTML tags in sanitize, non-digit
input in DateInput, panel-click-not-dismissing in ConfirmDialog,
role="search" on FilterBar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 23:14:59 +02:00
Hartmut 797aa5e350 fix(a11y): add ARIA attributes to core UI components
AnimatedModal: ariaLabelledBy prop, EntityCombobox: combobox/listbox
pattern, FilterBar: role="search", SortableColumnHeader: aria-sort,
global-error: html lang attr, eslint label rule depth config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 23:06:25 +02:00
Hartmut 6830bfb314 refactor(web): extract 4 pure render functions from TimelineResourcePanel
Move renderAllocBlocksFromData, renderLoadGraph, renderHeatmapOverlay,
renderDailyBars into timelineResourceRender.tsx (707 lines).

TimelineResourcePanel reduced from 1,270 to 589 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 22:57:19 +02:00
Hartmut 9bd7172018 test(web): add 162 tests for animation components and hooks
Components: AnimatedNumber (14), InfiniteScrollSentinel (16),
FadeIn (22), StaggerList (26).

Hooks: useUrlFilters (32), useWidgetFilterOptions (27),
useProjectDragContext (27).

Web test suite: 96 → 103 files, 1076 → 1238 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 22:45:44 +02:00
Hartmut d3f721ce58 refactor(web): extract ResourcesClient types + inline components, fix test TS errors
Extract types.ts, FilterDropdown.tsx, BooleanBadge.tsx from
ResourcesClient.tsx into resource-client/ subdirectory.
ResourcesClient reduced from 1,613 to 1,507 lines.

Fix TypeScript strict mode errors across 8 test files:
- Add id/order to BlueprintFieldDefinition test objects
- Use FieldType enum instead of string literals in useFilters
- Add non-null assertions for mock.calls array access
- Type ScrollDiv for jsdom scrollLeft workaround
- Fix exactOptionalPropertyTypes violations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 22:40:24 +02:00
Hartmut dcac9952ca test(web): add 232 tests for catalog, presets, skeleton, hooks
Lib: blueprint-field-catalog (74).

Hooks: useAppPreferences (25), useTheme (19),
useMultiSelectIntersection (12), useTimelineKeyboard (21).

Components: ColumnTogglePanel, DateRangePresets (17, timezone-safe),
ShimmerSkeleton (29), SuccessToast.

Fix ShimmerGroup tests to use plain divs (ShimmerSkeleton doesn't
forward the style prop from cloneElement).
Fix DateRangePresets tests to compute expected dates via toISOString
matching the component's UTC conversion.

Web test suite: 87 → 96 files, 844 → 1076 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 17:27:35 +02:00
Hartmut a3d75973ee test(web): add 291 tests for parsers, hooks, and UI components
Lib utilities: scopeImportParser (31), status-styles (58),
planningEntryIds (10), uuid (11).

Hooks: useFilters (28), useRowOrder (18), usePermissions (30),
useViewPrefs (24).

Components: AnimatedModal (14), DateInput (22), InfoTooltip (13),
ProgressRing (19).

Web test suite: 75 → 87 files, 553 → 844 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 17:14:11 +02:00
Hartmut 98dca6126f test(web): add 210 tests for lib utils, hooks, and UI components
Lib utilities: format (38), sanitize (12), project-colors (18),
csv-export (14).

Hooks: useDebounce (8), useTableSort (22), useLocalStorage (18),
useColumnConfig (19).

Components: BatchActionBar (17), SortableColumnHeader (14),
FilterChips (14), ErrorBoundary (16).

Web test suite: 63 → 75 files, 343 → 553 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 17:11:00 +02:00
Hartmut c0ba062460 test(web): add 57 UI component and hook tests with jsdom cleanup
Fix jsdom environment: add esbuild automatic JSX transform and
afterEach cleanup to prevent DOM leakage between tests.

Components: Badge (8), Button (13), FilterBar (5), EmptyState (8),
ConfirmDialog (8), useSelection hook (15).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 17:06:42 +02:00
Hartmut 63db4a09e6 refactor(web): set up component test infra + decompose ProjectWizard
Phase 4a: Add @testing-library/react, user-event, jest-dom, jsdom.
Switch vitest environment to jsdom, add setup file, create test-utils
with QueryClient wrapper.

Phase 4b: Extract ProjectWizard form logic into project-wizard/ subdir:
- types.ts: WizardState, Assignment, constants, factory functions
- useProjectWizardForm.ts: form state hook + canGoNext pure function

Phase 4c: 32 tests for canGoNext validation (all 5 steps), makeDefaultState,
and makeReq factory function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 17:00:45 +02:00
Hartmut 9bd3781c03 fix(types): flatten tRPC Zod schema types to resolve TS2589 inference depth errors
Cast Zod schemas with .refine()/.superRefine() to z.ZodType<InferredType> at the
procedure level. This short-circuits TypeScript's deep type recursion through
tRPC's middleware chain, eliminating 4 of 5 @ts-expect-error TS2589 suppressions
in web components (VacationModal, ProjectModal, UsersClient, CountriesClient).

Applied same pattern to allocation, timeline, staffing, dashboard, project, and
resource query/mutation procedures to reduce client-side type depth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 15:28:12 +02:00
Hartmut 0d79f97d7a fix(types): remove unnecessary as any casts in web components
- ProjectHealthWidget: row already typed as ProjectHealthRow with id field
- ResourceDetail: use narrowed unknown cast instead of any for error code
- provider.tsx: same pattern for TRPCClientError data access
- ChatPanel: use intersection type for Next.js typed route push

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 15:13:06 +02:00
Hartmut 9051ff73d0 fix(types): replace structural DB types with Pick<PrismaClient> and remove Prisma boundary as any casts
Replace ~440 lines of hand-written structural DB client types across 7 lib files
with `Pick<PrismaClient, ...>` from @capakraken/db. This eliminates all `as any`
casts at Prisma boundaries (cron routes, allocation effects, vacation procedures)
and surfaces two pre-existing bugs:
- weekly-digest.ts: `db.allocation.count()` called non-existent model (fixed → demandRequirement)
- estimate-reminders.ts: `submittedAt` field doesn't exist on EstimateVersion (fixed → updatedAt)

Also adds root eslint.config.mjs so lint-staged can lint package files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 15:09:16 +02:00
Hartmut 82acc56b8d chore: add pre-commit hooks, tighten ESLint, activate Sentry DSN, publish CI coverage (Phase 1)
- Install husky v9 + lint-staged: pre-commit runs eslint --fix and prettier on staged files
- Tighten ESLint base config: no-console→error, ban-ts-comment (ts-ignore banned, ts-expect-error with description allowed), reportUnusedDisableDirectives→error
- Migrate web app from deprecated `next lint` to `eslint src/` with flat config and react-hooks plugin
- Convert all 5 @ts-ignore to @ts-expect-error with descriptions, remove stale disable comments
- Add NEXT_PUBLIC_SENTRY_DSN to docker-compose.prod.yml and .env.example
- Add coverage artifact upload step to CI test job
- Pre-existing violations (102 warnings) downgraded to warn in web config for Phase 2 cleanup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 14:49:29 +02:00
Hartmut 78d50b78d3 fix: script portability and npm security updates
Scripts:
- stop.sh: replace Linux-only fuser with cross-platform lsof fallback
- start.sh: parameterize port (APP_PORT) and container name (dynamic lookup)
- app-dev-start.sh: cross-platform stat (GNU -c / BSD -f) and setpriv/su fallback
- deploy-compose.sh: parameterize Docker registry via DOCKER_REGISTRY env var
- harden-postgres.sh: make DB_USER and DB_NAME configurable via env vars

NPM security:
- next: 15.5.12 → 15.5.15 (fixes HTTP request smuggling CVE)
- nodemailer: 8.0.1 → 8.0.5 (fixes SMTP command injection CVEs)
- lodash-es: add pnpm override to force >=4.18.0 (fixes code injection + prototype pollution)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 14:06:58 +02:00
Hartmut e4bf121b33 feat(ui): weekend/vacation/checkbox colors follow accent theme
- Unify Saturday+Sunday into single isWeekend flag (header + grid lines)
- Replace hardcoded amber vacation bar/tooltip colors with brand-* classes
- Add global accent-color for checkboxes and radio buttons via CSS variable
- Update VACATION_TIMELINE_COLORS/BORDER to use brand palette (SICK stays red)
- Vacation-only tooltip uses neutral dark surface with brand accent border

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 14:06:44 +02:00
Hartmut 0339b11038 fix(ui): remove utilization row background tint from timeline
Remove the colored background tint for 50-100% utilized rows entirely.
Only over-utilized rows (>100%) keep the red warning tint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:53:21 +02:00
Hartmut 5afc6c8c94 fix(ui): remove blue-shifted hardcoded colors from timeline components
Replace hardcoded blue-shifted rgba values and slate-* classes with neutral
CSS variable references in timeline resource/project panels, tooltips,
constants, and heatmap mono palette. Change utilization row tint from blue
to green. Replace slate-950 open demand backgrounds with --surface-card.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:46:41 +02:00
Hartmut 60d89a1bc8 fix(ui): replace blue-shifted hardcoded gradient in WidgetContainer dark mode
The widget wrapper had a hardcoded dark gradient using rgba(22,23,26) and
rgba(16,17,19) which are blue-shifted. Replace with CSS variable references
--surface-elevated and --surface-card for neutral dark backgrounds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:35:44 +02:00
Hartmut b663755749 fix(ui): add gray-950 opacity variant overrides to dark theme normalization
Tailwind's gray-950 (rgb(3,7,18)) is blue-shifted. Add solid and opacity
variant overrides (/96, /95, /60, /50, /45, /40) to map gray-950 to
the neutral --surface-card CSS variable in dark mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 09:26:58 +02:00
Hartmut 05aa864359 refactor(ui): replace inline INPUT_CLS/LABEL_CLS/BTN_DANGER constants and action link classes with CSS component classes
Remove duplicated Tailwind class string constants from 15 component files.
Use app-input, app-select, app-label, app-action-danger-btn, and
app-action-delete CSS component classes from globals.css instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 09:21:03 +02:00
Hartmut 9ba49c9ab8 fix(ui): add dark mode variants to dashboard, layout, notification and chargeability components
Add missing dark: class variants for backgrounds, borders, and text across
dashboard widgets, AppShell sidebar, notification cards, and the chargeability
report table. Replace hardcoded slate/gray hex values with CSS variable
references. Fix chargeability hover tint and remove ineffective sticky thead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 09:20:50 +02:00
Hartmut 2a91257e69 fix(ui): neutralise dark theme — eliminate blue-shifted grays across all surfaces
Replace blue-shifted CSS variable values with balanced neutral RGB, add
comprehensive dark-mode overrides for bg-gray-*, border-gray-*, text-gray-*,
and their dark: variant forms. Remove light-mode text/border overrides that
leaked into both modes. Replace hardcoded rgba(255,255,255,...) in component
classes with CSS variable references. Merge duplicate fadeSlideIn keyframe
into fadeSlideUp. Change .app-data-table overflow to clip for sticky compat.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 09:20:38 +02:00
Hartmut db892ae285 fix(ui): move all :is(.dark) component class rules outside @layer
Rules inside @layer components lose to unlayered styles in the CSS cascade,
causing dark mode overrides to be silently ignored. Move ALL :is(.dark) rules
for app-surface, app-surface-strong, app-toolbar, app-input, app-select,
app-label, app-page-title, app-page-subtitle, app-data-table, and action
classes outside @layer — the same fix that resolved app-data-table white bg.

Also switch app-surface/strong from background: shorthand to separate
background-color + background-image to ensure the dark surface-card base
color is always applied independently of the gradient overlay.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 07:19:16 +02:00
Hartmut 9b5cd8549d refactor(ui): replace inline INPUT_CLS/BTN_DANGER/action link constants with component classes
- Replace 13 local INPUT_CLS/SELECT_CLS/LABEL_CLS/BTN_DANGER constants with
  app-input, app-select, app-label, app-action-danger-btn component classes
  (CustomFieldFilterBar, RolePresetsEditor, FieldCard, BlueprintFieldCatalog,
  BlueprintFieldEditor, BlueprintsClient, EstimateWizard, EstimateWorkspace-
  DraftEditor, DemandLineEditor, ScopeItemEditor, AssumptionEditor,
  ProjectWizard, BulkEditModal)
- Replace inline text-blue-600/text-red-500 action link strings with
  app-action-edit / app-action-delete in AllocationsClient, ProjectsClient,
  ScenarioPlanner, ProjectDemandsTable, RolesClient, BlueprintsClient,
  CreateTaskModal, RateCardsClient, UsersClient, ManagementLevelsClient

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 07:02:08 +02:00
Hartmut e575462b01 refactor(ui): clean dark theme — global-first, variable-backed approach
Phase 1: Replace all @apply dark: in @layer components with explicit :is(.dark)
rules for .app-input, .app-select, .app-label, .app-page-subtitle,
.app-page-title, .app-data-table th. This fixes unreliable PostCSS variant
handling in Tailwind v4 @layer components.

Phase 2: Add missing global dark overrides for interactive text colors:
text-blue-600/500, text-red-500/400, text-indigo-600/700, text-amber-600,
plus hover states. Add :is(.dark) option for native <select> dropdowns.

Phase 3: Add semantic component classes .app-action-edit, .app-action-delete,
.app-action-danger-btn — variable-backed, no hardcoded hex values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 07:01:59 +02:00
Hartmut ddd711f93f fix(ui): fix .app-data-table dark mode — @apply dark: unreliable in @layer components
Replace `@apply dark:bg-gray-900/95` with an explicit `:is(.dark) .app-data-table`
rule using CSS variables, matching the established pattern of `.app-surface` and
`.app-toolbar`. Fixes Allocations, ResourceTableWidget, and ProjectTableWidget
all appearing white in dark mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 22:51:06 +02:00
Hartmut d1a21a79b2 fix(ui): comprehensive dark-theme hardcoded color pass
Phase 1 — globals.css: add ~45 new dark-mode override rules covering 250+
component instances at once:
- bg-*-50 (red/green/blue/yellow/amber/purple/indigo/orange/brand/emerald)
- border-*-200 (colored alert/badge borders)
- hover:bg-*-50/100 (colored hover states)
- text-amber-700/orange-600/green-600/emerald-700/brand-700 (missing overrides)
- divide-gray-50 (ChargeabilityWidget sticky section dividers)

Phase 2 — targeted component fixes:
- Button.tsx: add dark variants to secondary (bg-gray-800) and ghost variants
- DynamicFieldEditor.tsx: add dark variants to INPUT_NORMAL and INPUT_ERROR constants
- WidgetContainer.tsx: replace slate-900 (blue-tinted) gradient with neutral
  surface-card values (rgb 22,23,26 / 16,17,19)
- status-styles.ts: add explicit dark variants to PROJECT_STATUS_BADGE and
  ORDER_TYPE_BADGE (consistent with other badge maps in same file)

Phase 3 — dashboard widget tables:
- TopValueWidget: dark thead, tbody divider, row hover
- DemandWidget: dark thead, tbody divider, row hover
- ChargeabilityWidget: dark sticky h3 headers (bg-white→surface-card),
  border-gray-100 thead rows, divide-gray-50 tbodys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 22:37:43 +02:00
Hartmut 13262b5cec refactor(ui): unify dark theme — replace hardcoded hex with CSS variables
- Replace sidebar #0d0e22 hardcoded hex with .sidebar-panel class backed by
  --surface-card CSS variable so all three sidebar elements (desktop, mobile,
  mobile header) share the same neutral-dark color as the main content
- Remove purple logo gradient (dark:from-[#0d0e22] dark:to-[#13162a]) — now uses
  --surface-elevated for a neutral, unified look
- Add .dark slate-*/gray-900 overrides: bg-slate-700/800/900, border-slate-800,
  hover:bg-slate-800 all map to --surface-elevated/--surface-card/--border-subtle
- Remove dead hardcoded rgb(45 51 71) rule for dark bg-gray-100 (was overridden
  further down anyway; now consistently uses --surface-elevated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 22:25:10 +02:00
Hartmut 1a2f7de5bd fix(ui): replace hardcoded purple values with accent-adaptive CSS variables
- Dark surface vars changed from purple-navy (8 8 22) to neutral near-black (10 10 12)
  so sky/emerald/amber accent themes no longer have a purple cast
- Light surface vars made nearly neutral (252 252 253) — lavender tint removed
- All rgba(100, 80, 160, ...) replaced with rgb(var(--accent-400) / ...) in
  .app-surface, .app-surface-strong, and .app-toolbar shadows/borders
- .app-page-title dark gradient midpoint changed from hardcoded #e8e4ff to
  rgb(var(--accent-100)) so it adapts to the chosen accent color
- Body light-mode background gradient opacity reduced to avoid over-tinting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 22:17:50 +02:00
Hartmut 1383169352 feat(ui): Aurora design system — glassmorphic dark mode, warm light mode, snappy animations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 22:10:52 +02:00
Hartmut 97cfd0ed90 fix(security): raise password minimum to 12 chars, hide raw error messages, add audit script
- Password validation: min(8) → min(12) across auth.ts, user-procedure-support.ts,
  and invite.ts (aligns with NIST SP 800-63B modern recommendations)
- Error boundary: stop rendering raw error.message which could leak internal
  details; always show the generic fallback text
- Add `pnpm audit` script (--audit-level=high) for dependency vulnerability scanning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:48:51 +02:00
Hartmut afabaa0b7a fix(security): prevent TOTP replay attacks and fix user enumeration in verifyTotp
Adds lastTotpAt timestamp to User model. After a successful TOTP validation,
the timestamp is recorded. Any reuse of the same code within the 30-second
window is rejected as a replay attack.

verifyTotp now returns a single generic UNAUTHORIZED error regardless of
whether the user ID is invalid or TOTP is not enabled, preventing enumeration
of user IDs and MFA status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:41:09 +02:00
Hartmut 1833182e90 fix(security): harden input validation schemas and fix SSR sanitize bypass
- blueprint rolePresets: cap array at 100 items to prevent storage abuse
- notification CreateManagedNotification: add .max() on title (500),
  body (2000), type (100), entityType/entityId (200), link (1000),
  taskAction (200)
- settings: add .max() on all string config fields; add regex allowlist
  (/^[a-zA-Z0-9._-]+$/) on model name fields (geminiModel,
  azureDalleDeployment, azureOpenAiDeployment) to prevent path manipulation
- sanitizeHtml: fix SSR bypass — server-side branch now strips HTML tags
  instead of returning the raw string unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:38:16 +02:00
Hartmut 9e31c6d972 fix(security): harden cron and API route authentication
- public-holidays cron: replace fail-open inline auth check with verifyCronSecret
  (was open to unauthenticated access when CRON_SECRET unset)
- /api/perf: replace timing-unsafe string comparison with verifyCronSecret
- /api/health: strip baseUrl and latency fields from response to avoid
  leaking infrastructure details (NEXTAUTH_URL config, internal timings)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:38:02 +02:00
Hartmut ebeb180f3f fix(ui): make project detail and scenario pages full width
Replace max-w-5xl/max-w-7xl constrained wrappers with the app-page utility
class, consistent with other full-width pages like the projects list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:14:42 +02:00
Hartmut 5cc177ccf9 fix(dashboard): prevent stale DB data from overwriting newer localStorage layout
Two related fixes:
1. When localStorage has a saved layout, mark it as authoritative so a DB
   response carrying older data (e.g. from a save cancelled by navigating away
   within the 2-second debounce window) cannot overwrite it.
2. Flush any pending debounced DB save immediately on component unmount so
   that navigating away within the window doesn't silently lose changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:14:36 +02:00
Hartmut 43de66e982 feat(api): add audit helpers, tool registry, shared tool manifest types, and UI primitives
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:14:26 +02:00
Hartmut 1a67af6761 fix(dashboard): measure grid container width after hydration completes, not just on mount 2026-04-09 20:58:57 +02:00
Hartmut 5ad1048519 feat(dashboard): expand grid to 16 columns with auto-migration for saved 12-col layouts 2026-04-09 20:50:40 +02:00
Hartmut 446aea5319 feat(dashboard): combine vertical + horizontal compaction for gap-free widget layout 2026-04-09 20:42:49 +02:00