From 1fc9153b9330ea485cd731f8fbc0fcac9d9354b1 Mon Sep 17 00:00:00 2001 From: kjxcodez Date: Wed, 5 Aug 2026 15:21:51 +0530 Subject: [PATCH 1/8] fix(api): handle pre-encrypted passwords in outreach accounts sync payload --- apps/api/src/services/outreach/outreach.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/api/src/services/outreach/outreach.service.ts b/apps/api/src/services/outreach/outreach.service.ts index eb80098..d1b2fb8 100644 --- a/apps/api/src/services/outreach/outreach.service.ts +++ b/apps/api/src/services/outreach/outreach.service.ts @@ -16,7 +16,8 @@ export class OutreachService { * Encrypts SMTP App Password and saves the email account to the workspace database. */ public async createEmailAccount(data: any): Promise { - const encrypted = encrypt(data.password); + const rawPassword = data.password || data.smtpPassword || data.imapPassword || ''; + const encrypted = encrypt(rawPassword); if (data.isDefault) { await EmailAccountModel.updateMany({ workspaceId: this.workspaceId } as any, { From 3a9f7e19a0847f5ffa2a307648aafb2bbbdb578f Mon Sep 17 00:00:00 2001 From: kjxcodez Date: Wed, 5 Aug 2026 15:24:02 +0530 Subject: [PATCH 2/8] fix(desktop): use standard UPDATE instead of REPLACE on existing SQLite records to preserve local-only credential fields --- .../main/database/repositories/local-crm.ts | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/main/database/repositories/local-crm.ts b/apps/desktop/src/main/database/repositories/local-crm.ts index 1b51d6d..b70c252 100644 --- a/apps/desktop/src/main/database/repositories/local-crm.ts +++ b/apps/desktop/src/main/database/repositories/local-crm.ts @@ -131,7 +131,21 @@ export const LocalCRMRepository = { const operation = existing ? 'UPDATE' : 'CREATE'; // 2. Perform write on target table - db.prepare(query).run(...params); + if (existing) { + const updateColumns = columns.filter((c) => c !== 'id'); + const setClause = updateColumns.map((c) => `${c} = ?`).join(', '); + const updateQuery = `UPDATE ${tableName} SET ${setClause} WHERE id = ?`; + const updateParams = updateColumns.map((col) => { + const val = record[col]; + if (val instanceof Date) return val.toISOString(); + if (typeof val === 'object' && val !== null) return JSON.stringify(val); + return val; + }); + updateParams.push(record.id); + db.prepare(updateQuery).run(...updateParams); + } else { + db.prepare(query).run(...params); + } // 3. Queue offline mutation task if this is a syncable crm table const syncableTables = [ @@ -218,7 +232,12 @@ export const LocalCRMRepository = { const placeholders = columns.map(() => '?').join(', '); const query = `INSERT OR REPLACE INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`; + const updateColumns = columns.filter((c) => c !== 'id'); + const setClause = updateColumns.map((c) => `${c} = ?`).join(', '); + const updateQuery = `UPDATE ${tableName} SET ${setClause} WHERE id = ?`; + const statement = db.prepare(query); + const updateStatement = db.prepare(updateQuery); const checkStmt = db.prepare(`SELECT * FROM ${tableName} WHERE id = ?`); const insertSyncQueue = db.prepare(` INSERT INTO sync_queue (id, workspaceId, entityType, entityId, operation, payload, version, retryCount, lastError, createdAt, updatedAt) @@ -237,17 +256,27 @@ export const LocalCRMRepository = { const transaction = db.transaction((list: any[]) => { for (const item of list) { - const params = columns.map((col) => { - const val = item[col]; - if (val instanceof Date) return val.toISOString(); - if (typeof val === 'object' && val !== null) return JSON.stringify(val); - return val; - }); - const existing = checkStmt.get(item.id) as any; const operation = existing ? 'UPDATE' : 'CREATE'; - statement.run(...params); + if (existing) { + const updateParams = updateColumns.map((col) => { + const val = item[col]; + if (val instanceof Date) return val.toISOString(); + if (typeof val === 'object' && val !== null) return JSON.stringify(val); + return val; + }); + updateParams.push(item.id); + updateStatement.run(...updateParams); + } else { + const params = columns.map((col) => { + const val = item[col]; + if (val instanceof Date) return val.toISOString(); + if (typeof val === 'object' && val !== null) return JSON.stringify(val); + return val; + }); + statement.run(...params); + } if (!skipQueue && syncableTables.includes(tableName)) { insertSyncQueue.run( From c1358b648067abdd0aeb5c4e69990894c521adfd Mon Sep 17 00:00:00 2001 From: kjxcodez Date: Wed, 5 Aug 2026 15:36:05 +0530 Subject: [PATCH 3/8] fix(api): support and preserve client-provided UUID string identifiers across all workspace scoped models --- apps/api/src/db/plugins/workspace.ts | 6 +++++- apps/api/src/repositories/base/base.repository.ts | 3 +++ apps/api/src/services/outreach/outreach.service.ts | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/api/src/db/plugins/workspace.ts b/apps/api/src/db/plugins/workspace.ts index 4ea61c3..b138a53 100644 --- a/apps/api/src/db/plugins/workspace.ts +++ b/apps/api/src/db/plugins/workspace.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'mongoose'; +import mongoose, { type Schema } from 'mongoose'; export interface WorkspaceScopedDocument { workspaceId: string; @@ -6,6 +6,10 @@ export interface WorkspaceScopedDocument { export function workspacePlugin(schema: Schema) { schema.add({ + _id: { + type: String, + default: () => new mongoose.Types.ObjectId().toString() + }, workspaceId: { type: String, required: true, diff --git a/apps/api/src/repositories/base/base.repository.ts b/apps/api/src/repositories/base/base.repository.ts index d25c542..9dde633 100644 --- a/apps/api/src/repositories/base/base.repository.ts +++ b/apps/api/src/repositories/base/base.repository.ts @@ -86,6 +86,9 @@ export class BaseRepository { public async create(data: Partial | any, session?: ClientSession): Promise { try { const payload = this.workspaceId ? { ...data, workspaceId: this.workspaceId } : data; + if (payload.id && !payload._id) { + payload._id = payload.id; + } const doc = new this.model(payload); const saveOptions = session ? { session } : {}; diff --git a/apps/api/src/services/outreach/outreach.service.ts b/apps/api/src/services/outreach/outreach.service.ts index d1b2fb8..3d41acb 100644 --- a/apps/api/src/services/outreach/outreach.service.ts +++ b/apps/api/src/services/outreach/outreach.service.ts @@ -26,6 +26,7 @@ export class OutreachService { } const account = new EmailAccountModel({ + _id: data.id || data._id || undefined, workspaceId: this.workspaceId as any, name: data.name, email: data.email, @@ -94,6 +95,7 @@ export class OutreachService { const variables = Array.from(new Set([...bodyVars, ...subjectVars])); const template = new EmailTemplateModel({ + _id: data.id || data._id || undefined, workspaceId: new mongoose.Types.ObjectId(this.workspaceId), name: data.name, subject: data.subject, From 9fec5cfc9ddcd67314cfb15cc65c3ab74f7cad1b Mon Sep 17 00:00:00 2001 From: kjxcodez Date: Wed, 5 Aug 2026 15:41:36 +0530 Subject: [PATCH 4/8] fix(api): fallback querying support for old ObjectId formats in delete and update actions --- .../src/repositories/base/base.repository.ts | 21 +++++++++++++----- .../src/services/outreach/outreach.service.ts | 22 ++++++++++++------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/apps/api/src/repositories/base/base.repository.ts b/apps/api/src/repositories/base/base.repository.ts index 9dde633..11746f0 100644 --- a/apps/api/src/repositories/base/base.repository.ts +++ b/apps/api/src/repositories/base/base.repository.ts @@ -1,4 +1,4 @@ -import { type Model, type Document, type ClientSession } from 'mongoose'; +import mongoose, { type Model, type Document, type ClientSession } from 'mongoose'; import { NotFoundError, ConflictError, @@ -14,6 +14,16 @@ export class BaseRepository { protected workspaceId?: string ) {} + /** + * Generates a query filter that works for both String and ObjectId identifiers. + */ + protected normalizeIdFilter(id: string): any { + if (mongoose.Types.ObjectId.isValid(id)) { + return { $or: [{ _id: id }, { _id: new mongoose.Types.ObjectId(id) }] }; + } + return { _id: id }; + } + /** * Translates mongoose exceptions to domain errors. */ @@ -45,7 +55,7 @@ export class BaseRepository { public async findById(id: string, session?: ClientSession): Promise { try { - const filter = this.applyScope({ _id: id } as any); + const filter = this.applyScope(this.normalizeIdFilter(id)); const doc = await this.model.findOne(filter).session(session || null); if (!doc) { throw new NotFoundError(`Resource with id ${id} not found.`); @@ -105,7 +115,7 @@ export class BaseRepository { session?: ClientSession ): Promise { try { - const filter = this.applyScope({ _id: id } as any); + const filter = this.applyScope(this.normalizeIdFilter(id)); const options: any = { new: true, runValidators: true }; if (session) { options.session = session; @@ -129,7 +139,7 @@ export class BaseRepository { public async delete(id: string, session?: ClientSession): Promise { try { - const filter = this.applyScope({ _id: id } as any); + const filter = this.applyScope(this.normalizeIdFilter(id)); const doc = await this.model.findOne(filter).session(session || null); if (!doc) { throw new NotFoundError(`Resource with id ${id} not found.`); @@ -140,7 +150,8 @@ export class BaseRepository { return true; } - const result = await this.model.deleteOne({ _id: id } as any).session(session || null); + const deleteFilter = this.normalizeIdFilter(id); + const result = await this.model.deleteOne(deleteFilter).session(session || null); return result.deletedCount > 0; } catch (error) { if (error instanceof NotFoundError) throw error; diff --git a/apps/api/src/services/outreach/outreach.service.ts b/apps/api/src/services/outreach/outreach.service.ts index 3d41acb..4276879 100644 --- a/apps/api/src/services/outreach/outreach.service.ts +++ b/apps/api/src/services/outreach/outreach.service.ts @@ -58,20 +58,26 @@ export class OutreachService { } public async deleteEmailAccount(id: string): Promise { - await EmailAccountModel.findOneAndDelete({ - _id: id, - workspaceId: this.workspaceId - } as any); + const filter: any = { workspaceId: this.workspaceId }; + if (mongoose.Types.ObjectId.isValid(id)) { + filter.$or = [{ _id: id }, { _id: new mongoose.Types.ObjectId(id) }]; + } else { + filter._id = id; + } + await EmailAccountModel.findOneAndDelete(filter); } /** * Simulates SMTP credential validation. */ public async testConnection(id: string): Promise { - const acc = await EmailAccountModel.findOne({ - _id: id, - workspaceId: this.workspaceId - } as any); + const filter: any = { workspaceId: this.workspaceId }; + if (mongoose.Types.ObjectId.isValid(id)) { + filter.$or = [{ _id: id }, { _id: new mongoose.Types.ObjectId(id) }]; + } else { + filter._id = id; + } + const acc = await EmailAccountModel.findOne(filter); if (!acc) throw new Error('Email Account not found.'); From f41fa116fbe2065502215ac3c26d4526dec934ff Mon Sep 17 00:00:00 2001 From: kjxcodez Date: Wed, 5 Aug 2026 15:49:33 +0530 Subject: [PATCH 5/8] fix(api): bypass schema casting on updates and deletions in BaseRepository to correctly match legacy ObjectId documents --- .../src/repositories/base/base.repository.ts | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/apps/api/src/repositories/base/base.repository.ts b/apps/api/src/repositories/base/base.repository.ts index 11746f0..6922b1b 100644 --- a/apps/api/src/repositories/base/base.repository.ts +++ b/apps/api/src/repositories/base/base.repository.ts @@ -56,11 +56,11 @@ export class BaseRepository { public async findById(id: string, session?: ClientSession): Promise { try { const filter = this.applyScope(this.normalizeIdFilter(id)); - const doc = await this.model.findOne(filter).session(session || null); - if (!doc) { + const rawDoc = await this.model.collection.findOne(filter); + if (!rawDoc) { throw new NotFoundError(`Resource with id ${id} not found.`); } - return doc; + return this.model.hydrate(rawDoc) as T; } catch (error) { if (error instanceof NotFoundError) throw error; this.handleError(error); @@ -116,21 +116,17 @@ export class BaseRepository { ): Promise { try { const filter = this.applyScope(this.normalizeIdFilter(id)); - const options: any = { new: true, runValidators: true }; - if (session) { - options.session = session; - } - - const doc = (await this.model.findOneAndUpdate( - filter, - { $set: updateData }, - options - )) as unknown as T | null; - - if (!doc) { + const rawDoc = await this.model.collection.findOne(filter); + if (!rawDoc) { throw new NotFoundError(`Resource with id ${id} not found.`); } - return doc; + + const doc = this.model.hydrate(rawDoc); + doc.set(updateData); + + const saveOptions = session ? { session } : {}; + await doc.save(saveOptions); + return doc as T; } catch (error) { if (error instanceof NotFoundError) throw error; this.handleError(error); @@ -140,19 +136,20 @@ export class BaseRepository { public async delete(id: string, session?: ClientSession): Promise { try { const filter = this.applyScope(this.normalizeIdFilter(id)); - const doc = await this.model.findOne(filter).session(session || null); - if (!doc) { + const rawDoc = await this.model.collection.findOne(filter); + if (!rawDoc) { throw new NotFoundError(`Resource with id ${id} not found.`); } + const doc = this.model.hydrate(rawDoc); if (typeof (doc as any).softDelete === 'function') { await (doc as any).softDelete(); return true; } const deleteFilter = this.normalizeIdFilter(id); - const result = await this.model.deleteOne(deleteFilter).session(session || null); - return result.deletedCount > 0; + const result = await this.model.collection.deleteOne(deleteFilter); + return result.deletedCount! > 0; } catch (error) { if (error instanceof NotFoundError) throw error; this.handleError(error); From b41bc918dd1e01944b89610aa7def8b5e6edbb28 Mon Sep 17 00:00:00 2001 From: kjxcodez Date: Wed, 5 Aug 2026 16:23:20 +0530 Subject: [PATCH 6/8] feat(api,schema,desktop): unify primary keys on client-authoritative String/UUID format, revert casting bypasses, and add pull-sync conflict protection --- .../api/src/db/models/beta-applicant.model.ts | 7 ++- apps/api/src/db/models/user.model.ts | 7 ++- apps/api/src/db/models/workspace.model.ts | 7 ++- .../src/repositories/base/base.repository.ts | 58 +++++++++---------- .../src/services/outreach/outreach.service.ts | 22 +++---- .../main/database/repositories/local-crm.ts | 4 ++ packages/schema/src/fields/common.ts | 9 +-- 7 files changed, 58 insertions(+), 56 deletions(-) diff --git a/apps/api/src/db/models/beta-applicant.model.ts b/apps/api/src/db/models/beta-applicant.model.ts index 43885d2..19f6385 100644 --- a/apps/api/src/db/models/beta-applicant.model.ts +++ b/apps/api/src/db/models/beta-applicant.model.ts @@ -1,6 +1,7 @@ import mongoose, { Schema, Document } from 'mongoose'; -export interface BetaApplicantDocument extends Document { +export interface BetaApplicantDocument extends Document { + _id: string; email: string; platform: 'win' | 'mac-arm' | 'mac-intel' | 'linux'; motivation: string; @@ -9,6 +10,10 @@ export interface BetaApplicantDocument extends Document { const BetaApplicantSchema = new Schema( { + _id: { + type: String, + default: () => new mongoose.Types.ObjectId().toString() + }, email: { type: String, required: true, diff --git a/apps/api/src/db/models/user.model.ts b/apps/api/src/db/models/user.model.ts index 171a1bb..180d8b5 100644 --- a/apps/api/src/db/models/user.model.ts +++ b/apps/api/src/db/models/user.model.ts @@ -9,7 +9,8 @@ import { } from '../plugins/index.js'; export interface UserDocument - extends mongoose.Document, SoftDeleteDocument, AuditDocument, TimestampDocument { + extends mongoose.Document, SoftDeleteDocument, AuditDocument, TimestampDocument { + _id: string; email: string; passwordHash?: string | null; name: string; @@ -25,6 +26,10 @@ export interface UserDocument const userSchema = new Schema( { + _id: { + type: String, + default: () => new mongoose.Types.ObjectId().toString() + }, email: { type: String, required: true, diff --git a/apps/api/src/db/models/workspace.model.ts b/apps/api/src/db/models/workspace.model.ts index 997d33b..153e5d3 100644 --- a/apps/api/src/db/models/workspace.model.ts +++ b/apps/api/src/db/models/workspace.model.ts @@ -21,7 +21,8 @@ export interface WorkspaceMember { } export interface WorkspaceDocument - extends mongoose.Document, SoftDeleteDocument, AuditDocument, TimestampDocument { + extends mongoose.Document, SoftDeleteDocument, AuditDocument, TimestampDocument { + _id: string; name: string; slug: string; ownerId: string; @@ -39,6 +40,10 @@ export interface WorkspaceDocument const workspaceSchema = new Schema( { + _id: { + type: String, + default: () => new mongoose.Types.ObjectId().toString() + }, name: { type: String, required: true, diff --git a/apps/api/src/repositories/base/base.repository.ts b/apps/api/src/repositories/base/base.repository.ts index 6922b1b..0070a72 100644 --- a/apps/api/src/repositories/base/base.repository.ts +++ b/apps/api/src/repositories/base/base.repository.ts @@ -1,4 +1,4 @@ -import mongoose, { type Model, type Document, type ClientSession } from 'mongoose'; +import { type Model, type Document, type ClientSession } from 'mongoose'; import { NotFoundError, ConflictError, @@ -8,21 +8,13 @@ import { type FilterQuery = any; -export class BaseRepository { +export class BaseRepository> { constructor( protected model: Model, protected workspaceId?: string ) {} - /** - * Generates a query filter that works for both String and ObjectId identifiers. - */ - protected normalizeIdFilter(id: string): any { - if (mongoose.Types.ObjectId.isValid(id)) { - return { $or: [{ _id: id }, { _id: new mongoose.Types.ObjectId(id) }] }; - } - return { _id: id }; - } + /** * Translates mongoose exceptions to domain errors. @@ -55,12 +47,12 @@ export class BaseRepository { public async findById(id: string, session?: ClientSession): Promise { try { - const filter = this.applyScope(this.normalizeIdFilter(id)); - const rawDoc = await this.model.collection.findOne(filter); - if (!rawDoc) { + const filter = this.applyScope({ _id: id } as any); + const doc = await this.model.findOne(filter).session(session || null); + if (!doc) { throw new NotFoundError(`Resource with id ${id} not found.`); } - return this.model.hydrate(rawDoc) as T; + return doc; } catch (error) { if (error instanceof NotFoundError) throw error; this.handleError(error); @@ -115,18 +107,22 @@ export class BaseRepository { session?: ClientSession ): Promise { try { - const filter = this.applyScope(this.normalizeIdFilter(id)); - const rawDoc = await this.model.collection.findOne(filter); - if (!rawDoc) { + const filter = this.applyScope({ _id: id } as any); + const options: any = { new: true, runValidators: true }; + if (session) { + options.session = session; + } + + const doc = (await this.model.findOneAndUpdate( + filter, + { $set: updateData }, + options + )) as unknown as T | null; + + if (!doc) { throw new NotFoundError(`Resource with id ${id} not found.`); } - - const doc = this.model.hydrate(rawDoc); - doc.set(updateData); - - const saveOptions = session ? { session } : {}; - await doc.save(saveOptions); - return doc as T; + return doc; } catch (error) { if (error instanceof NotFoundError) throw error; this.handleError(error); @@ -135,21 +131,19 @@ export class BaseRepository { public async delete(id: string, session?: ClientSession): Promise { try { - const filter = this.applyScope(this.normalizeIdFilter(id)); - const rawDoc = await this.model.collection.findOne(filter); - if (!rawDoc) { + const filter = this.applyScope({ _id: id } as any); + const doc = await this.model.findOne(filter).session(session || null); + if (!doc) { throw new NotFoundError(`Resource with id ${id} not found.`); } - const doc = this.model.hydrate(rawDoc); if (typeof (doc as any).softDelete === 'function') { await (doc as any).softDelete(); return true; } - const deleteFilter = this.normalizeIdFilter(id); - const result = await this.model.collection.deleteOne(deleteFilter); - return result.deletedCount! > 0; + const result = await this.model.deleteOne({ _id: id } as any).session(session || null); + return result.deletedCount > 0; } catch (error) { if (error instanceof NotFoundError) throw error; this.handleError(error); diff --git a/apps/api/src/services/outreach/outreach.service.ts b/apps/api/src/services/outreach/outreach.service.ts index 4276879..3d41acb 100644 --- a/apps/api/src/services/outreach/outreach.service.ts +++ b/apps/api/src/services/outreach/outreach.service.ts @@ -58,26 +58,20 @@ export class OutreachService { } public async deleteEmailAccount(id: string): Promise { - const filter: any = { workspaceId: this.workspaceId }; - if (mongoose.Types.ObjectId.isValid(id)) { - filter.$or = [{ _id: id }, { _id: new mongoose.Types.ObjectId(id) }]; - } else { - filter._id = id; - } - await EmailAccountModel.findOneAndDelete(filter); + await EmailAccountModel.findOneAndDelete({ + _id: id, + workspaceId: this.workspaceId + } as any); } /** * Simulates SMTP credential validation. */ public async testConnection(id: string): Promise { - const filter: any = { workspaceId: this.workspaceId }; - if (mongoose.Types.ObjectId.isValid(id)) { - filter.$or = [{ _id: id }, { _id: new mongoose.Types.ObjectId(id) }]; - } else { - filter._id = id; - } - const acc = await EmailAccountModel.findOne(filter); + const acc = await EmailAccountModel.findOne({ + _id: id, + workspaceId: this.workspaceId + } as any); if (!acc) throw new Error('Email Account not found.'); diff --git a/apps/desktop/src/main/database/repositories/local-crm.ts b/apps/desktop/src/main/database/repositories/local-crm.ts index b70c252..e2e6d56 100644 --- a/apps/desktop/src/main/database/repositories/local-crm.ts +++ b/apps/desktop/src/main/database/repositories/local-crm.ts @@ -260,6 +260,10 @@ export const LocalCRMRepository = { const operation = existing ? 'UPDATE' : 'CREATE'; if (existing) { + // Prevent remote sync from overwriting local changes still queued to upload + if (existing.syncStatus === 'pending') { + continue; + } const updateParams = updateColumns.map((col) => { const val = item[col]; if (val instanceof Date) return val.toISOString(); diff --git a/packages/schema/src/fields/common.ts b/packages/schema/src/fields/common.ts index 6a65cf0..fef50d6 100644 --- a/packages/schema/src/fields/common.ts +++ b/packages/schema/src/fields/common.ts @@ -8,17 +8,12 @@ const emptyToNull = (val: unknown) => { return val; }; -export const objectIdField = z.string().regex(/^[0-9a-fA-F]{24}$/, { - message: 'Invalid ObjectId format' -}); +export const objectIdField = z.string().min(1, 'ID is required'); // Nullable variant that accepts empty strings (coerces them to null) export const objectIdFieldNullable = z.preprocess( emptyToNull, - z - .string() - .regex(/^[0-9a-fA-F]{24}$/, { message: 'Invalid ObjectId format' }) - .nullable() + z.string().nullable() ); export const emailField = z.string().email({ message: 'Invalid email address' }); From 12bcb8f5b9af0a2605753b694ac5c481014e81c3 Mon Sep 17 00:00:00 2001 From: kjxcodez Date: Wed, 5 Aug 2026 16:35:11 +0530 Subject: [PATCH 7/8] feat: implement AuthLayout with interactive animated background component --- apps/desktop/src/renderer/components/auth/animated-bg.tsx | 4 ++-- apps/desktop/src/renderer/layouts/AuthLayout.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/renderer/components/auth/animated-bg.tsx b/apps/desktop/src/renderer/components/auth/animated-bg.tsx index c11eda8..baca74c 100644 --- a/apps/desktop/src/renderer/components/auth/animated-bg.tsx +++ b/apps/desktop/src/renderer/components/auth/animated-bg.tsx @@ -171,10 +171,10 @@ export function AnimatedBackground() { className="pointer-events-none absolute inset-0 overflow-hidden bg-[#000000]" > {/* 1. Noise texture — filmic grain for surface depth */} -
+
{/* 2. Scanlines — subtle CRT depth effect */} -
+
{/* 3. Interactive constellation canvas */} {/* Form column */} -
+
{/* Subtle background pulsing glows behind the glassmorphic card */} -
-
+
+
-
+
{/* Top-left decorative dots */} Date: Wed, 5 Aug 2026 16:47:50 +0530 Subject: [PATCH 8/8] feat(desktop): map campaign status casing between local SQLite (capitalized) and remote API (uppercase) during sync --- apps/desktop/src/main/services/sync-engine.ts | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/main/services/sync-engine.ts b/apps/desktop/src/main/services/sync-engine.ts index 05d1c3d..5d23381 100644 --- a/apps/desktop/src/main/services/sync-engine.ts +++ b/apps/desktop/src/main/services/sync-engine.ts @@ -128,11 +128,18 @@ export class SyncEngine { try { const campaigns = await this.sdk.campaigns.list(); if (campaigns && campaigns.length) { - const records = campaigns.map((c: any) => ({ - ...c, - workspaceId: this.workspaceId, - syncStatus: 'synced' - })); + const records = campaigns.map((c: any) => { + let status = c.status; + if (status) { + status = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase(); + } + return { + ...c, + status, + workspaceId: this.workspaceId, + syncStatus: 'synced' + }; + }); await LocalCRMRepository.saveMany('campaigns', records, true); // skipQueue = true } } catch (e) { @@ -328,6 +335,11 @@ export class SyncEngine { try { const payload = JSON.parse(item.payload || '{}'); + // Map local campaign status casing to server uppercase requirements + if (item.entityType === 'campaigns' && payload.status) { + payload.status = payload.status.toUpperCase(); + } + // Resolve SDK module dynamically const clientModule = this.resolveSdkModule(item.entityType); if (!clientModule) {