Skip to content

Stage#9609

Merged
evereq merged 6 commits intostagefrom
develop
Mar 14, 2026
Merged

Stage#9609
evereq merged 6 commits intostagefrom
develop

Conversation

@evereq
Copy link
Member

@evereq evereq commented Mar 14, 2026

PR

Please note: we will close your PR without comment if you do not check the boxes above and provide ALL requested information.



Summary by cubic

Fixes desktop timer offline sync issues by serializing timer API calls and enforcing ordered local updates, preventing race conditions and corrupted time logs. Also hardens local server startup and escapes JWT secrets in generated env files.

  • Bug Fixes

    • Serialize toggleApiStart, toggleApiStop, updateTimeLog, and addTimeLog via a mutex with a per-op timeout to prevent overlapping requests and cascade deletions during offline/online transitions.
    • Replace deferred asapScheduler update with awaited IPC update in sequence queue to preserve write order; pass timeLogId to TimeSlotQueue so timeslots link to the correct log.
    • Add guards to updateTimeLog/addTimeLog when organizationId/tenantId are missing; support optional fields and include organizationContactId.
    • Require JWT secrets for local server start and throw an error if missing.
    • Escape JWT secrets in generated desktop env content using escapeForSingleQuote to handle backslashes, quotes, and newlines.
  • Dependencies

    • Bump undici to 6.24.0 and locutus to 3.0.14; update caniuse-lite and node-abi in yarn.lock.

Written for commit 1c6889b. Summary will update on new commits.

rahul-rocket and others added 6 commits March 13, 2026 08:31
* Apply suggestion from @coderabbitai[bot]

* Update apps/desktop/src/index.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update .scripts/electron-desktop-environment/concrete-environment-content/desktop-environment-content.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Bumps [locutus](https://github.com/locutusjs/locutus) from 3.0.10 to 3.0.14.
- [Release notes](https://github.com/locutusjs/locutus/releases)
- [Changelog](https://github.com/locutusjs/locutus/blob/main/CHANGELOG.md)
- [Commits](locutusjs/locutus@v3.0.10...v3.0.14)

---
updated-dependencies:
- dependency-name: locutus
  dependency-version: 3.0.14
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [undici](https://github.com/nodejs/undici) from 6.23.0 to 6.24.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](nodejs/undici@v6.23.0...v6.24.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.24.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
* fix: desktop timer stopped offline

* fix: desktop timer offline handle

* fix: remove logs

* fix: change timelogid source
Bumps [undici](https://github.com/nodejs/undici) from 6.23.0 to 6.24.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](nodejs/undici@v6.23.0...v6.24.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.24.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…-3.0.14

chore(deps): bump locutus from 3.0.10 to 3.0.14
@evereq evereq merged commit 0ba681c into stage Mar 14, 2026
26 of 35 checks passed
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1e2f61ae-60ac-4800-a4f2-2b0ae720abce

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cla-assistant
Copy link

cla-assistant bot commented Mar 14, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
3 out of 4 committers have signed the CLA.

✅ rahul-rocket
✅ evereq
✅ syns2191
❌ dependabot[bot]
You have signed the CLA already but the status is still pending? Let us recheck it.

@cla-assistant
Copy link

cla-assistant bot commented Mar 14, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
3 out of 4 committers have signed the CLA.

✅ rahul-rocket
✅ syns2191
✅ evereq
❌ dependabot[bot]
You have signed the CLA already but the status is still pending? Let us recheck it.

@sonarqubecloud
Copy link

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/desktop-ui-lib/src/lib/time-tracker/time-tracker.service.ts">

<violation number="1" location="packages/desktop-ui-lib/src/lib/time-tracker/time-tracker.service.ts:585">
P2: The `try/catch` around `firstValueFrom(...)` is ineffective because `firstValueFrom` returns a Promise rather than throwing synchronously. The detailed error logging (with `requestBody`) in the `catch` block will never execute. Either make the callback `async` and `await` the result, or remove the redundant try/catch since `_serialized` already catches and logs errors.</violation>
</file>

<file name="apps/desktop/src/index.ts">

<violation number="1" location="apps/desktop/src/index.ts:351">
P1: This guard breaks local startup for configs that rely on existing environment/additional-config JWT secrets instead of `setupConfig.secret`. Fall back to the current `process.env` values before throwing.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +351 to +355
if (!setupConfig.secret || !setupConfig.secret.jwt || !setupConfig.secret.refresh_token) {
throw new AppError('MAINSTRSERVER', new Error('JWT secrets are required for local server startup'));
}
process.env.JWT_SECRET = setupConfig.secret.jwt;
process.env.JWT_REFRESH_TOKEN_SECRET = setupConfig.secret.refresh_token;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This guard breaks local startup for configs that rely on existing environment/additional-config JWT secrets instead of setupConfig.secret. Fall back to the current process.env values before throwing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/index.ts, line 351:

<comment>This guard breaks local startup for configs that rely on existing environment/additional-config JWT secrets instead of `setupConfig.secret`. Fall back to the current `process.env` values before throwing.</comment>

<file context>
@@ -347,8 +347,12 @@ async function startServer(setupConfig: DesktopSetupConfig, restart = false) {
-		process.env.JWT_SECRET = setupConfig.secret?.jwt;
-		process.env.JWT_REFRESH_TOKEN_SECRET = setupConfig.secret?.refresh_token;
+
+		if (!setupConfig.secret || !setupConfig.secret.jwt || !setupConfig.secret.refresh_token) {
+			throw new AppError('MAINSTRSERVER', new Error('JWT secrets are required for local server startup'));
+		}
</file context>
Suggested change
if (!setupConfig.secret || !setupConfig.secret.jwt || !setupConfig.secret.refresh_token) {
throw new AppError('MAINSTRSERVER', new Error('JWT secrets are required for local server startup'));
}
process.env.JWT_SECRET = setupConfig.secret.jwt;
process.env.JWT_REFRESH_TOKEN_SECRET = setupConfig.secret.refresh_token;
const jwtSecret = setupConfig.secret?.jwt || process.env.JWT_SECRET;
const jwtRefreshTokenSecret = setupConfig.secret?.refresh_token || process.env.JWT_REFRESH_TOKEN_SECRET;
if (!jwtSecret || !jwtRefreshTokenSecret) {
throw new AppError('MAINSTRSERVER', new Error('JWT secrets are required for local server startup'));
}
process.env.JWT_SECRET = jwtSecret;
process.env.JWT_REFRESH_TOKEN_SECRET = jwtRefreshTokenSecret;
Fix with Cubic

this._loggerService.error<any>(`Error stopping timer: ${moment().format()}`, { error, requestBody: body });
throw error;
}
return this._serialized('toggleApiStop', () => {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The try/catch around firstValueFrom(...) is ineffective because firstValueFrom returns a Promise rather than throwing synchronously. The detailed error logging (with requestBody) in the catch block will never execute. Either make the callback async and await the result, or remove the redundant try/catch since _serialized already catches and logs errors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/desktop-ui-lib/src/lib/time-tracker/time-tracker.service.ts, line 585:

<comment>The `try/catch` around `firstValueFrom(...)` is ineffective because `firstValueFrom` returns a Promise rather than throwing synchronously. The detailed error logging (with `requestBody`) in the `catch` block will never execute. Either make the callback `async` and `await` the result, or remove the redundant try/catch since `_serialized` already catches and logs errors.</comment>

<file context>
@@ -516,26 +582,38 @@ export class TimeTrackerService {
-			this._loggerService.error<any>(`Error stopping timer: ${moment().format()}`, { error, requestBody: body });
-			throw error;
-		}
+		return this._serialized('toggleApiStop', () => {
+			try {
+				return firstValueFrom(this.http.post<ITimeLog>(API_URL, body, options));
</file context>
Fix with Cubic

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 14, 2026

Greptile Summary

This PR merges multiple fixes targeting the desktop app's offline-sync reliability and environment code-generation security. The core changes are: (1) a promise-chain mutex in TimeTrackerService that serializes timer API mutations (start/stop/update/add) to prevent race conditions between offline-sync replay and online timer operations; (2) replacing asapScheduler fire-and-forget with direct await in SequenceQueue to prevent out-of-order local DB writes; (3) escaping JWT secrets in generated environment files to prevent code-gen breakage from special characters; and (4) adding null-guard checks for organizationId/tenantId before API calls that would silently create orphaned timelogs.

  • Timer mutex serialization: _serialized() chains all timer state mutations through a single promise queue with a 120s failsafe timeout, preventing concurrent API calls from triggering cascade deletions of timelogs on the server
  • Offline sync ordering fix: SequenceQueue.synchronize() now awaits the local DB update instead of deferring it via asapScheduler, fixing corrupted syncDuration and timeslotId values
  • TimeLogId propagation: TimeSlotQueue now receives and forwards an optional timeLogId so time slots are associated with the correct time log during offline sync
  • Environment escaping: New escapeForSingleQuote utility applied to JWT values in two of three environment content generators — desktop-environment-content.ts was missed and still has the unescaped pattern
  • Null-org guards: updateTimeLog and addTimeLog now throw early if organizationId or tenantId is missing, preventing invisible orphaned timelogs
  • JWT startup validation: Desktop startServer now throws AppError if JWT secrets are missing before starting the local API server
  • Dependency updates: undici 6.23.0→6.24.0, locutus 3.0.10→3.0.14
  • PR template: The "Have you explained what your changes do" checkbox is unchecked

Confidence Score: 3/5

  • Generally safe but has one missed file that should be fixed before merge to avoid inconsistent escaping behavior.
  • The mutex and offline-sync ordering fixes are well-designed with proper timeout failsafes and error isolation. However, the escaping fix was incompletely applied — desktop-environment-content.ts still has the vulnerable unescaped JWT interpolation pattern. The PR also lacks a description of what changes were made and why, making it harder to assess intent. The core logic changes to timer serialization are sound but represent significant behavioral changes to the offline sync flow that should be thoroughly tested.
  • Pay close attention to packages/desktop-ui-lib/src/lib/offline-sync/concretes/sequence-queue.ts (significant logic refactoring in offline sync) and .scripts/electron-desktop-environment/concrete-environment-content/desktop-environment-content.ts (missed escaping fix — not in the PR but needs to be).

Important Files Changed

Filename Overview
.scripts/electron-desktop-environment/utils/escape-string.ts New utility function that escapes backslashes, single quotes, and newlines for safe interpolation into single-quoted JS string literals. Correct and well-documented.
.scripts/electron-desktop-environment/concrete-environment-content/desktop-api-server-environment-content.ts Applies escapeForSingleQuote to JWT secret interpolations, preventing code-gen breakage from special characters in secrets.
.scripts/electron-desktop-environment/concrete-environment-content/desktop-server-environment-content.ts Same escaping fix as desktop-api-server variant. Note: the sibling desktop-environment-content.ts was not similarly updated (flagged in review).
apps/desktop/src/index.ts Adds a guard that throws AppError if JWT secrets are missing before local server startup, preventing the server from starting with undefined env vars.
packages/desktop-ui-lib/src/lib/time-tracker/time-tracker.service.ts Major change: adds a promise-chain mutex (_serialized) to serialize timer API calls, preventing race conditions between offline-sync and online sessions. Also adds null-org guards to updateTimeLog/addTimeLog, and includes organizationContactId in payloads. Contains a dead try/catch in toggleApiStop (pre-existing issue, flagged as style).
packages/desktop-ui-lib/src/lib/offline-sync/concretes/sequence-queue.ts Significant refactoring: replaces asapScheduler fire-and-forget with direct await to prevent out-of-order DB writes. Adds new branch for handling latest.id existing but not running. Passes timeLogId to TimeSlotQueue constructor. Removes unused asapScheduler import.
packages/desktop-ui-lib/src/lib/offline-sync/concretes/time-slot-queue.ts Adds optional _timeLogId constructor parameter and conditionally spreads it into the time-slot push payload, ensuring time slots are associated with the correct time log during offline sync.
yarn.lock Dependency lock file updates for undici (6.23.0 → 6.24.0) and locutus (3.0.10 → 3.0.14). Standard maintenance updates.

Sequence Diagram

sequenceDiagram
    participant SQ as SequenceQueue
    participant TTS as TimeTrackerService
    participant Mutex as _timerMutex
    participant API as Server API
    participant IPC as Electron IPC

    Note over SQ: Offline sync begins
    SQ->>TTS: toggleApiStart(timer)
    TTS->>Mutex: _serialized('toggleApiStart', fn)
    Mutex->>API: POST /timer/start
    API-->>Mutex: TimeLog response
    Mutex-->>TTS: result
    TTS-->>SQ: latest

    SQ->>SQ: new TimeSlotQueue(timeLogId)
    SQ->>SQ: Process time slot intervals

    SQ->>TTS: toggleApiStop(timer) or updateTimeLog()
    TTS->>Mutex: _serialized('toggleApiStop', fn)
    Note over Mutex: Waits for previous op to complete
    Mutex->>API: POST /timer/stop
    API-->>Mutex: TimeLog response
    Mutex-->>TTS: result
    TTS-->>SQ: latest

    SQ->>IPC: await UPDATE_SYNCED_TIMER
    Note over SQ: Direct await (no asapScheduler)
    IPC-->>SQ: DB updated
Loading

Comments Outside Diff (1)

  1. .scripts/electron-desktop-environment/concrete-environment-content/desktop-environment-content.ts, line 33-34 (link)

    Missing escapeForSingleQuote for JWT values

    This file embeds JWT values into single-quoted string literals on lines 33-34, identical to the pattern in the sibling files (desktop-api-server-environment-content.ts and desktop-server-environment-content.ts) that were updated in this PR. However, this file was not updated to use the new escapeForSingleQuote() utility. If a secret value contains a single quote or backslash, the generated source code will be syntactically broken — the same class of bug this PR is fixing in the other two generator files.

    Please apply escapeForSingleQuote(...) to both JWT interpolations here (lines 33–34), and add the corresponding import at the top of the file.

Last reviewed commit: 1c6889b

Comment on lines +585 to +592
return this._serialized('toggleApiStop', () => {
try {
return firstValueFrom(this.http.post<ITimeLog>(API_URL, body, options));
} catch (error) {
this._loggerService.error<any>(`Error stopping timer: ${moment().format()}`, { error, requestBody: body });
throw error;
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead try/catch — never catches async rejections

The try/catch block wraps firstValueFrom(this.http.post(...)), which returns a Promise. Synchronous try/catch cannot catch rejected Promises — the catch block will never execute. The error logging on line 589 is unreachable code.

This is a pre-existing issue (the old code had the same pattern), but now that the call is wrapped in _serialized — which does properly await the result — this inner try/catch is pure dead code. Consider either removing it or converting the lambda to async and adding await:

Suggested change
return this._serialized('toggleApiStop', () => {
try {
return firstValueFrom(this.http.post<ITimeLog>(API_URL, body, options));
} catch (error) {
this._loggerService.error<any>(`Error stopping timer: ${moment().format()}`, { error, requestBody: body });
throw error;
}
});
return this._serialized('toggleApiStop', async () => {
try {
return await firstValueFrom(this.http.post<ITimeLog>(API_URL, body, options));
} catch (error) {
this._loggerService.error<any>(`Error stopping timer: ${moment().format()}`, { error, requestBody: body });
throw error;
}
});

@nx-cloud
Copy link

nx-cloud bot commented Mar 14, 2026

View your CI Pipeline Execution ↗ for commit 1c6889b

Command Status Duration Result
nx build api -c=production --prod ✅ Succeeded 5s View ↗
nx run-many -t build -c development -p constant... ✅ Succeeded 9s View ↗
nx build desktop --configuration=production --b... ✅ Succeeded 1s View ↗
nx build desktop-api --configuration=production... ✅ Succeeded <1s View ↗
nx run api:desktop-api ✅ Succeeded 11s View ↗
nx run gauzy:desktop-ui --base-href ./ ✅ Succeeded 3m 52s View ↗
nx run-many -t build -c production -p constants... ✅ Succeeded 8s View ↗
nx build gauzy -c=production --prod --verbose ✅ Succeeded 58s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-14 20:02:16 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants