* fix(tags): count tag usages with COUNT(DISTINCT) and fill tagTypeName on MikroORM
TagService.findTags LEFT JOINs all 25 tagged relations (plus many-to-many
custom fields) in one query and counted each with COUNT(...). The joins
multiply rows, so every counter was multiplied by the matches of the other
relations: a tag on 2 employees and 3 tasks reported 6 + 6 = 12 usages on
the Tags page instead of 5. Use COUNT(DISTINCT ...) for every counter.
The MikroORM branch also never set `tagTypeName`, which the Tags page shows
in its Type column ("—" for every tag). Set it from the populated tagType.
The MikroORM branch still returns no *_counter fields (usage shows 0);
that needs an aggregate query of its own.
* refactor(tags): build the usage joins and counters from one table; always load tagType
- Drive the 25 relation LEFT JOINs and COUNT(DISTINCT) counters from an
exported TAG_USAGE_COUNTERS table instead of 50 near-identical lines
(Sonar duplication on new code).
- MikroORM: always populate tagType so tagTypeName is set even when the
caller requests no relations.
- Spec: assert each relation is joined and counted under its expected
counter (and nothing else), plus a many-to-many custom field.
* test(tags): cover tagType loading and tagTypeName on the MikroORM branch
* fix(candidate,equipment-sharing): apply the requested sort order in pagination
The candidates and equipment sharing tables sort on the server, but
CandidateService.pagination and EquipmentSharingService.pagination build
their own queries and ignored the requested order in both ORM branches,
so sorting by their date (and candidate status) columns had no effect.
Same bug as the employee (#10338) and time off (#10335) paginations.
Apply the order through the shared parseSortOrder() allowlist:
- candidates: appliedDate, hiredDate, rejectDate, status
- equipment sharing: shareRequestDay, shareStartDay, shareEndDay
(TypeORM via addOrderBy on the hand-built query builder)
* test(candidate,equipment-sharing): cover the sort order forwarded by pagination in both ORM branches
* fix(time-off): apply the employee, description and policy filters with MikroORM
In the MikroORM branch of TimeOffRequestService.pagination, the text
filters read `where.user`, `where.description` and `where.policy` from the
local query object being built (which only holds tenant, organization,
status, dates...) instead of the client filter, so filtering the time off
table by employee name, description or policy name had no effect with
MikroORM. The TypeORM branch reads them from options.where correctly.
Read them from options.where, and cover the three filters with tests.
* test(time-off): assert both halves of the name + date range condition
* fix(time-off): driver-compatible text filters and no empty name keywords (MikroORM)
- MikroORM emits `$ilike` verbatim as ILIKE, which MySQL and SQLite reject.
Add mikroOrmContains() in core/utils: `$ilike` on PostgreSQL, `$like`
elsewhere (their LIKE is already case-insensitive), and use it for the
employee name, description and policy filters.
- Split the employee name on any whitespace and drop empty keywords: with
repeated spaces, split(' ') produced '' and a `%%` pattern matching
every name.
- Tests for both, plus the helper.
* refactor(time-off): push both name conditions in one call (Sonar S7778)
* fix(core): detect the MikroORM driver from dbMikroOrmConnectionOptions for mikroOrmContains
getDBType() reads dbConnectionOptions (the TypeORM options, no driver)
and matches the driver with instanceof, while the MikroORM config sets
`driver` to the driver class in dbMikroOrmConnectionOptions. Under
MikroORM it therefore always answered PostgreSQL, so mikroOrmContains()
still produced $ilike on MySQL / SQLite.
Add isMikroOrmPostgres(), which reads dbMikroOrmConnectionOptions.driver
and matches the class or an instance, and use it as mikroOrmContains()'s
default. getDBType() itself is left unchanged: its other MikroORM callers
(custom entity fields, seeds) would change behaviour on MySQL / SQLite.
* fix(employee): apply the requested sort order in employee pagination
The employees table marks Income, Expenses, Bonus and Time Tracking as
sortable and sorts on the server (order[averageIncome]=ASC etc.), but
EmployeeService.pagination builds its own query and ignored options.order
in both the TypeORM and MikroORM branches, so those sorts had no effect.
Same bug as #1941 in the time off pagination.
Move the order sanitizing added for #1941 into a shared parseSortOrder()
in core/utils (allowed columns only, ASC/DESC only, client key order kept),
use it for employees and time off, and cover it with unit tests.
* fix(employee): access the required pagination options without optional chaining
options is a required parameter and is already destructured unguarded
above, so the optional chaining in the MikroORM branch was inconsistent
(DeepScan INSUFFICIENT_NULL_CHECK).
* fix(employee): parse the sort order inside each ORM branch
Reading options.order before the switch was an unguarded access ahead of
the TypeORM branch's `options && ...` checks (DeepScan
INSUFFICIENT_NULL_CHECK). Parse it in each branch, following that
branch's existing access style.
* refactor(employee): pass the parsed sort order inline
Avoid lexical declarations in case blocks (Sonar S6836) and use an
optional chain (S6582). An empty order map leaves the query unsorted,
so no conditional spread is needed.
* test(employee): cover the sort order forwarded by pagination in both ORM branches
* fix(time-off): apply the requested sort order in time off pagination
The Time Off table sorts on the server (ServerDataSource sends
order[start]=ASC etc.), but TimeOffRequestService.pagination builds its
own query and ignored options.order in both the TypeORM and MikroORM
branches, so sorting by Start, End or Request Date had no effect.
Pass the requested order to the ORM, keeping only the sortable date
columns (start, end, requestDate) with an ASC/DESC direction.
Closes#1941
* fix(time-off): only accept string sort directions
Read the direction only when the query value is a string instead of
stringifying any value (Sonar S6551: objects would become '[object Object]').
* fix(time-off): keep the client's sort key precedence
Iterate the requested order keys instead of the allowlist, so a
multi-column sort keeps the order the client asked for.
* feat(i18n): add strings for the add-employee dialog and form sections
* feat(user-forms): upload the profile photo through the shared avatar uploader
* feat(user-forms): group basic info fields into profile, account and employment sections
* feat(employees): track queued employees and the current entry in the add dialog
* feat(employees): redesign the add-employee dialog with labelled steps, review list and fixed footer
* style(employees): style the add-employee dialog header, steps, list and footer
* feat(tags): draw the picker checkbox and show selected tags as tinted labels
* style(tags): compact rows for the tag picker dropdown panel
* fix(employees): vertically centre the Show Deleted toggle in the header
* fix(employees): give Status and Screen Capture columns room from Name and Email
* fix(employees): address review feedback on the add-employee dialog
- mark template-only members protected
- translate the step bar's aria-label
- clear loading in a finally block so a failed create can be retried
- fit the step bar on narrow dialogs by naming only the current step
* fix(employees): resolve SonarCloud reliability issues in touched files
- mark fire-and-forget promises with void (typescript:S9383)
- drop async from delete(), which awaits nothing (typescript:S7503)
* fix(user-forms): hide SUPER_ADMIN from the role picker once the permission check resolves
excludeRoles() resolves after the role field may already have rendered its
options, and it mutated the excludes array in place, so the field kept
offering SUPER_ADMIN to users without that role.
- basic info form: assign a new excludes array instead of pushing
- role field: cache the fetched roles and re-filter them whenever excludes
changes, clearing a selection that is no longer allowed
* fix(user-forms): keep a preselected role until the role list has loaded
applyExcludes() also runs from the excludes setter, which can fire before
getAll() returns. With no roles loaded every preselected role looked
disallowed and was cleared. Only check the selection once the roles have
loaded; renderRoles() re-runs the check at that point.
* fix(timer): toast a setting change only when the value changes
Re-clicking the same Desktop Timer setting wrote it again and stacked another success alert. Skip the write and the toast when the stored value is unchanged.
Fixes#8479
* fix(timer): compare settings against the last saved value
SSL and auto-start edit the stored object before the equality check, so those saves were skipped.
* test(core): pin what the tenant-isolation guards actually do
TenantBaseGuard and TenantPermissionGuard gate most controllers
(@UseGuards(TenantPermissionGuard, PermissionGuard)), yet no spec exercised
them: every other suite mocks or overrides them. This adds 39 behavioural
tests covering the tenant from the request context, the tenant-id header
path, the GET/DELETE query and data.findInput paths, the POST/PUT/PATCH body
paths, @Public, the super-admin switch, handler-over-controller permission
metadata, de-duplication, and the 5-minute permission cache (hit, miss,
per-role key).
Two current behaviours are pinned deliberately and documented in the spec,
not changed: a matching tenant-id header is the whole check (a body or
query tenantId naming another tenant passes the guard, so services must take
the tenant from context), and a permission verdict is cached for 5 minutes.
Mutation-checked: making the header or body comparison always pass,
ignoring the base guard's verdict, dropping the allowSuperAdminRole switch,
or changing the cache TTL each turns the suite red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(core): follow the house let/const rule and drop the lint findings in the guard spec
Review follow-up (Greptile). The context helper now declares the request
headers with let and rebuilds them instead of mutating a const, per the
repository's rule. Also clears the ESLint error (empty stub function) and
the nine no-explicit-any warnings the new spec added: typed casts through
unknown, and one typed handle on env.allowSuperAdminRole. Still 39/39, and
all five guard mutations are still caught.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(record-view): add opt-in wide input to the record drawer
* feat(record-view): toggle the wide drawer class from the input
* style(record-view): size and pad the wide drawer for long-form content
* feat(tasks): render task descriptions from GitHub-flavoured markdown and safe HTML
* feat(tasks): add task view component deriving pills, people and description
* feat(tasks): lay out task view like an issue tracker with a collapsible details panel
* style(tasks): style task view details, avatars and markdown content
* feat(tasks): declare the task view component
* feat(tasks): show the task view in the wide drawer
* refactor(tasks): drop the record-view descriptor now served by the task view
* feat(record-view): add markdown and status field types, section variants and row icons
* feat(record-view): render panels, person chips, status and markdown in the shared view
* feat(tasks): describe the task view through the shared record view
* refactor(tasks): remove the separate task view component
* refactor(record-view): split markdown block rendering into per-block renderers
* refactor(record-view): derive avatar hue without bitwise truncation
* fix(record-view): keep fenced code intact when stripping comments and split list parsing
* fix(record-view): guard meta rows by permission and use inject() with class bindings
* fix(tasks): honour done statuses when flagging an overdue task
* fix(record-view): close code fences only on a matching fence and resolve Sonar findings in the markdown renderer
* style(record-view): merge the duplicate wide drawer selector
* fix(record-view): detect tilde fences, keep terminal # in headings and make placeholder restoration terminate
* fix(record-view): strip placeholder marks from reference URLs and restore placeholders in one pass
* fix(auth): make email verification work end to end and stop register dead-ends
Verification link and notice
- /auth/confirm-email no longer sits behind NoAuthGuard. Registering signs the
user in, so the emailed link was opened by a signed-in user, redirected to the
dashboard before the resolver ran, and never verified anything. The token is
still required and still single use (the API clears it on success).
- A refused or expired link now shows its message instead of an endless spinner,
and a successful one marks the signed-in user verified in the store.
- New "verify your email" notice with Resend (POST /auth/email/verify/resend-link,
already throttled 3/min) in the main layout and on tenant onboarding. It shows
only when GET /auth/email/verify/status (new, same feature flag, so 404 where
verification is off) confirms the user is unverified.
- Settings > Billing: an unverified admin with no linked subscription is told a
paid plan connects once the address is verified.
Email sending
- Templates fall back to English when the recipient's locale has none, instead
of rendering an empty email (verification exists only in en/bg/he/ru).
- Send failures are logged with the provider code, SMTP reply code and command,
every address masked; verification sends are now recorded in email_sent with
status SENT/FAILED (subject only, never the link). A transport that fails
verification throws instead of returning undefined.
- resend-link answers 503 when the provider refused the message, not "OK".
- The verification link encodes the address (plus-addressing survived as a space).
- A caller-supplied appEmailConfirmationUrl is honoured only on an origin this
deployment serves (CLIENT_BASE_URL, the configured links, EMAIL_LINK_ALLOWED_ORIGINS;
"*" disables the check).
- Auth emails carry X-PM-TrackLinks: None so Postmark stops storing tokens as clicks.
- {{appLink}} falls back to CLIENT_BASE_URL when APP_LINK is empty: every
hosted deployment has APP_LINK empty, so welcome emails linked to localhost:4200.
Register
- A refused sign-up shows the API's 4xx message (e.g. "A subscription is
required...") instead of "Something went wrong", and the 403 checkoutUrl is
offered as a "Continue to checkout" button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(auth): confirm-email trusts the API, not the link, for who was verified
Review follow-ups on the confirm-email page:
- After a successful confirmation, re-read GET /auth/email/verify/status
instead of comparing the link's email parameter with the signed-in user.
The token decides which account was confirmed; the email parameter is not
bound to it.
- A request that got no HTTP answer (status 0) is shown as a connection
problem, not as an invalid link.
- ActivatedRoute, Store and AuthService come from inject().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(auth): apply a verification status only to the user it was asked for
If a different user signs in while the status or resend request is in flight,
the answer no longer updates the new user's verification state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui-core): drop a resend answer once another user has signed in
The verification notice now tracks the user it speaks for. A different user
signing in cancels the pending resend and resets its state, and a late answer
for the previous user changes nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui-core): a dismissed verification notice stays dismissed only for that user
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Product-scoped (hosted plan only) Stripe webhook linking, signup paywall, lazy tenant link and /billing routes; checkout-session proof at register/onboarding; BILLING_PRODUCT, BILLING_SIGNUP_PAYWALL and BILLING_WEBHOOK_LINKING (default off); 402 payment_method_required on a paid upgrade without a card. See the PR description for details.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(desktop): use a single ErrorHandler that forwards to Sentry and ErrorHandlerService
The desktop, agent and desktop-timer apps registered two ErrorHandler
providers. Angular only keeps the last one, so either Sentry never saw
uncaught errors (desktop) or the app toast/logging handler was dead
(agent, desktop-timer).
Replace both with one factory provider that delegates to the Sentry
handler and then to ErrorHandlerService. Sentry console logging is
disabled since ErrorHandlerService already logs the error.
Closes#9106
* refactor(desktop-ui-lib): extract provideGlobalErrorHandler to remove duplicated provider
Move the combined ErrorHandler factory into desktop-ui-lib so the three
desktop apps share one implementation. The reporting handler (Sentry) is
passed in, so desktop-ui-lib does not need a Sentry dependency.
* fix(desktop-ui-lib): always run ErrorHandlerService and guard nullish errors in global handler
Run ErrorHandlerService in a finally block so a throwing reporting handler
cannot skip it, and wrap nullish values in an Error before passing them to
ErrorHandlerService. The value sent to the reporting handler is unchanged.
* feat(i18n): add English strings for the accounting dashboard view
* feat(dashboard): redesign the accounting dashboard with KPIs, cash-flow chart and sortable breakdown
* fix(dashboard): keep employee chart legend items at the list font size
* feat(i18n): add Acholi strings for the accounting dashboard view
* feat(i18n): add Arabic and Hebrew strings for the accounting dashboard view
* feat(i18n): add Bulgarian strings for the accounting dashboard view
* feat(i18n): add German strings for the accounting dashboard view
* feat(i18n): add Spanish strings for the accounting dashboard view
* feat(i18n): add French strings for the accounting dashboard view
* feat(i18n): add Italian strings for the accounting dashboard view
* feat(i18n): add Dutch strings for the accounting dashboard view
* feat(i18n): add Polish strings for the accounting dashboard view
* feat(i18n): add Portuguese strings for the accounting dashboard view
* feat(i18n): add Russian strings for the accounting dashboard view
* feat(i18n): add Chinese strings for the accounting dashboard view
* fix(dashboard): address accounting dashboard review findings
- keep the signed income share for the label, clamp only the bar
- reject non-positive total income as a percentage denominator
- add a screen-reader table of the chart's daily values
- let long KPI amounts wrap instead of truncating
- format chart dates in the active UI language
- translate the accounting view strings in Hebrew and Acholi
* feat(dashboard): restore the cash-flow chart's day grid, point dots and full axis labels
- draw a vertical grid line per date, in the value grid's colour
- show a dot on every day for each series
- label the y-axis with full figures and the x-axis with full dates
* feat(dashboard): add a sort control to the earnings breakdown
- add a sort-by select and a direction toggle to the breakdown header
- sort by total expenses, highest first, by default
- start-align the money columns and trail their sort arrows
- narrow the employee column so the money columns get more room
- add the sort label and direction strings in every locale
* refactor(dashboard): inject the accounting view's theme service and currency pipe with inject()
* fix(dashboard): address accounting chart and header review findings
- keep a vertical grid line for every date; thin only the colliding labels
- format y-axis figures in the active UI language
- let the breakdown panel header and sort controls wrap on narrow screens
* fix(dashboard): read the x-axis tick font through CartesianScaleOptions
* fix(docs-ui): let the table row kebab open its actions menu
nbContextMenu's click trigger listens on document, so stopping the click on the button meant the menu never opened. The table's row-open handler already ignores clicks from buttons.
* docs(docs-ui): update the table double-click guard comment
* fix(docs-ui): let the card kebab open its actions menu
The card body now skips clicks, double clicks and Enter coming from its kebab instead of the kebab stopping propagation, which kept nbContextMenu from ever opening.
* fix(docs-ui): keep a tree node from activating on a kebab click
Override the tree's mouse click action so a click on the node menu button does not toggle the active node.
* fix(docs-ui): let the tree node kebab open its actions menu
Drop the stopPropagation that kept nbContextMenu (and the Shift+F10 path) from opening the node menu.
* feat(docs-ui): add column icons and column chooser strings
Per-column Eva icons and the DOCS.TABLE strings for the column count, the locked and auto-hidden hints, Show all and Reset to default.
* feat(docs-ui): add show-all, reset and visibility state to the column chooser
Visible-column count, whether any preference is stored, which columns the narrow-viewport defaults hid, and actions to show every column or drop the stored preferences.
* fix(docs-ui): open the column chooser and redesign it
nbTooltip and nbPopover on the same button shared one NbDynamicOverlay, so a click only ever showed the tooltip. The tooltip now sits on a wrapper.
The chooser becomes a list of switch rows with column icons, a locked Name row, an auto-hidden hint for narrow screens, a live count and Show all / Reset to default actions.
* feat(docs-ui): group the document actions menu and make it context-aware
Every item gets an icon and the menu is split into sections (open, create, organize, share, AI, destructive) with dividers only between non-empty ones. Delete is marked danger and the tree shows its F2 and Del shortcuts.
A FILE row offers Preview instead of a second, identical Open; Open on a folder or page says where it goes; Duplicate with children is dropped for a container the list reports as empty.
* feat(docs-ui): tag the table, card and tree action menus with a shared class
Pass DOCS_ACTION_MENU_CLASS through nbContextMenuClass so the three kebab menus can be styled as one.
* style(docs-ui): restyle the document actions menu
Compact rows with icons, rounded hover, section dividers, key-cap shortcut hints and a red Delete. Global on purpose: the menu renders in the CDK overlay, and the shell wraps every Documents route.
* feat(docs-ui): add the actions menu to folder cards
Folder cards had no kebab, so in card view a folder could only be drilled into. The kebab sits beside the folder card button in a wrapper, since a button may not contain another one.
* refactor(docs-ui): mark template-only members protected
Follows the Angular guideline for the column chooser members and the action menu class added in this branch.
* fix(docs-ui): keep tree key handling off the node menu button
The tree handles Enter and Space on body and calls preventDefault, which cancelled the menu button's own click. The button now stops their propagation without preventing the default. Also marks the tree's actionMenuClass protected.
The develop push of #10324 (9a6f8787) failed "Check Spelling and Typos
with cspell" (run 36399734369): 13 issues in 5 files. PRs into develop
run no cspell, so this surfaced only after the merge.
- "Nx's" (4x) and "callables": reworded in the comments.
- "internmap" (a d3 dependency, npm package name): added to .cspell.json.
- "avascript" / "msdt" in the safe-url spec: file-level cspell:ignore,
as the repo does elsewhere; they are the tail of the entity-encoded
"javascript:" payloads and the ms-msdt: scheme the tests feed in.
Comment, dictionary and directive changes only; no code or config
behaviour changes. Local cspell 6.31.3 on the five files: 0 issues
(the pre-fix safe-url spec, as a control: 6 issues).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A synchronous call cannot be interrupted (Jest's timeout included), so a
grossly slow isAllowedUrl is now reported after ONE call on the smaller
hostile input - the same single cold call the old 100/200 ms asserts
timed - instead of after the batches and the fourfold input have
multiplied the wait (CodeRabbit). Running each measurement in a child
process, as also suggested, is not done: a call that never returns
already ends in a failure (the job timeout), never a pass, and spawn/IPC
time would be billed to the measurements.
Known-dirty control (local): an injected O(n^2/200) scan in
isAllowedUrl makes the data: linearity test fail with a growth factor of
16.8 against the < 10 bound; the restored code passes (docs-ui 541/541).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs-ui safe-url linearity tests: time each input size in batches of
calls (doubled until a batch runs >= 50 ms, median of three) and take
the growth ratio as measured, with no 1 ms floor that could understate
growth for sub-millisecond samples (CodeRabbit). Keep a generous
absolute ceiling (3 s per rejection of the smaller input, ~9x the
slowest CI reading) so a uniformly slow validator cannot pass either.
- role-permission demo-mode suite: state its scope precisely. The reload
only adds rows, so it never removes a deletion grant a tenant already
holds; the seed and RolePermissionService keep demo tenants free of it,
and PermissionGuard has no demo-mode rule (a product question, out of
scope for this test-harness PR).
- Sonar: String.raw for the transformIgnorePatterns regex (pattern
verified byte-identical), startsWith in jest.resolver.js, and no
blanket eslint-disable in the new activepieces jest config (its
template, integration-make-com, has none; eslint clean).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Local runs after this commit: ui-core 45/45 suites, ui-auth 4/4, gauzy
29/29, desktop-ui-lib 41/41, gauzy-server 2/2, and every plugin UI project
that was red (integration-ai/github/hubstaff/upwork-ui, job-employee/
matching/proposal/search-ui, jobs-ui) green.
- jest.interop.js now patches every callable CommonJS package the code
imports both ways (moment, randomcolor) with `default` + `__esModule`, so
both import styles resolve to the function under either esModuleInterop.
apps/gauzy's spec tsconfig turns esModuleInterop on (as ui-core, docs-ui
and videos-ui do): ui-core's line chart default-imports
chartjs-plugin-annotation and Chart.register received undefined.
- jest.preset.js transforms d3-* / internmap (ESM-only, reached through
@swimlane/ngx-charts).
- desktop-ui-lib specs: section forms get the FormGroup their parent passes,
cell renderers their rowData, dialogs their data; Settings/Setup/
ScreenCapture get the library's GAUZY_ENV and the time tracker the
AuthStrategy/AuthService the desktop apps bootstrap; the task table a
signed-in session. Two test-side overrides are documented in place:
LanguageSelectorComponent binds [(ngModel)] without FormsModule (a dead
binding in the app too), and the plugin source forms use <nb-hint> as a
plain styled element (Nebular has no such component).
- apps/gauzy: page specs get the date-picker config their route sets,
the timesheet layout / job pages their route data, the task dialog a
selected organization (its unguarded async read crashed the Jest worker)
and a stand-in for docs-ui's links panel (docs-ui's entry point loads a
PDF viewer that uses import.meta, which CommonJS Jest cannot parse).
- integration-upwork-ui: two hand-written specs used jasmine spies (jest
has none) and partial Router/TranslateService mocks the template could
not render with; they use jest.fn() and the real services now.
- gauzy-server's AppComponent spec was the 2021 CLI scaffold (title
'desktop-web-ui', a "Welcome" h1) and declared a standalone component; it
now imports it and asserts what the shell does (router outlet, language
bridge started on init), with the library entry mocked to its one token.
- jobs-ui: the layout spec has asserted a main landmark since it was
written; the layout now renders <main role="main"> around its outlet (the
app shell defines no other main landmark).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once the Angular suites loaded, most failures were CLI-generated
`should create` stubs that had never run, failing on the first injection
(TranslateService, NbToastrService, NbDialogRef, ActivatedRoute, the
translate pipe, the default icon pack).
- jest.angular-defaults.ts (setupFilesAfterEnv, after each project's
test-setup): a top-level beforeEach adds what the app's root module
provides - TranslateModule.forRoot, the Nebular forRoot modules,
NgxPermissionsModule.forRoot, the Tabler/eva icon pack, HttpClient
testing, router, noop animations, a NbDialogRef stub, GAUZY_ENV - and a
matchMedia stand-in (jsdom has none). configureTestingModule merges, so a
spec's own providers still win. It imports no ui-core/core: that would
cache real modules a spec later jest.mock()s.
- jest.preset.js maps dayjs/esm (imported by ngx-daterangepicker-material's
.mjs bundle) to dayjs's CommonJS build.
- Specs of non-standalone components import the NgModule that declares
them, provide what the host feature module provides (EmployeesService,
AuthService, NbAuthModule.forRoot, PipesModule), and set the inputs /
route data / date-picker config the app always supplies before render.
- Two stubs imported classes their files do not export
(GauzyRangePickerComponent, ViewComponent) and declared `undefined`.
- desktop-ui-lib's test-setup installs an inert `window.electronAPI`
preload bridge of the shape apps/agent exposes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Iteration 1 of the hermetic run (36330457608, 83 pass / 14 fail) moved the
Angular suites past TestBed init and into two load-time failures:
- "(0, moment_1.default) is not a function" in ~50 suites across 11
projects. ui-core imports `moment` as a default import, most other code
as a namespace import; the bundlers accept both, the TypeScript CJS emit
Jest runs cannot under a single esModuleInterop setting. jest.interop.js
(a preset setupFiles entry, concatenated with any project's own) gives
the loaded module `default` and `__esModule`, so both styles resolve to
the moment function itself, as they do in the app build.
- "Cannot find module '@gauzy/ui-config'" in 23 ui-core suites: ui-core's
tsconfig.json maps it to the BUILT dist copy, which a test run does not
have, and Nx's Jest resolver falls back to the spec tsconfig's paths. The
spec tsconfig now carries the same source mappings as tsconfig.lib.json.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docker/build-push-action uploads a build record (*.dockerbuild) for every image
build. The record stores every build-arg value in plain text, and on a public
repo any signed-in GitHub user can download it. Set DOCKER_BUILD_RECORD_UPLOAD
and DOCKER_BUILD_SUMMARY to false at workflow level in every workflow that uses
the action. BuildKit secrets are never recorded, so nothing else changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(security): prove GitHub installation ownership before binding it (GHSA-4rwq)
POST /integration/github/install bound whatever installation_id the request
body carried, as long as the state nonce belonged to the caller's tenant. The
nonce proves which tenant started the flow, not which GitHub installation that
tenant may bind — and binding hands the tenant the App's token for every
repository in the installation. A tenant could bind another organization's
installation and read its private repositories.
The GitHub App now issues an OAuth code with the post-install redirect ("Request
user authorization (OAuth) during installation"). POST /install — the
authenticated request — exchanges it and binds only an installation the
authorizing GitHub user is entitled to in full:
- a personal installation: the user must be that account;
- an organization installation: the user must already reach every repository
it covers (their count equals the App's own count, read before and after to
close a create/delete race).
The code is signed with the nonce it arrived with, so a code leaked from a
post-install URL cannot be replayed against another tenant's nonce, and the
user token is revoked once checked. Without a code the install is refused with
a message naming the GitHub App setting to enable.
Also closes a sibling on the same surface: GithubMiddleware loaded an
integration's settings before authentication using ?tenantId= ahead of the
Tenant-Id header, while the tenant guard validates only the header when one is
sent. Own tenant in the header plus a victim's in the query let the repository,
metadata, issue and sync routes act on the victim's installation. The
middleware now records who owns the settings, and GithubIntegrationTenantGuard
refuses a mismatch after authentication.
The install popup now shows why a connection was refused instead of closing
after two seconds, and handles the update/request redirects GitHub sends.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(security): spend the install code at the callback, compare repository ids
Two P1 review findings on the first version of this fix, both correct:
- The post-install callback is public and signed ANY code with ANY live nonce,
so a leaked, unused victim code plus a nonce from the attacker's own tenant
still bound the victim's installation — the code binding proved nothing. The
callback now exchanges the code the moment it arrives (spending it, so it
cannot be replayed from a URL, history or log), checks entitlement there, and
hands the browser only a signed, 10-minute proof bound to this flow's nonce
and installation. The code never reaches the browser, and POST /install binds
nothing without a valid proof. When there is no proof, install_check tells
the web app why (no code / not entitled / unverifiable).
- Comparing repository COUNTS could be balanced by a member who creates
throwaway repositories and deletes them between the reads while the
repositories hidden from them remain. Entitlement for an organization
installation is now a set comparison: every repository id the App can reach
must be one the user can read (user ids read first, App ids after).
Also: installation ids beyond Number.MAX_SAFE_INTEGER are refused (Octokit
takes a number and would check a different installation than the one stored),
member install requests (setup_action=request) no longer hit a raw 400, and the
popup text is translatable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Unit Tests workflow has never been green (24 of 97 projects red at
develop eac7136562, run 36319732347). Causes addressed here:
- Env leak: Nx loads the committed .env.local (DEMO=true,
WORKER_QUEUE_ENABLED=false, NODE_ENV=development) into every task.
The test step now sets NX_LOAD_DOT_ENV_FILES=false, and the two specs
that depend on a mode pin it themselves (role-permission counts pin
environment.demo=false and gain a demo-mode suite asserting 209 per
role; the worker spec clears the queue env before the constants load).
.env.local is unchanged.
- Angular harness: nohoist gives each workspace its own @angular copy,
so setupZoneTestEnv() initialised a different TestBed from the one the
spec used. A workspace resolver (jest.resolver.js, wrapping Nx's) makes
every @angular/* import in a Jest project resolve to one copy. The
Angular projects now inherit the preset's transformIgnorePatterns,
which gains the .mjs exception plus @datorama, @ngneat and lodash-es.
- ui-config's environment.ts is generated and gitignored; the workflow
runs `yarn config:dev` before the tests, as the Playwright workflow does.
- Misconfigured targets: gauzy (jest.config.js -> .ts, Angular transform,
setupFile moved into the config), integration-sim-ui (.ts -> .cts),
integration-activepieces (config file added), mcp-auth (ran
`node build/main.js --test`; now the jest executor like apps/mcp).
- Real failures: the openai "silence" case used a body with no `text`,
which the shared helper rejects by contract; the toolbar spec read the
tabIndex property, which is 0 on any button; the docs-ui linearity
tests asserted wall-clock bounds, now a 4x-input growth ratio.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Render blueprints declare SENTRY_DSN with sync: false, so Render prompts
the person creating the Blueprint for their own DSN instead of shipping one.
The desktop, desktop-timer, server, server-api and agent apps logged the full
DSN at startup; they now only say that Sentry is enabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.env.compose (loaded by docker-compose.yml by default), .env.demo.compose,
.env.docker, render.yaml, .render/render.demo.yaml and both fly.toml files
carried the Ever Gauzy project's DSN, so every self-hosted install reported its
errors and log lines into Ever's Sentry project and spent its quota. They are
now empty, with a note to set your own DSN. Existing installs keep the value
they were created with; only rotating that key stops them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SENTRY_LOG_LEVELS accepted 'fatal', but SentryService had no fatal override, so
Nest's ConsoleLogger.fatal printed the message and Sentry never saw it (also
before this branch). A fatal log now becomes an event whenever fatal or error is
captured, and is a breadcrumb otherwise. The level list is a Set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Static Checks / lint (x64-4 lane): every cache hit since the job moved to
this lane ran out of space. The 2.47 GB archive plus the tree it unpacks to
does not fit the 16Gi RAM-backed workspace, so each hit fell back to a
1.5-4 h cold install (16 of 16 runs cold since #10303). The Restore step
now moves the archive onto the disk-backed package-cache volume (the
parent of YARN_CACHE_FOLDER) before extracting, so only the tree lives in
RAM. Where YARN_CACHE_FOLDER is unset, or the move fails, it extracts in
place exactly as before. Two report-only df lines (after the restore and
at the top of Summarize) record workspace headroom and can never fail a
step.
Triggers: apply the owner decision of 2026-09-21 (already on draft #10254,
same text) directly to develop. A pull request INTO develop no longer
starts static-checks, typos (cspell), snyk-analysis (trigger removed,
restore lines kept in a comment), or build, secrets-analysis and the
external-uptime-monitor self-test (branches-ignore: develop, so PRs into
stage/master still run them). Every push trigger is unchanged, so all of
them still run when code lands on develop. develop has no required status
checks, so no PR is blocked by the missing runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Those buttons used the muted secondary style, so they looked disabled next to Edit and Download. Use the primary action style while they stay clickable.
Fixes#8522
The whole ever-co Sentry organisation (5,000 errors a month on the current
plan) has accepted no error after the first hours of each monthly reset:
2026-07-09, 08-09 and 09-09 are the only days with accepted errors in 90 days.
About 97% of what was accepted were info-level log lines, not errors.
- SentryService (the API's Nest logger) turned every log/warn/debug/verbose
call into its own Sentry event and ignored the `logLevels: ['error']` the API
passes. It now honours `logLevels`: listed levels become events, the rest
become breadcrumbs on the next event. An unset or empty list keeps the old
capture-everything behaviour. The API reads the list from SENTRY_LOG_LEVELS
(default `error`), so capturing warnings or logs again is a config change.
- RequestContextMiddleware logged the start and end of every request,
including the Kubernetes readiness probe on /api/health every 10 s on every
pod: 2 events per probe, ~2,900 events an hour from the four Ever Teams API
pods alone. /api/health and /api/health/* are no longer logged. This is
decided by path only, since a client-set User-Agent must not be able to hide
other requests.
- apps/api/src/sentry.ts printed the DSN at startup and ran the SDK in debug
mode in production (`environment.production` is false in the published
image), writing several SDK lines per request. Debug is now opt-in with
SENTRY_DEBUG=true, and the DSN is no longer printed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up (Greptile). core's and integration-zapier's
tsconfig.spec.json comments, and the idempotency README, still said allowJs
was required for transformIgnorePatterns to take effect. With the pinned
ts-jest (>= 29.3.2) that is no longer true for files under node_modules,
which is all the list covers. Reword the three comments; the allowJs
settings themselves are unchanged (identical parsed JSON).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six service/controller specs in three plugins failed to load, so they ran
zero tests. Two defects were stacked, and the first hid the second.
1. Their testing modules load @gauzy/core, which imports uuid. The installed
uuid (14.0.0) is ESM-only. The allow-list of ESM packages Jest must
transform lived in packages/core/jest.config.ts, with a copy in
integration-zapier, and not in the shared preset. Move it into
jest.preset.js so inheriting projects get it, and let core inherit it
(jest --showConfig resolves to the byte-identical pattern). zapier keeps
its own copy, which replaces the preset's, and now points at the preset.
No allowJs is needed: ts-jest >= 29.3.2 compiles node_modules .js to
CommonJS regardless. All six specs pass with allowJs unset.
2. The specs are Nest CLI "should be defined" scaffolding whose testing
modules never resolved the dependency graph of the class under test.
Add stub providers for exactly what each class injects, and override
the @UseGuards guards in the controller specs. Nest builds those in the
module that owns the controller, but they are not constructor
dependencies. The spec changes are additive only: no it/expect changed
and nothing declared was removed.
Verified locally, before vs after on 1b2278d329:
- the three plugins go from 0 tests run to green (videos 3/3, 8 tests;
job-proposal 2/2; wakatime 2/2)
- the other 31 plugins with specs are unchanged, suite for suite
- the 11 non-plugin projects that inherit the preset and have specs are
unchanged
- core passes 179/179 suites, 2174 tests
- a mutation check (drop one repository stub) turns the spec red
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>