@open-mercato/core 0.6.4-develop.4264.1.53368d85fe → 0.6.4-develop.4282.1.4d95e85930

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.4-develop.4264.1.53368d85fe",
3
+ "version": "0.6.4-develop.4282.1.4d95e85930",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -243,16 +243,16 @@
243
243
  "zod": "^4.4.3"
244
244
  },
245
245
  "peerDependencies": {
246
- "@open-mercato/ai-assistant": "0.6.4-develop.4264.1.53368d85fe",
247
- "@open-mercato/shared": "0.6.4-develop.4264.1.53368d85fe",
248
- "@open-mercato/ui": "0.6.4-develop.4264.1.53368d85fe",
246
+ "@open-mercato/ai-assistant": "0.6.4-develop.4282.1.4d95e85930",
247
+ "@open-mercato/shared": "0.6.4-develop.4282.1.4d95e85930",
248
+ "@open-mercato/ui": "0.6.4-develop.4282.1.4d95e85930",
249
249
  "react": "^19.0.0",
250
250
  "react-dom": "^19.0.0"
251
251
  },
252
252
  "devDependencies": {
253
- "@open-mercato/ai-assistant": "0.6.4-develop.4264.1.53368d85fe",
254
- "@open-mercato/shared": "0.6.4-develop.4264.1.53368d85fe",
255
- "@open-mercato/ui": "0.6.4-develop.4264.1.53368d85fe",
253
+ "@open-mercato/ai-assistant": "0.6.4-develop.4282.1.4d95e85930",
254
+ "@open-mercato/shared": "0.6.4-develop.4282.1.4d95e85930",
255
+ "@open-mercato/ui": "0.6.4-develop.4282.1.4d95e85930",
256
256
  "@testing-library/dom": "^10.4.1",
257
257
  "@testing-library/jest-dom": "^6.9.1",
258
258
  "@testing-library/react": "^16.3.1",
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import * as React from 'react'
4
- import { Users, Phone, Check, Mail, Calendar, AlertTriangle, X } from 'lucide-react'
4
+ import { Users, Phone, Check, Mail, Calendar, AlertTriangle, X, StickyNote } from 'lucide-react'
5
5
  import { cn } from '@open-mercato/shared/lib/utils'
6
6
  import { useT } from '@open-mercato/shared/lib/i18n/context'
7
7
  import { validatePhoneNumber } from '@open-mercato/shared/lib/phone'
@@ -33,6 +33,7 @@ const TYPE_TABS: Array<{ type: ActivityType; icon: React.ComponentType<{ classNa
33
33
  { type: 'call', icon: Phone, labelKey: 'customers.schedule.types.call', fallback: 'Call' },
34
34
  { type: 'task', icon: Check, labelKey: 'customers.schedule.types.task', fallback: 'Task' },
35
35
  { type: 'email', icon: Mail, labelKey: 'customers.schedule.types.email', fallback: 'Email' },
36
+ { type: 'note', icon: StickyNote, labelKey: 'customers.schedule.types.note', fallback: 'Note' },
36
37
  ]
37
38
 
38
39
  type DialogChrome = { titleKey: string; titleFallback: string; subtitleKey: string; subtitleFallback: string; saveKey: string; saveFallback: string; saveIcon: React.ComponentType<{ className?: string }> }
@@ -58,6 +59,11 @@ const TYPE_CHROME: Record<ActivityType, DialogChrome> = {
58
59
  subtitleKey: 'customers.schedule.email.subtitle', subtitleFallback: 'Compose and send a tracked email',
59
60
  saveKey: 'customers.schedule.email.save', saveFallback: 'Send email', saveIcon: Mail,
60
61
  },
62
+ note: {
63
+ titleKey: 'customers.schedule.note.title', titleFallback: 'Add note',
64
+ subtitleKey: 'customers.schedule.note.subtitle', subtitleFallback: 'Write down a note about this interaction',
65
+ saveKey: 'customers.schedule.note.save', saveFallback: 'Save note', saveIcon: StickyNote,
66
+ },
61
67
  }
62
68
 
63
69
  const CALL_DIRECTIONS: Array<{ key: 'outbound' | 'inbound'; labelKey: string; labelFallback: string; dot: string }> = [
@@ -1,4 +1,4 @@
1
- export type ActivityType = 'meeting' | 'call' | 'task' | 'email'
1
+ export type ActivityType = 'meeting' | 'call' | 'task' | 'email' | 'note'
2
2
 
3
3
  export type ScheduleFieldId =
4
4
  | 'title'
@@ -39,6 +39,9 @@ export const FIELD_VISIBILITY: Record<ActivityType, Set<ScheduleFieldId>> = {
39
39
  'participants', 'linkedEntities', 'description',
40
40
  'reminder', 'visibility',
41
41
  ]),
42
+ note: new Set([
43
+ 'title', 'linkedEntities', 'description', 'visibility',
44
+ ]),
42
45
  }
43
46
 
44
47
  type LabelOverride = { key: string; fallback: string }
@@ -71,6 +74,10 @@ export const FIELD_LABEL_OVERRIDES: Partial<
71
74
  linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },
72
75
  description: { key: 'customers.schedule.message', fallback: 'Message' },
73
76
  },
77
+ note: {
78
+ linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },
79
+ description: { key: 'customers.schedule.note.content', fallback: 'Note' },
80
+ },
74
81
  }
75
82
 
76
83
  export function isVisible(type: ActivityType, fieldId: ScheduleFieldId): boolean {
@@ -60,6 +60,7 @@ const DEFAULT_REMINDER_MINUTES: Record<ActivityType, number> = {
60
60
  call: 5,
61
61
  task: 1440,
62
62
  email: 15,
63
+ note: 15,
63
64
  }
64
65
 
65
66
  function padDatePart(value: number): string {
@@ -2207,6 +2207,10 @@
2207
2207
  "customers.schedule.meeting.title": "Neues Meeting",
2208
2208
  "customers.schedule.message": "Message",
2209
2209
  "customers.schedule.noResults": "No results",
2210
+ "customers.schedule.note.content": "Notiz",
2211
+ "customers.schedule.note.save": "Notiz speichern",
2212
+ "customers.schedule.note.subtitle": "Eine Notiz zu dieser Interaktion schreiben",
2213
+ "customers.schedule.note.title": "Notiz hinzufügen",
2210
2214
  "customers.schedule.participants": "Participants",
2211
2215
  "customers.schedule.recurrence.active": "Repeats",
2212
2216
  "customers.schedule.recurrence.afterCount": "After {{count}} occurrences",
@@ -2253,6 +2257,7 @@
2253
2257
  "customers.schedule.types.call": "Call",
2254
2258
  "customers.schedule.types.email": "Email",
2255
2259
  "customers.schedule.types.meeting": "Meeting",
2260
+ "customers.schedule.types.note": "Notiz",
2256
2261
  "customers.schedule.types.task": "Task",
2257
2262
  "customers.schedule.update": "Aktivität aktualisieren",
2258
2263
  "customers.schedule.visibility": "Visibility",
@@ -2207,6 +2207,10 @@
2207
2207
  "customers.schedule.meeting.title": "New meeting",
2208
2208
  "customers.schedule.message": "Message",
2209
2209
  "customers.schedule.noResults": "No results",
2210
+ "customers.schedule.note.content": "Note",
2211
+ "customers.schedule.note.save": "Save note",
2212
+ "customers.schedule.note.subtitle": "Write down a note about this interaction",
2213
+ "customers.schedule.note.title": "Add note",
2210
2214
  "customers.schedule.participants": "Participants",
2211
2215
  "customers.schedule.recurrence.active": "Repeats",
2212
2216
  "customers.schedule.recurrence.afterCount": "After {{count}} occurrences",
@@ -2253,6 +2257,7 @@
2253
2257
  "customers.schedule.types.call": "Call",
2254
2258
  "customers.schedule.types.email": "Email",
2255
2259
  "customers.schedule.types.meeting": "Meeting",
2260
+ "customers.schedule.types.note": "Note",
2256
2261
  "customers.schedule.types.task": "Task",
2257
2262
  "customers.schedule.update": "Update activity",
2258
2263
  "customers.schedule.visibility": "Visibility",
@@ -2207,6 +2207,10 @@
2207
2207
  "customers.schedule.meeting.title": "Nueva reunión",
2208
2208
  "customers.schedule.message": "Message",
2209
2209
  "customers.schedule.noResults": "No results",
2210
+ "customers.schedule.note.content": "Nota",
2211
+ "customers.schedule.note.save": "Guardar nota",
2212
+ "customers.schedule.note.subtitle": "Escribe una nota sobre esta interacción",
2213
+ "customers.schedule.note.title": "Añadir nota",
2210
2214
  "customers.schedule.participants": "Participants",
2211
2215
  "customers.schedule.recurrence.active": "Repeats",
2212
2216
  "customers.schedule.recurrence.afterCount": "After {{count}} occurrences",
@@ -2253,6 +2257,7 @@
2253
2257
  "customers.schedule.types.call": "Call",
2254
2258
  "customers.schedule.types.email": "Email",
2255
2259
  "customers.schedule.types.meeting": "Meeting",
2260
+ "customers.schedule.types.note": "Nota",
2256
2261
  "customers.schedule.types.task": "Task",
2257
2262
  "customers.schedule.update": "Actualizar actividad",
2258
2263
  "customers.schedule.visibility": "Visibility",
@@ -2207,6 +2207,10 @@
2207
2207
  "customers.schedule.meeting.title": "Nowe spotkanie",
2208
2208
  "customers.schedule.message": "Wiadomość",
2209
2209
  "customers.schedule.noResults": "No results",
2210
+ "customers.schedule.note.content": "Notatka",
2211
+ "customers.schedule.note.save": "Zapisz notatkę",
2212
+ "customers.schedule.note.subtitle": "Zapisz notatkę dotyczącą tej interakcji",
2213
+ "customers.schedule.note.title": "Dodaj notatkę",
2210
2214
  "customers.schedule.participants": "Uczestnicy",
2211
2215
  "customers.schedule.recurrence.active": "Powtarza się",
2212
2216
  "customers.schedule.recurrence.afterCount": "Po {{count}} wystąpieniach",
@@ -2253,6 +2257,7 @@
2253
2257
  "customers.schedule.types.call": "Połączenie",
2254
2258
  "customers.schedule.types.email": "E-mail",
2255
2259
  "customers.schedule.types.meeting": "Spotkanie",
2260
+ "customers.schedule.types.note": "Notatka",
2256
2261
  "customers.schedule.types.task": "Zadanie",
2257
2262
  "customers.schedule.update": "Aktualizuj aktywność",
2258
2263
  "customers.schedule.visibility": "Widoczność",
@@ -261,9 +261,6 @@ export async function refreshCoverageSnapshot(
261
261
  if (tenantId !== null && hasTenant) baseQuery = baseQuery.where('b.tenant_id' as any, '=', tenantId)
262
262
  if (!withDeleted && hasDeleted) baseQuery = baseQuery.where('b.deleted_at' as any, 'is', null as any)
263
263
 
264
- const baseRow = await baseQuery.executeTakeFirst() as { count: unknown } | undefined
265
- const baseCount = toCount(baseRow?.count)
266
-
267
264
  let indexQuery = db
268
265
  .selectFrom('entity_indexes as ei' as any)
269
266
  .select(sql`count(*)`.as('count'))
@@ -272,13 +269,10 @@ export async function refreshCoverageSnapshot(
272
269
  if (tenantId !== null) indexQuery = indexQuery.where('ei.tenant_id' as any, '=', tenantId)
273
270
  if (!withDeleted) indexQuery = indexQuery.where('ei.deleted_at' as any, 'is', null as any)
274
271
 
275
- const indexRow = await indexQuery.executeTakeFirst() as { count: unknown } | undefined
276
- const indexCount = toCount(indexRow?.count)
272
+ const vectorCountPromise = (async (): Promise<number | undefined> => {
273
+ const hasVectorTable = await tableHasColumn(db, 'vector_search', 'entity_id')
274
+ if (!hasVectorTable || typeof tenantId !== 'string' || tenantId.length === 0) return undefined
277
275
 
278
- // Count vector entries directly from database
279
- let vectorCount: number | undefined
280
- const hasVectorTable = await tableHasColumn(db, 'vector_search', 'entity_id')
281
- if (hasVectorTable && typeof tenantId === 'string' && tenantId.length > 0) {
282
276
  try {
283
277
  let vectorQuery = db
284
278
  .selectFrom('vector_search' as any)
@@ -289,7 +283,7 @@ export async function refreshCoverageSnapshot(
289
283
  vectorQuery = vectorQuery.where('organization_id' as any, '=', organizationId)
290
284
  }
291
285
  const vectorRow = await vectorQuery.executeTakeFirst() as { count: unknown } | undefined
292
- vectorCount = toCount(vectorRow?.count)
286
+ return toCount(vectorRow?.count)
293
287
  } catch (err) {
294
288
  console.warn('[query_index] Failed to resolve vector count for coverage snapshot', {
295
289
  entityType,
@@ -297,9 +291,18 @@ export async function refreshCoverageSnapshot(
297
291
  organizationId,
298
292
  error: err instanceof Error ? err.message : err,
299
293
  })
300
- vectorCount = undefined
294
+ return undefined
301
295
  }
302
- }
296
+ })()
297
+
298
+ const [baseRow, indexRow, vectorCount] = await Promise.all([
299
+ baseQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,
300
+ indexQuery.executeTakeFirst() as Promise<{ count: unknown } | undefined>,
301
+ vectorCountPromise,
302
+ ])
303
+
304
+ const baseCount = toCount(baseRow?.count)
305
+ const indexCount = toCount(indexRow?.count)
303
306
 
304
307
  await writeCoverageCounts(em, { entityType, tenantId, organizationId, withDeleted }, {
305
308
  baseCount,
@@ -1758,17 +1758,25 @@ export class HybridQueryEngine implements QueryEngine {
1758
1758
  withDeleted: boolean
1759
1759
  ): Promise<{ baseCount: number; indexedCount: number } | null> {
1760
1760
  try {
1761
- if (!this.isCoverageOptimizationEnabled()) {
1762
- await refreshCoverageSnapshot(this.em, {
1763
- entityType: entity, tenantId, organizationId, withDeleted,
1764
- })
1765
- }
1766
1761
  const db = this.getDb()
1767
- const row = await readCoverageSnapshot(db as any, {
1762
+ const scope = {
1768
1763
  entityType: entity, tenantId, organizationId, withDeleted,
1769
- })
1770
- if (!row) return null
1771
- return { baseCount: row.baseCount, indexedCount: row.indexedCount }
1764
+ }
1765
+ const row = await readCoverageSnapshot(db as any, scope)
1766
+ if (row && this.isCoverageSnapshotFresh(row)) {
1767
+ return { baseCount: row.baseCount, indexedCount: row.indexedCount }
1768
+ }
1769
+
1770
+ if (this.isCoverageOptimizationEnabled()) {
1771
+ this.scheduleCoverageRefresh(entity, tenantId, organizationId, withDeleted)
1772
+ if (!row) return null
1773
+ return { baseCount: row.baseCount, indexedCount: row.indexedCount }
1774
+ }
1775
+
1776
+ await refreshCoverageSnapshot(this.em, scope)
1777
+ const refreshed = await readCoverageSnapshot(db as any, scope)
1778
+ if (!refreshed) return null
1779
+ return { baseCount: refreshed.baseCount, indexedCount: refreshed.indexedCount }
1772
1780
  } catch (err) {
1773
1781
  if (this.isDebugVerbosity()) {
1774
1782
  this.debug('coverage:snapshot:read-error', {
@@ -1780,6 +1788,21 @@ export class HybridQueryEngine implements QueryEngine {
1780
1788
  }
1781
1789
  }
1782
1790
 
1791
+ private isCoverageSnapshotFresh(
1792
+ row: Awaited<ReturnType<typeof readCoverageSnapshot>>
1793
+ ): boolean {
1794
+ if (this.coverageStatsTtlMs <= 0) return false
1795
+ if (!row) return false
1796
+ const refreshedAt = row.refreshed_at instanceof Date
1797
+ ? row.refreshed_at
1798
+ : row.refreshed_at
1799
+ ? new Date(row.refreshed_at)
1800
+ : null
1801
+ const refreshedAtMs = refreshedAt?.getTime()
1802
+ if (!refreshedAtMs || !Number.isFinite(refreshedAtMs)) return false
1803
+ return Date.now() - refreshedAtMs <= this.coverageStatsTtlMs
1804
+ }
1805
+
1783
1806
  private scheduleAutoReindex(
1784
1807
  entity: string,
1785
1808
  opts: QueryOptions,