Files
Trapa-EurekaandClaude Opus 5 d810c81cf3 feat(core): persistence invariants, config/boundary validation, migration smoke test and correlation IDs (#10198)
Adds test harnesses for tenant isolation, TypeORM/MikroORM parity, persistence invariants and idempotency; startup validation for DB config; Nx plugin/core boundary rules; a fresh-database migration smoke test; and a correlation id carried from HTTP requests into docs queue jobs and logs.

Testing (packages/core/src/lib/core/testing):
- tenant-isolation: an in-memory tenant-aware repository, fixtures and strict assertions (a non-empty page, every row in the caller's tenant). Applied to EmployeeService and OrganizationProjectService.
- orm-conformance and persistence-invariants: suites that run the same checks under TypeORM and MikroORM (run-both-orms.sh uses yarn nx).
- idempotency: assertion helpers and specs for token cleanup (positive control), employee notifications and the Zapier timer webhook.
- database/migration-smoke.spec.ts: runs the full migration chain on a fresh better-sqlite3 database. It is excluded from the default core jest run; run it with `nx run core:test-migration-smoke`.

Idempotency:
- EmployeeNotificationService.create() takes an opt-in `absorbRedelivery`. With it set, an identical unread, unarchived notification under 60 s old (same receiver, entity, type, sender, title and message) is returned instead of inserted, and a missing key or a failed lookup inserts as usual. The event handler does not enable it (the in-process EventBus never redelivers), so every caller inserts one row per event as before.
- ZapierWebhookService skips resending a webhook that already succeeded for the same subscription, action and time log within 5 minutes. The key is reserved while in flight, failed deliveries stay retryable, pruning stops at the first unexpired entry, and the cache is capped at 10,000 entries.

Config (packages/config):
- An unknown DB_TYPE fails fast with the list of supported values; an empty DB_TYPE still means better-sqlite3; mongodb throws an Error.
- Pool and timeout variables are parsed with Number.parseInt semantics, only for postgres and mysql. They throw only where tarn already refused to start and warn otherwise. SQLite ignores them and still prints the startup values.

Observability:
- RequestContextMiddleware accepts an inbound x-correlation-id of 1-128 visible ASCII characters (otherwise it generates a UUIDv4) and echoes it on the response. CORS allows and exposes X-Correlation-Id. RequestContext.currentCorrelationId() is added.
- The docs queue carries the correlation id through job payloads (including bulk reindex) into pipeline outcome, error, dead-letter and enqueue-failure logs.

Boundaries and build:
- Every project gets a type tag. The ESLint depConstraints stop type:core depending on plugins, and plugins may depend only on core, shared libs and the known extension points (ai-chat, job-proposal, integration-ai, job-*-ui).
- core declares @gauzy/scheduler (package.json and implicitDependencies); the webapp Dockerfile copies scheduler's package.json.
- The integration-zapier jest config can import @gauzy/core (transformIgnorePatterns, allowJs, isolatedModules).
- Fixes the stale @nrwl/nx eslint-disable id in the e2e roles-permissions steps and the broken @gauzy/core mock in the docs document-scope spec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 23:35:52 +02:00
..
…

@gauzy/ui-react-components

Reusable React UI components and design tokens for Gauzy dashboards. Pure React + TypeScript — no Angular or plugin-system dependencies. Built with @nx/js:tsc (no CSS pipeline: components style themselves inline and register the few rules that need a stylesheet through useInjectedStyles).

Design tokens

  • themeTokens — theme-adaptive CSS custom-property references (var(--gauzy-card-2), var(--gauzy-text-color-2), var(--border-radius), var(--color-primary-default), …). Use these for anything mounted inside the Gauzy shell through @gauzy/ui-react; the component then follows the light/dark/corporate/material theme the user picked, exactly like Angular SCSS that uses nb-theme(...).
  • statusColor(status) — var(--color-<status>-default) for a Nebular status name.
  • theme — legacy flat object of hardcoded light-mode hex values. Kept for standalone previews; do not use it for in-app surfaces.

Components

Theme-adaptive ports of the Nebular / Gauzy primitives the dashboards are built from:

  • CounterPoint — port of <gauzy-counter-point>: a strip of up to 24 pill dots (total/value/color) or, with progress, a 10px progress bar. computeCounterPoints() / counterPointBackground() are exported for tests.
  • ProgressBar — port of <nb-progress-bar> (value, status, size, height, displayValue), painted with --progress-bar-<status>-*.
  • Badge — port of <nb-badge> in its unpositioned dashboard form (text, status).
  • Avatar — port of <ngx-avatar class="avatar-dashboard"> (name, src, caption, appendCaption, size, presence, onClick, variant: 'dashboard' | 'activity').
  • Spinner — port of the [nbSpinner] overlay (active, status, size, message); the parent must be position: relative.
  • Popover — click-toggled floating panel like [nbPopover] (content, placement, controlled open/onOpenChange); portaled to document.body, closes on outside click / Escape. computePopoverPosition() is exported for tests.

Layout / legacy:

  • Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter — compound card layout.
  • WidgetCard — label + value stat card built on Card.
  • Progress — minimal 4px bar (percent, color).
  • ColorDots — coloured dot indicators (count, colors).

Utilities

  • formatDuration(seconds) — HH:mm:ss
  • progressStatus(percent) — Nebular status for a percentage (mirror of @gauzy/ui-core/common)
  • currentWeekRange() / todayRange() — local date ranges
  • ensureStyleTag(id, css) / useInjectedStyles(id, css) — one-time <style> registration

Usage

import { CounterPoint, ProgressBar, Spinner, themeTokens } from '@gauzy/ui-react-components';

function WorkedToday({ seconds, capacity, loading }: { seconds: number; capacity: number; loading: boolean }) {
  return (
    <div style={{ position: 'relative', background: themeTokens.card1, borderRadius: themeTokens.radius }}>
      <Spinner active={loading} />
      <div style={{ color: themeTokens.text2 }}>Worked today</div>
      <CounterPoint total={capacity} value={seconds} color="#00D68F" />
      <ProgressBar value={42} height="5px" />
    </div>
  );
}