@open-mercato/core 0.6.5-develop.5187.1.82e5532561 → 0.6.5-develop.5212.1.b47932beef

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.
@@ -1,4 +1,4 @@
1
- [build:core] found 3310 entry points
1
+ [build:core] found 3311 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 185 entry points
4
4
  [build:core:generated] built successfully
@@ -1,5 +1,5 @@
1
1
  import { promises as fs } from "fs";
2
- import path from "path";
2
+ import { resolveContainedPath, resolveLegacyPublicRoot } from "../pathContainment.js";
3
3
  class LegacyPublicStorageDriver {
4
4
  constructor() {
5
5
  this.key = "legacyPublic";
@@ -27,13 +27,7 @@ class LegacyPublicStorageDriver {
27
27
  };
28
28
  }
29
29
  resolveAbsolutePath(storagePath) {
30
- let safeRelative = storagePath.replace(/^\/*/, "");
31
- let prev;
32
- do {
33
- prev = safeRelative;
34
- safeRelative = safeRelative.replace(/\.\.(\/|\\)/g, "");
35
- } while (safeRelative !== prev);
36
- return path.join(process.cwd(), safeRelative);
30
+ return resolveContainedPath(process.cwd(), storagePath, resolveLegacyPublicRoot());
37
31
  }
38
32
  }
39
33
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/attachments/lib/drivers/legacyPublicDriver.ts"],
4
- "sourcesContent": ["import { promises as fs } from 'fs'\nimport path from 'path'\nimport type { StorageDriver, StoreFilePayload, StoredFile, ReadFileResult } from './types'\n\nexport class LegacyPublicStorageDriver implements StorageDriver {\n readonly key = 'legacyPublic'\n\n store(_payload: StoreFilePayload): Promise<StoredFile> {\n throw new Error('legacy-public driver is read-only')\n }\n\n async read(_partitionCode: string, storagePath: string): Promise<ReadFileResult> {\n const absolutePath = this.resolveAbsolutePath(storagePath)\n const buffer = await fs.readFile(absolutePath)\n return { buffer }\n }\n\n async delete(_partitionCode: string, storagePath: string): Promise<void> {\n const absolutePath = this.resolveAbsolutePath(storagePath)\n try {\n await fs.unlink(absolutePath)\n } catch {\n // best-effort removal\n }\n }\n\n async toLocalPath(\n _partitionCode: string,\n storagePath: string,\n ): Promise<{ filePath: string; cleanup: () => Promise<void> }> {\n return {\n filePath: this.resolveAbsolutePath(storagePath),\n cleanup: async () => {},\n }\n }\n\n private resolveAbsolutePath(storagePath: string): string {\n let safeRelative = storagePath.replace(/^\\/*/, '')\n let prev: string\n do {\n prev = safeRelative\n safeRelative = safeRelative.replace(/\\.\\.(\\/|\\\\)/g, '')\n } while (safeRelative !== prev)\n return path.join(process.cwd(), safeRelative)\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AAGV,MAAM,0BAAmD;AAAA,EAAzD;AACL,SAAS,MAAM;AAAA;AAAA,EAEf,MAAM,UAAiD;AACrD,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAAA,EAEA,MAAM,KAAK,gBAAwB,aAA8C;AAC/E,UAAM,eAAe,KAAK,oBAAoB,WAAW;AACzD,UAAM,SAAS,MAAM,GAAG,SAAS,YAAY;AAC7C,WAAO,EAAE,OAAO;AAAA,EAClB;AAAA,EAEA,MAAM,OAAO,gBAAwB,aAAoC;AACvE,UAAM,eAAe,KAAK,oBAAoB,WAAW;AACzD,QAAI;AACF,YAAM,GAAG,OAAO,YAAY;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,YACJ,gBACA,aAC6D;AAC7D,WAAO;AAAA,MACL,UAAU,KAAK,oBAAoB,WAAW;AAAA,MAC9C,SAAS,YAAY;AAAA,MAAC;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,oBAAoB,aAA6B;AACvD,QAAI,eAAe,YAAY,QAAQ,QAAQ,EAAE;AACjD,QAAI;AACJ,OAAG;AACD,aAAO;AACP,qBAAe,aAAa,QAAQ,gBAAgB,EAAE;AAAA,IACxD,SAAS,iBAAiB;AAC1B,WAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,YAAY;AAAA,EAC9C;AACF;",
4
+ "sourcesContent": ["import { promises as fs } from 'fs'\nimport { resolveContainedPath, resolveLegacyPublicRoot } from '../pathContainment'\nimport type { StorageDriver, StoreFilePayload, StoredFile, ReadFileResult } from './types'\n\nexport class LegacyPublicStorageDriver implements StorageDriver {\n readonly key = 'legacyPublic'\n\n store(_payload: StoreFilePayload): Promise<StoredFile> {\n throw new Error('legacy-public driver is read-only')\n }\n\n async read(_partitionCode: string, storagePath: string): Promise<ReadFileResult> {\n const absolutePath = this.resolveAbsolutePath(storagePath)\n const buffer = await fs.readFile(absolutePath)\n return { buffer }\n }\n\n async delete(_partitionCode: string, storagePath: string): Promise<void> {\n const absolutePath = this.resolveAbsolutePath(storagePath)\n try {\n await fs.unlink(absolutePath)\n } catch {\n // best-effort removal\n }\n }\n\n async toLocalPath(\n _partitionCode: string,\n storagePath: string,\n ): Promise<{ filePath: string; cleanup: () => Promise<void> }> {\n return {\n filePath: this.resolveAbsolutePath(storagePath),\n cleanup: async () => {},\n }\n }\n\n private resolveAbsolutePath(storagePath: string): string {\n return resolveContainedPath(process.cwd(), storagePath, resolveLegacyPublicRoot())\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,YAAY,UAAU;AAC/B,SAAS,sBAAsB,+BAA+B;AAGvD,MAAM,0BAAmD;AAAA,EAAzD;AACL,SAAS,MAAM;AAAA;AAAA,EAEf,MAAM,UAAiD;AACrD,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAAA,EAEA,MAAM,KAAK,gBAAwB,aAA8C;AAC/E,UAAM,eAAe,KAAK,oBAAoB,WAAW;AACzD,UAAM,SAAS,MAAM,GAAG,SAAS,YAAY;AAC7C,WAAO,EAAE,OAAO;AAAA,EAClB;AAAA,EAEA,MAAM,OAAO,gBAAwB,aAAoC;AACvE,UAAM,eAAe,KAAK,oBAAoB,WAAW;AACzD,QAAI;AACF,YAAM,GAAG,OAAO,YAAY;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,YACJ,gBACA,aAC6D;AAC7D,WAAO;AAAA,MACL,UAAU,KAAK,oBAAoB,WAAW;AAAA,MAC9C,SAAS,YAAY;AAAA,MAAC;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,oBAAoB,aAA6B;AACvD,WAAO,qBAAqB,QAAQ,IAAI,GAAG,aAAa,wBAAwB,CAAC;AAAA,EACnF;AACF;",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@ import { promises as fs } from "fs";
2
2
  import path from "path";
3
3
  import { randomUUID } from "crypto";
4
4
  import { resolvePartitionRoot } from "../storage.js";
5
+ import { resolveContainedPath } from "../pathContainment.js";
5
6
  function sanitizeFileName(fileName) {
6
7
  if (!fileName) return "file";
7
8
  return fileName.replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -54,14 +55,8 @@ class LocalStorageDriver {
54
55
  };
55
56
  }
56
57
  resolveAbsolutePath(partitionCode, storagePath) {
57
- let safeRelative = storagePath.replace(/^\/*/, "");
58
- let prev;
59
- do {
60
- prev = safeRelative;
61
- safeRelative = safeRelative.replace(/\.\.(\/|\\)/g, "");
62
- } while (safeRelative !== prev);
63
58
  const root = resolvePartitionRoot(partitionCode);
64
- return path.join(root, safeRelative);
59
+ return resolveContainedPath(root, storagePath);
65
60
  }
66
61
  }
67
62
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/attachments/lib/drivers/localDriver.ts"],
4
- "sourcesContent": ["import { promises as fs } from 'fs'\nimport path from 'path'\nimport { randomUUID } from 'crypto'\nimport { resolvePartitionRoot } from '../storage'\nimport type { StorageDriver, StoreFilePayload, StoredFile, ReadFileResult } from './types'\n\nfunction sanitizeFileName(fileName: string): string {\n if (!fileName) return 'file'\n return fileName.replace(/[^a-zA-Z0-9._-]/g, '_')\n}\n\nfunction resolveOrgSegment(orgId: string | null | undefined): string {\n if (typeof orgId === 'string' && orgId.trim().length > 0) return `org_${orgId}`\n return 'org_shared'\n}\n\nfunction resolveTenantSegment(tenantId: string | null | undefined): string {\n if (typeof tenantId === 'string' && tenantId.trim().length > 0) return `tenant_${tenantId}`\n return 'tenant_shared'\n}\n\nexport class LocalStorageDriver implements StorageDriver {\n readonly key = 'local'\n\n async store(payload: StoreFilePayload): Promise<StoredFile> {\n const root = resolvePartitionRoot(payload.partitionCode)\n const orgSegment = resolveOrgSegment(payload.orgId ?? null)\n const tenantSegment = resolveTenantSegment(payload.tenantId ?? null)\n const safeName = sanitizeFileName(payload.fileName || 'file')\n const uniqueSuffix = randomUUID().replace(/-/g, '').slice(0, 12)\n const storedName = `${Date.now()}_${uniqueSuffix}_${safeName}`\n const relativePath = path.join(orgSegment, tenantSegment, storedName)\n const absolutePath = path.join(root, relativePath)\n await fs.mkdir(path.dirname(absolutePath), { recursive: true })\n await fs.writeFile(absolutePath, payload.buffer)\n return {\n storagePath: relativePath.replace(/\\\\/g, '/'),\n }\n }\n\n async read(partitionCode: string, storagePath: string): Promise<ReadFileResult> {\n const absolutePath = this.resolveAbsolutePath(partitionCode, storagePath)\n const buffer = await fs.readFile(absolutePath)\n return { buffer }\n }\n\n async delete(partitionCode: string, storagePath: string): Promise<void> {\n const absolutePath = this.resolveAbsolutePath(partitionCode, storagePath)\n try {\n await fs.unlink(absolutePath)\n } catch {\n // best-effort removal\n }\n }\n\n async toLocalPath(\n partitionCode: string,\n storagePath: string,\n ): Promise<{ filePath: string; cleanup: () => Promise<void> }> {\n const filePath = this.resolveAbsolutePath(partitionCode, storagePath)\n return {\n filePath,\n cleanup: async () => {\n // no-op: local path is the real file, do not delete\n },\n }\n }\n\n private resolveAbsolutePath(partitionCode: string, storagePath: string): string {\n let safeRelative = storagePath.replace(/^\\/*/, '')\n let prev: string\n do {\n prev = safeRelative\n safeRelative = safeRelative.replace(/\\.\\.(\\/|\\\\)/g, '')\n } while (safeRelative !== prev)\n const root = resolvePartitionRoot(partitionCode)\n return path.join(root, safeRelative)\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AAGrC,SAAS,iBAAiB,UAA0B;AAClD,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,SAAS,QAAQ,oBAAoB,GAAG;AACjD;AAEA,SAAS,kBAAkB,OAA0C;AACnE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,OAAO,KAAK;AAC7E,SAAO;AACT;AAEA,SAAS,qBAAqB,UAA6C;AACzE,MAAI,OAAO,aAAa,YAAY,SAAS,KAAK,EAAE,SAAS,EAAG,QAAO,UAAU,QAAQ;AACzF,SAAO;AACT;AAEO,MAAM,mBAA4C;AAAA,EAAlD;AACL,SAAS,MAAM;AAAA;AAAA,EAEf,MAAM,MAAM,SAAgD;AAC1D,UAAM,OAAO,qBAAqB,QAAQ,aAAa;AACvD,UAAM,aAAa,kBAAkB,QAAQ,SAAS,IAAI;AAC1D,UAAM,gBAAgB,qBAAqB,QAAQ,YAAY,IAAI;AACnE,UAAM,WAAW,iBAAiB,QAAQ,YAAY,MAAM;AAC5D,UAAM,eAAe,WAAW,EAAE,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE;AAC/D,UAAM,aAAa,GAAG,KAAK,IAAI,CAAC,IAAI,YAAY,IAAI,QAAQ;AAC5D,UAAM,eAAe,KAAK,KAAK,YAAY,eAAe,UAAU;AACpE,UAAM,eAAe,KAAK,KAAK,MAAM,YAAY;AACjD,UAAM,GAAG,MAAM,KAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9D,UAAM,GAAG,UAAU,cAAc,QAAQ,MAAM;AAC/C,WAAO;AAAA,MACL,aAAa,aAAa,QAAQ,OAAO,GAAG;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,eAAuB,aAA8C;AAC9E,UAAM,eAAe,KAAK,oBAAoB,eAAe,WAAW;AACxE,UAAM,SAAS,MAAM,GAAG,SAAS,YAAY;AAC7C,WAAO,EAAE,OAAO;AAAA,EAClB;AAAA,EAEA,MAAM,OAAO,eAAuB,aAAoC;AACtE,UAAM,eAAe,KAAK,oBAAoB,eAAe,WAAW;AACxE,QAAI;AACF,YAAM,GAAG,OAAO,YAAY;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,YACJ,eACA,aAC6D;AAC7D,UAAM,WAAW,KAAK,oBAAoB,eAAe,WAAW;AACpE,WAAO;AAAA,MACL;AAAA,MACA,SAAS,YAAY;AAAA,MAErB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,oBAAoB,eAAuB,aAA6B;AAC9E,QAAI,eAAe,YAAY,QAAQ,QAAQ,EAAE;AACjD,QAAI;AACJ,OAAG;AACD,aAAO;AACP,qBAAe,aAAa,QAAQ,gBAAgB,EAAE;AAAA,IACxD,SAAS,iBAAiB;AAC1B,UAAM,OAAO,qBAAqB,aAAa;AAC/C,WAAO,KAAK,KAAK,MAAM,YAAY;AAAA,EACrC;AACF;",
4
+ "sourcesContent": ["import { promises as fs } from 'fs'\nimport path from 'path'\nimport { randomUUID } from 'crypto'\nimport { resolvePartitionRoot } from '../storage'\nimport { resolveContainedPath } from '../pathContainment'\nimport type { StorageDriver, StoreFilePayload, StoredFile, ReadFileResult } from './types'\n\nfunction sanitizeFileName(fileName: string): string {\n if (!fileName) return 'file'\n return fileName.replace(/[^a-zA-Z0-9._-]/g, '_')\n}\n\nfunction resolveOrgSegment(orgId: string | null | undefined): string {\n if (typeof orgId === 'string' && orgId.trim().length > 0) return `org_${orgId}`\n return 'org_shared'\n}\n\nfunction resolveTenantSegment(tenantId: string | null | undefined): string {\n if (typeof tenantId === 'string' && tenantId.trim().length > 0) return `tenant_${tenantId}`\n return 'tenant_shared'\n}\n\nexport class LocalStorageDriver implements StorageDriver {\n readonly key = 'local'\n\n async store(payload: StoreFilePayload): Promise<StoredFile> {\n const root = resolvePartitionRoot(payload.partitionCode)\n const orgSegment = resolveOrgSegment(payload.orgId ?? null)\n const tenantSegment = resolveTenantSegment(payload.tenantId ?? null)\n const safeName = sanitizeFileName(payload.fileName || 'file')\n const uniqueSuffix = randomUUID().replace(/-/g, '').slice(0, 12)\n const storedName = `${Date.now()}_${uniqueSuffix}_${safeName}`\n const relativePath = path.join(orgSegment, tenantSegment, storedName)\n const absolutePath = path.join(root, relativePath)\n await fs.mkdir(path.dirname(absolutePath), { recursive: true })\n await fs.writeFile(absolutePath, payload.buffer)\n return {\n storagePath: relativePath.replace(/\\\\/g, '/'),\n }\n }\n\n async read(partitionCode: string, storagePath: string): Promise<ReadFileResult> {\n const absolutePath = this.resolveAbsolutePath(partitionCode, storagePath)\n const buffer = await fs.readFile(absolutePath)\n return { buffer }\n }\n\n async delete(partitionCode: string, storagePath: string): Promise<void> {\n const absolutePath = this.resolveAbsolutePath(partitionCode, storagePath)\n try {\n await fs.unlink(absolutePath)\n } catch {\n // best-effort removal\n }\n }\n\n async toLocalPath(\n partitionCode: string,\n storagePath: string,\n ): Promise<{ filePath: string; cleanup: () => Promise<void> }> {\n const filePath = this.resolveAbsolutePath(partitionCode, storagePath)\n return {\n filePath,\n cleanup: async () => {\n // no-op: local path is the real file, do not delete\n },\n }\n }\n\n private resolveAbsolutePath(partitionCode: string, storagePath: string): string {\n const root = resolvePartitionRoot(partitionCode)\n return resolveContainedPath(root, storagePath)\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AACrC,SAAS,4BAA4B;AAGrC,SAAS,iBAAiB,UAA0B;AAClD,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,SAAS,QAAQ,oBAAoB,GAAG;AACjD;AAEA,SAAS,kBAAkB,OAA0C;AACnE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,OAAO,KAAK;AAC7E,SAAO;AACT;AAEA,SAAS,qBAAqB,UAA6C;AACzE,MAAI,OAAO,aAAa,YAAY,SAAS,KAAK,EAAE,SAAS,EAAG,QAAO,UAAU,QAAQ;AACzF,SAAO;AACT;AAEO,MAAM,mBAA4C;AAAA,EAAlD;AACL,SAAS,MAAM;AAAA;AAAA,EAEf,MAAM,MAAM,SAAgD;AAC1D,UAAM,OAAO,qBAAqB,QAAQ,aAAa;AACvD,UAAM,aAAa,kBAAkB,QAAQ,SAAS,IAAI;AAC1D,UAAM,gBAAgB,qBAAqB,QAAQ,YAAY,IAAI;AACnE,UAAM,WAAW,iBAAiB,QAAQ,YAAY,MAAM;AAC5D,UAAM,eAAe,WAAW,EAAE,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE;AAC/D,UAAM,aAAa,GAAG,KAAK,IAAI,CAAC,IAAI,YAAY,IAAI,QAAQ;AAC5D,UAAM,eAAe,KAAK,KAAK,YAAY,eAAe,UAAU;AACpE,UAAM,eAAe,KAAK,KAAK,MAAM,YAAY;AACjD,UAAM,GAAG,MAAM,KAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9D,UAAM,GAAG,UAAU,cAAc,QAAQ,MAAM;AAC/C,WAAO;AAAA,MACL,aAAa,aAAa,QAAQ,OAAO,GAAG;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,eAAuB,aAA8C;AAC9E,UAAM,eAAe,KAAK,oBAAoB,eAAe,WAAW;AACxE,UAAM,SAAS,MAAM,GAAG,SAAS,YAAY;AAC7C,WAAO,EAAE,OAAO;AAAA,EAClB;AAAA,EAEA,MAAM,OAAO,eAAuB,aAAoC;AACtE,UAAM,eAAe,KAAK,oBAAoB,eAAe,WAAW;AACxE,QAAI;AACF,YAAM,GAAG,OAAO,YAAY;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,YACJ,eACA,aAC6D;AAC7D,UAAM,WAAW,KAAK,oBAAoB,eAAe,WAAW;AACpE,WAAO;AAAA,MACL;AAAA,MACA,SAAS,YAAY;AAAA,MAErB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,oBAAoB,eAAuB,aAA6B;AAC9E,UAAM,OAAO,qBAAqB,aAAa;AAC/C,WAAO,qBAAqB,MAAM,WAAW;AAAA,EAC/C;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,23 @@
1
+ import path from "path";
2
+ function sanitizeStorageRelativePath(storagePath) {
3
+ return String(storagePath ?? "").split(/[\\/]+/).filter((segment) => segment.length > 0 && segment !== "." && segment !== "..").join(path.sep);
4
+ }
5
+ function resolveContainedPath(joinRoot, storagePath, containmentRoot) {
6
+ const base = path.resolve(joinRoot);
7
+ const boundary = path.resolve(containmentRoot ?? joinRoot);
8
+ const candidate = path.resolve(base, sanitizeStorageRelativePath(storagePath));
9
+ const relativeToBoundary = path.relative(boundary, candidate);
10
+ if (relativeToBoundary.startsWith("..") || path.isAbsolute(relativeToBoundary)) {
11
+ throw new Error("[internal] attachment storage path escapes its containment root");
12
+ }
13
+ return candidate;
14
+ }
15
+ function resolveLegacyPublicRoot() {
16
+ return path.join(process.cwd(), "public");
17
+ }
18
+ export {
19
+ resolveContainedPath,
20
+ resolveLegacyPublicRoot,
21
+ sanitizeStorageRelativePath
22
+ };
23
+ //# sourceMappingURL=pathContainment.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/attachments/lib/pathContainment.ts"],
4
+ "sourcesContent": ["import path from 'path'\n\n/**\n * Reduce a stored relative path to safe segments: drop leading slashes, empty\n * segments, `.`, and `..`. The result can never contain a traversal segment,\n * so joining it onto a root can never climb above that root.\n */\nexport function sanitizeStorageRelativePath(storagePath: string): string {\n return String(storagePath ?? '')\n .split(/[\\\\/]+/)\n .filter((segment) => segment.length > 0 && segment !== '.' && segment !== '..')\n .join(path.sep)\n}\n\n/**\n * Resolve a stored path against `joinRoot` and assert the result stays within\n * `containmentRoot` (defaults to `joinRoot`). Throws when the resolved path\n * escapes the boundary \u2014 e.g. a legacy row whose path points outside `public/`.\n */\nexport function resolveContainedPath(\n joinRoot: string,\n storagePath: string,\n containmentRoot?: string,\n): string {\n const base = path.resolve(joinRoot)\n const boundary = path.resolve(containmentRoot ?? joinRoot)\n const candidate = path.resolve(base, sanitizeStorageRelativePath(storagePath))\n const relativeToBoundary = path.relative(boundary, candidate)\n if (relativeToBoundary.startsWith('..') || path.isAbsolute(relativeToBoundary)) {\n throw new Error('[internal] attachment storage path escapes its containment root')\n }\n return candidate\n}\n\n/**\n * The fixed sub-root that `legacyPublic` rows are allowed to resolve within.\n * Stored paths include the `public/` prefix (see Migration20251117181353), so\n * they are joined onto `process.cwd()` but constrained to `process.cwd()/public`.\n */\nexport function resolveLegacyPublicRoot(): string {\n return path.join(process.cwd(), 'public')\n}\n"],
5
+ "mappings": "AAAA,OAAO,UAAU;AAOV,SAAS,4BAA4B,aAA6B;AACvE,SAAO,OAAO,eAAe,EAAE,EAC5B,MAAM,QAAQ,EACd,OAAO,CAAC,YAAY,QAAQ,SAAS,KAAK,YAAY,OAAO,YAAY,IAAI,EAC7E,KAAK,KAAK,GAAG;AAClB;AAOO,SAAS,qBACd,UACA,aACA,iBACQ;AACR,QAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,QAAM,WAAW,KAAK,QAAQ,mBAAmB,QAAQ;AACzD,QAAM,YAAY,KAAK,QAAQ,MAAM,4BAA4B,WAAW,CAAC;AAC7E,QAAM,qBAAqB,KAAK,SAAS,UAAU,SAAS;AAC5D,MAAI,mBAAmB,WAAW,IAAI,KAAK,KAAK,WAAW,kBAAkB,GAAG;AAC9E,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AACA,SAAO;AACT;AAOO,SAAS,0BAAkC;AAChD,SAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,QAAQ;AAC1C;",
6
+ "names": []
7
+ }
@@ -2,6 +2,7 @@ import { promises as fs } from "fs";
2
2
  import path from "path";
3
3
  import { randomUUID } from "crypto";
4
4
  import { resolvePartitionEnvKey } from "./partitionEnv.js";
5
+ import { resolveContainedPath, resolveLegacyPublicRoot } from "./pathContainment.js";
5
6
  function resolvePartitionRoot(code) {
6
7
  const envKey = resolvePartitionEnvKey(code);
7
8
  const envPath = process.env[envKey];
@@ -40,16 +41,11 @@ async function storePartitionFile(payload) {
40
41
  };
41
42
  }
42
43
  function resolveAttachmentAbsolutePath(partitionCode, storagePath, storageDriver) {
43
- let safeRelative = storagePath.replace(/^\/*/, "");
44
- do {
45
- var prev = safeRelative;
46
- safeRelative = safeRelative.replace(/\.\.(\/|\\)/g, "");
47
- } while (safeRelative !== prev);
48
44
  if (storageDriver === "legacyPublic") {
49
- return path.join(process.cwd(), safeRelative);
45
+ return resolveContainedPath(process.cwd(), storagePath, resolveLegacyPublicRoot());
50
46
  }
51
47
  const root = resolvePartitionRoot(partitionCode);
52
- return path.join(root, safeRelative);
48
+ return resolveContainedPath(root, storagePath);
53
49
  }
54
50
  async function deletePartitionFile(partitionCode, storagePath, storageDriver) {
55
51
  const absolutePath = resolveAttachmentAbsolutePath(partitionCode, storagePath, storageDriver);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/attachments/lib/storage.ts"],
4
- "sourcesContent": ["import { promises as fs } from 'fs'\nimport path from 'path'\nimport { randomUUID } from 'crypto'\nimport { resolvePartitionEnvKey } from './partitionEnv'\n\nexport function resolvePartitionRoot(code: string): string {\n const envKey = resolvePartitionEnvKey(code)\n const envPath = process.env[envKey]\n if (envPath && envPath.trim().length > 0) {\n return path.resolve(envPath)\n }\n return path.join(process.cwd(), 'storage', 'attachments', code)\n}\n\nfunction sanitizeFileName(fileName: string): string {\n if (!fileName) return 'file'\n return fileName.replace(/[^a-zA-Z0-9._-]/g, '_')\n}\n\nfunction resolveOrgSegment(orgId: string | null | undefined): string {\n if (typeof orgId === 'string' && orgId.trim().length > 0) return `org_${orgId}`\n return 'org_shared'\n}\n\nfunction resolveTenantSegment(tenantId: string | null | undefined): string {\n if (typeof tenantId === 'string' && tenantId.trim().length > 0) return `tenant_${tenantId}`\n return 'tenant_shared'\n}\n\n/**\n * @deprecated Use `StorageDriverFactory.resolveForPartition()` + `driver.store()` instead.\n * Kept for backward compatibility with external callers.\n */\nexport type StorePartitionFilePayload = {\n partitionCode: string\n orgId: string | null | undefined\n tenantId: string | null | undefined\n fileName: string\n buffer: Buffer\n}\n\nexport type StoredPartitionFile = {\n storagePath: string\n absolutePath: string\n fileName: string\n}\n\n/**\n * @deprecated Use `StorageDriverFactory.resolveForPartition()` + `driver.store()` instead.\n */\nexport async function storePartitionFile(payload: StorePartitionFilePayload): Promise<StoredPartitionFile> {\n const root = resolvePartitionRoot(payload.partitionCode)\n const orgSegment = resolveOrgSegment(payload.orgId ?? null)\n const tenantSegment = resolveTenantSegment(payload.tenantId ?? null)\n const safeName = sanitizeFileName(payload.fileName || 'file')\n const uniqueSuffix = randomUUID().replace(/-/g, '').slice(0, 12)\n const storedName = `${Date.now()}_${uniqueSuffix}_${safeName}`\n const relativePath = path.join(orgSegment, tenantSegment, storedName)\n const absolutePath = path.join(root, relativePath)\n await fs.mkdir(path.dirname(absolutePath), { recursive: true })\n await fs.writeFile(absolutePath, payload.buffer)\n return {\n storagePath: relativePath.replace(/\\\\/g, '/'),\n absolutePath,\n fileName: storedName,\n }\n}\n\n/**\n * @deprecated Use `StorageDriverFactory.resolveForAttachment()` + `driver.read()` / `driver.toLocalPath()` instead.\n */\nexport function resolveAttachmentAbsolutePath(\n partitionCode: string,\n storagePath: string,\n storageDriver?: string | null\n): string {\n // Remove leading slashes first\n let safeRelative = storagePath.replace(/^\\/*/, '')\n // Remove all ../ (and ..\\) path traversal segments, repeatedly until gone\n do {\n var prev = safeRelative\n safeRelative = safeRelative.replace(/\\.\\.(\\/|\\\\)/g, '')\n } while (safeRelative !== prev)\n if (storageDriver === 'legacyPublic') {\n return path.join(process.cwd(), safeRelative)\n }\n const root = resolvePartitionRoot(partitionCode)\n return path.join(root, safeRelative)\n}\n\n/**\n * @deprecated Use `StorageDriverFactory.resolveForAttachment()` + `driver.delete()` instead.\n */\nexport async function deletePartitionFile(\n partitionCode: string,\n storagePath: string,\n storageDriver?: string | null\n): Promise<void> {\n const absolutePath = resolveAttachmentAbsolutePath(partitionCode, storagePath, storageDriver)\n try {\n await fs.unlink(absolutePath)\n } catch {\n // best-effort removal\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAC3B,SAAS,8BAA8B;AAEhC,SAAS,qBAAqB,MAAsB;AACzD,QAAM,SAAS,uBAAuB,IAAI;AAC1C,QAAM,UAAU,QAAQ,IAAI,MAAM;AAClC,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACxC,WAAO,KAAK,QAAQ,OAAO;AAAA,EAC7B;AACA,SAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,WAAW,eAAe,IAAI;AAChE;AAEA,SAAS,iBAAiB,UAA0B;AAClD,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,SAAS,QAAQ,oBAAoB,GAAG;AACjD;AAEA,SAAS,kBAAkB,OAA0C;AACnE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,OAAO,KAAK;AAC7E,SAAO;AACT;AAEA,SAAS,qBAAqB,UAA6C;AACzE,MAAI,OAAO,aAAa,YAAY,SAAS,KAAK,EAAE,SAAS,EAAG,QAAO,UAAU,QAAQ;AACzF,SAAO;AACT;AAuBA,eAAsB,mBAAmB,SAAkE;AACzG,QAAM,OAAO,qBAAqB,QAAQ,aAAa;AACvD,QAAM,aAAa,kBAAkB,QAAQ,SAAS,IAAI;AAC1D,QAAM,gBAAgB,qBAAqB,QAAQ,YAAY,IAAI;AACnE,QAAM,WAAW,iBAAiB,QAAQ,YAAY,MAAM;AAC5D,QAAM,eAAe,WAAW,EAAE,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE;AAC/D,QAAM,aAAa,GAAG,KAAK,IAAI,CAAC,IAAI,YAAY,IAAI,QAAQ;AAC5D,QAAM,eAAe,KAAK,KAAK,YAAY,eAAe,UAAU;AACpE,QAAM,eAAe,KAAK,KAAK,MAAM,YAAY;AACjD,QAAM,GAAG,MAAM,KAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9D,QAAM,GAAG,UAAU,cAAc,QAAQ,MAAM;AAC/C,SAAO;AAAA,IACL,aAAa,aAAa,QAAQ,OAAO,GAAG;AAAA,IAC5C;AAAA,IACA,UAAU;AAAA,EACZ;AACF;AAKO,SAAS,8BACd,eACA,aACA,eACQ;AAER,MAAI,eAAe,YAAY,QAAQ,QAAQ,EAAE;AAEjD,KAAG;AACD,QAAI,OAAO;AACX,mBAAe,aAAa,QAAQ,gBAAgB,EAAE;AAAA,EACxD,SAAS,iBAAiB;AAC1B,MAAI,kBAAkB,gBAAgB;AACpC,WAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,YAAY;AAAA,EAC9C;AACA,QAAM,OAAO,qBAAqB,aAAa;AAC/C,SAAO,KAAK,KAAK,MAAM,YAAY;AACrC;AAKA,eAAsB,oBACpB,eACA,aACA,eACe;AACf,QAAM,eAAe,8BAA8B,eAAe,aAAa,aAAa;AAC5F,MAAI;AACF,UAAM,GAAG,OAAO,YAAY;AAAA,EAC9B,QAAQ;AAAA,EAER;AACF;",
4
+ "sourcesContent": ["import { promises as fs } from 'fs'\nimport path from 'path'\nimport { randomUUID } from 'crypto'\nimport { resolvePartitionEnvKey } from './partitionEnv'\nimport { resolveContainedPath, resolveLegacyPublicRoot } from './pathContainment'\n\nexport function resolvePartitionRoot(code: string): string {\n const envKey = resolvePartitionEnvKey(code)\n const envPath = process.env[envKey]\n if (envPath && envPath.trim().length > 0) {\n return path.resolve(envPath)\n }\n return path.join(process.cwd(), 'storage', 'attachments', code)\n}\n\nfunction sanitizeFileName(fileName: string): string {\n if (!fileName) return 'file'\n return fileName.replace(/[^a-zA-Z0-9._-]/g, '_')\n}\n\nfunction resolveOrgSegment(orgId: string | null | undefined): string {\n if (typeof orgId === 'string' && orgId.trim().length > 0) return `org_${orgId}`\n return 'org_shared'\n}\n\nfunction resolveTenantSegment(tenantId: string | null | undefined): string {\n if (typeof tenantId === 'string' && tenantId.trim().length > 0) return `tenant_${tenantId}`\n return 'tenant_shared'\n}\n\n/**\n * @deprecated Use `StorageDriverFactory.resolveForPartition()` + `driver.store()` instead.\n * Kept for backward compatibility with external callers.\n */\nexport type StorePartitionFilePayload = {\n partitionCode: string\n orgId: string | null | undefined\n tenantId: string | null | undefined\n fileName: string\n buffer: Buffer\n}\n\nexport type StoredPartitionFile = {\n storagePath: string\n absolutePath: string\n fileName: string\n}\n\n/**\n * @deprecated Use `StorageDriverFactory.resolveForPartition()` + `driver.store()` instead.\n */\nexport async function storePartitionFile(payload: StorePartitionFilePayload): Promise<StoredPartitionFile> {\n const root = resolvePartitionRoot(payload.partitionCode)\n const orgSegment = resolveOrgSegment(payload.orgId ?? null)\n const tenantSegment = resolveTenantSegment(payload.tenantId ?? null)\n const safeName = sanitizeFileName(payload.fileName || 'file')\n const uniqueSuffix = randomUUID().replace(/-/g, '').slice(0, 12)\n const storedName = `${Date.now()}_${uniqueSuffix}_${safeName}`\n const relativePath = path.join(orgSegment, tenantSegment, storedName)\n const absolutePath = path.join(root, relativePath)\n await fs.mkdir(path.dirname(absolutePath), { recursive: true })\n await fs.writeFile(absolutePath, payload.buffer)\n return {\n storagePath: relativePath.replace(/\\\\/g, '/'),\n absolutePath,\n fileName: storedName,\n }\n}\n\n/**\n * @deprecated Use `StorageDriverFactory.resolveForAttachment()` + `driver.read()` / `driver.toLocalPath()` instead.\n */\nexport function resolveAttachmentAbsolutePath(\n partitionCode: string,\n storagePath: string,\n storageDriver?: string | null\n): string {\n if (storageDriver === 'legacyPublic') {\n return resolveContainedPath(process.cwd(), storagePath, resolveLegacyPublicRoot())\n }\n const root = resolvePartitionRoot(partitionCode)\n return resolveContainedPath(root, storagePath)\n}\n\n/**\n * @deprecated Use `StorageDriverFactory.resolveForAttachment()` + `driver.delete()` instead.\n */\nexport async function deletePartitionFile(\n partitionCode: string,\n storagePath: string,\n storageDriver?: string | null\n): Promise<void> {\n const absolutePath = resolveAttachmentAbsolutePath(partitionCode, storagePath, storageDriver)\n try {\n await fs.unlink(absolutePath)\n } catch {\n // best-effort removal\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAC3B,SAAS,8BAA8B;AACvC,SAAS,sBAAsB,+BAA+B;AAEvD,SAAS,qBAAqB,MAAsB;AACzD,QAAM,SAAS,uBAAuB,IAAI;AAC1C,QAAM,UAAU,QAAQ,IAAI,MAAM;AAClC,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACxC,WAAO,KAAK,QAAQ,OAAO;AAAA,EAC7B;AACA,SAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,WAAW,eAAe,IAAI;AAChE;AAEA,SAAS,iBAAiB,UAA0B;AAClD,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,SAAS,QAAQ,oBAAoB,GAAG;AACjD;AAEA,SAAS,kBAAkB,OAA0C;AACnE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,OAAO,KAAK;AAC7E,SAAO;AACT;AAEA,SAAS,qBAAqB,UAA6C;AACzE,MAAI,OAAO,aAAa,YAAY,SAAS,KAAK,EAAE,SAAS,EAAG,QAAO,UAAU,QAAQ;AACzF,SAAO;AACT;AAuBA,eAAsB,mBAAmB,SAAkE;AACzG,QAAM,OAAO,qBAAqB,QAAQ,aAAa;AACvD,QAAM,aAAa,kBAAkB,QAAQ,SAAS,IAAI;AAC1D,QAAM,gBAAgB,qBAAqB,QAAQ,YAAY,IAAI;AACnE,QAAM,WAAW,iBAAiB,QAAQ,YAAY,MAAM;AAC5D,QAAM,eAAe,WAAW,EAAE,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE;AAC/D,QAAM,aAAa,GAAG,KAAK,IAAI,CAAC,IAAI,YAAY,IAAI,QAAQ;AAC5D,QAAM,eAAe,KAAK,KAAK,YAAY,eAAe,UAAU;AACpE,QAAM,eAAe,KAAK,KAAK,MAAM,YAAY;AACjD,QAAM,GAAG,MAAM,KAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9D,QAAM,GAAG,UAAU,cAAc,QAAQ,MAAM;AAC/C,SAAO;AAAA,IACL,aAAa,aAAa,QAAQ,OAAO,GAAG;AAAA,IAC5C;AAAA,IACA,UAAU;AAAA,EACZ;AACF;AAKO,SAAS,8BACd,eACA,aACA,eACQ;AACR,MAAI,kBAAkB,gBAAgB;AACpC,WAAO,qBAAqB,QAAQ,IAAI,GAAG,aAAa,wBAAwB,CAAC;AAAA,EACnF;AACA,QAAM,OAAO,qBAAqB,aAAa;AAC/C,SAAO,qBAAqB,MAAM,WAAW;AAC/C;AAKA,eAAsB,oBACpB,eACA,aACA,eACe;AACf,QAAM,eAAe,8BAA8B,eAAe,aAAa,aAAa;AAC5F,MAAI;AACF,UAAM,GAAG,OAAO,YAAY;AAAA,EAC9B,QAAQ;AAAA,EAER;AACF;",
6
6
  "names": []
7
7
  }
@@ -13,6 +13,7 @@ import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimi
13
13
  import { useCustomFieldDefs } from "@open-mercato/ui/backend/utils/customFieldDefs";
14
14
  import { applyCustomFieldVisibility } from "@open-mercato/ui/backend/utils/customFieldColumns";
15
15
  import { BooleanIcon } from "@open-mercato/ui/backend/ValueIcons";
16
+ import { markdownToPlainText } from "@open-mercato/ui/backend/markdown/markdownToPlainText";
16
17
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
17
18
  import { useT } from "@open-mercato/shared/lib/i18n/context";
18
19
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
@@ -281,7 +282,7 @@ function ProductsDataTable({
281
282
  "/",
282
283
  row.original.handle
283
284
  ] }) : null,
284
- row.original.description ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: row.original.description }) : null
285
+ row.original.description ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: markdownToPlainText(row.original.description) }) : null
285
286
  ] }),
286
287
  meta: { sticky: true }
287
288
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/catalog/components/products/ProductsDataTable.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { DataTable, type DataTableExportFormat } from '@open-mercato/ui/backend/DataTable'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { deleteCrud, buildCrudExportUrl } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useCustomFieldDefs } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { applyCustomFieldVisibility } from '@open-mercato/ui/backend/utils/customFieldColumns'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport type { FilterOption } from '@open-mercato/ui/backend/FilterOverlay'\nimport { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { E } from '#generated/entities.ids.generated'\nimport { ProductImageCell } from './ProductImageCell'\n\ntype PricingScope = {\n variant_id?: string | null\n offer_id?: string | null\n channel_id?: string | null\n user_id?: string | null\n user_group_id?: string | null\n customer_id?: string | null\n customer_group_id?: string | null\n}\n\ntype PricingInfo = {\n kind?: string | null\n price_kind_id?: string | null\n price_kind_code?: string | null\n currency_code?: string | null\n unit_price_net?: string | null\n unit_price_gross?: string | null\n min_quantity?: number | null\n max_quantity?: number | null\n tax_rate?: string | null\n scope?: PricingScope | null\n} | null\n\ntype OfferInfo = {\n id: string\n channelId: string\n channelName?: string | null\n channelCode?: string | null\n title: string\n description?: string | null\n isActive: boolean\n}\n\nexport type ProductRow = {\n id: string\n title: string\n subtitle?: string | null\n description?: string | null\n sku?: string | null\n handle?: string | null\n product_type?: string | null\n status_entry_id?: string | null\n primary_currency_code?: string | null\n default_unit?: string | null\n default_media_id?: string | null\n default_media_url?: string | null\n is_configurable?: boolean\n is_active?: boolean\n metadata?: Record<string, unknown> | null\n custom_fieldset_code?: string | null\n created_at?: string\n updated_at?: string\n offers?: OfferInfo[]\n pricing?: PricingInfo\n} & Record<string, unknown>\n\ntype ProductsResponse = {\n items?: ProductRow[]\n total?: number\n totalPages?: number\n}\n\nconst PAGE_SIZE = 25\nconst ENTITY_ID = E.catalog.catalog_product\n\nfunction formatDate(value?: string): string {\n if (!value) return '\u2014'\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return '\u2014'\n return date.toLocaleDateString()\n}\n\nfunction renderOffers(offers: OfferInfo[] | undefined): React.ReactNode {\n if (!offers || offers.length === 0) return <span className=\"text-xs text-muted-foreground\">\u2014</span>\n const visible = offers.slice(0, 3)\n return (\n <div className=\"flex flex-wrap gap-1\">\n {visible.map((offer) => {\n const label =\n typeof offer.channelName === 'string' && offer.channelName.trim().length\n ? offer.channelName.trim()\n : typeof offer.title === 'string' && offer.title.trim().length\n ? offer.title.trim()\n : offer.channelId\n const badgeTitle =\n typeof offer.channelCode === 'string' && offer.channelCode.trim().length ? offer.channelCode : undefined\n return (\n <span\n key={offer.id}\n className={`inline-flex items-center rounded-full border px-2 py-0.5 text-xs ${\n offer.isActive ? 'bg-secondary/80 text-secondary-foreground' : 'bg-muted text-muted-foreground'\n }`}\n title={badgeTitle}\n >\n {label}\n </span>\n )\n })}\n {offers.length > visible.length ? (\n <span className=\"text-xs text-muted-foreground\">+{offers.length - visible.length}</span>\n ) : null}\n </div>\n )\n}\n\nfunction renderPrice(pricing: PricingInfo | undefined, currency?: string | null, fallback = '\u2014'): React.ReactNode {\n if (!pricing) return <span className=\"text-xs text-muted-foreground\">{fallback}</span>\n const unit = pricing.unit_price_net ?? pricing.unit_price_gross\n if (unit == null) return <span className=\"text-xs text-muted-foreground\">{fallback}</span>\n const formatted = `${currency ?? pricing.currency_code ?? ''} ${unit}`\n const kind = pricing.kind ?? 'list'\n return (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{formatted.trim()}</span>\n <span className=\"text-xs text-muted-foreground\">{kind}</span>\n </div>\n )\n}\n\nexport type ProductsDataTableSnapshot = {\n search: string\n filterValues: FilterValues\n total: number\n}\n\nexport type ProductsDataTableProps = {\n /**\n * Extra actions rendered alongside the built-in Create button in the\n * DataTable header. Used by the Step 4.9 AI merchandising sheet\n * trigger without coupling DataTable to the AI module.\n */\n extraActions?: React.ReactNode\n /**\n * Optional callback invoked whenever the table's search / filter /\n * total-matching snapshot changes. Used by the Step 4.9 AI merchandising\n * sheet to form a selection-aware pageContext per spec \u00A710.1.\n */\n onSnapshotChange?: (snapshot: ProductsDataTableSnapshot) => void\n}\n\nexport default function ProductsDataTable({\n extraActions,\n onSnapshotChange,\n}: ProductsDataTableProps = {}) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const scopeVersion = useOrganizationScopeVersion()\n const [rows, setRows] = React.useState<ProductRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [cacheStatus, setCacheStatus] = React.useState<'hit' | 'miss' | null>(null)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'title', desc: false }])\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(false)\n const [reloadToken, setReloadToken] = React.useState(0)\n // Step 5.18 (spec \u00A710 line 836, D18 demo): refresh the list when a\n // catalog.product.* event arrives via the DOM event bridge. Confirmed\n // AI bulk mutations (one `ai.action.confirmed` + one\n // `catalog.product.updated` per record) and direct API writes both\n // surface here so the table reflects the new state without a manual\n // reload.\n useAppEvent('catalog.product.*', () => {\n setReloadToken((token) => token + 1)\n })\n const [customFieldsetFilter, setCustomFieldsetFilter] = React.useState<string | null>(null)\n const { data: customFieldDefs = [] } = useCustomFieldDefs(ENTITY_ID, {\n keyExtras: [scopeVersion, reloadToken],\n })\n const [channelOptionsCache, setChannelOptionsCache] = React.useState<Record<string, FilterOption>>({})\n const [categoryOptionsCache, setCategoryOptionsCache] = React.useState<Record<string, FilterOption>>({})\n const [tagOptionsCache, setTagOptionsCache] = React.useState<Record<string, FilterOption>>({})\n\n const registerOptions = React.useCallback(\n (\n setter: React.Dispatch<React.SetStateAction<Record<string, FilterOption>>>,\n options: FilterOption[]\n ) => {\n setter((prev) => {\n const next = { ...prev }\n options.forEach((opt) => {\n if (opt.value) next[opt.value] = opt\n })\n return next\n })\n },\n []\n )\n\n const registerChannelOptions = React.useCallback(\n (options: FilterOption[]) => registerOptions(setChannelOptionsCache, options),\n [registerOptions]\n )\n const registerCategoryOptions = React.useCallback(\n (options: FilterOption[]) => registerOptions(setCategoryOptionsCache, options),\n [registerOptions]\n )\n const registerTagOptions = React.useCallback(\n (options: FilterOption[]) => registerOptions(setTagOptionsCache, options),\n [registerOptions]\n )\n\n const channelOptions = React.useMemo(() => Object.values(channelOptionsCache), [channelOptionsCache])\n const categoryOptions = React.useMemo(() => Object.values(categoryOptionsCache), [categoryOptionsCache])\n const tagOptions = React.useMemo(() => Object.values(tagOptionsCache), [tagOptionsCache])\n\n const loadChannelOptions = React.useCallback(\n async (term?: string): Promise<FilterOption[]> => {\n try {\n const params = new URLSearchParams({ pageSize: '100', isActive: 'true' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; name?: string; code?: string }> }>(\n `/api/sales/channels?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.channelsLoadError', 'Failed to load channels') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label =\n typeof entry.name === 'string'\n ? entry.name\n : typeof entry.code === 'string'\n ? entry.code\n : value\n return { value, label, description: typeof entry.code === 'string' ? entry.code : undefined }\n })\n .filter((option) => !!option) as FilterOption[]\n registerChannelOptions(options)\n return options\n } catch {\n return []\n }\n },\n [registerChannelOptions, t],\n )\n\n const loadCategoryOptions = React.useCallback(\n async (term?: string): Promise<FilterOption[]> => {\n try {\n const params = new URLSearchParams({ pageSize: '200', view: 'manage' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; name?: string; parentName?: string | null }> }>(\n `/api/catalog/categories?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.categoriesLoadError', 'Failed to load categories') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label = typeof entry.name === 'string' && entry.name.trim().length ? entry.name : value\n const description =\n typeof entry.parentName === 'string' && entry.parentName.trim().length ? entry.parentName : null\n return { value, label, description }\n })\n .filter((option) => !!option) as FilterOption[]\n registerCategoryOptions(options)\n return options\n } catch {\n return []\n }\n },\n [registerCategoryOptions, t],\n )\n\n const loadTagOptions = React.useCallback(\n async (term?: string): Promise<FilterOption[]> => {\n try {\n const params = new URLSearchParams({ pageSize: '100' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; label?: string }> }>(\n `/api/catalog/tags?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.tagsLoadError', 'Failed to load tags') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label = typeof entry.label === 'string' && entry.label.trim().length ? entry.label : value\n return { value, label }\n })\n .filter((option) => !!option) as FilterOption[]\n registerTagOptions(options)\n return options\n } catch {\n return []\n }\n },\n [registerTagOptions, t],\n )\n\n const productTypeOptions = React.useMemo<FilterOption[]>(() => [\n { value: 'simple', label: t('catalog.products.types.simple', 'Simple') },\n { value: 'configurable', label: t('catalog.products.types.configurable', 'Configurable') },\n { value: 'virtual', label: t('catalog.products.types.virtual', 'Virtual') },\n { value: 'downloadable', label: t('catalog.products.types.downloadable', 'Downloadable') },\n {\n value: 'bundle',\n label: `${t('catalog.products.types.bundle', 'Bundle')} (${t('common.comingSoon', 'Coming soon')})`,\n },\n {\n value: 'grouped',\n label: `${t('catalog.products.types.grouped', 'Grouped')} (${t('common.comingSoon', 'Coming soon')})`,\n },\n ], [t])\n\n const productTypeLabelMap = React.useMemo(() => {\n const map = new Map<string, string>()\n productTypeOptions.forEach((opt) => map.set(opt.value, opt.label))\n return map\n }, [productTypeOptions])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n { id: 'status', label: t('catalog.products.filters.status'), type: 'text' },\n { id: 'isActive', label: t('catalog.products.filters.active'), type: 'checkbox' },\n { id: 'configurable', label: t('catalog.products.filters.configurable'), type: 'checkbox' },\n { id: 'productType', label: t('catalog.products.filters.productType', 'Type'), type: 'select', options: productTypeOptions },\n {\n id: 'channelIds',\n label: t('catalog.products.filters.channels'),\n type: 'tags',\n loadOptions: loadChannelOptions,\n options: channelOptions,\n formatValue: (val) => channelOptionsCache[val]?.label ?? val,\n formatDescription: (val) => channelOptionsCache[val]?.description ?? null,\n },\n {\n id: 'categoryIds',\n label: t('catalog.products.filters.categories', 'Categories'),\n type: 'tags',\n loadOptions: loadCategoryOptions,\n options: categoryOptions,\n formatValue: (val) => categoryOptionsCache[val]?.label ?? val,\n formatDescription: (val) => categoryOptionsCache[val]?.description ?? null,\n },\n {\n id: 'tagIds',\n label: t('catalog.products.filters.tags', 'Tags'),\n type: 'tags',\n loadOptions: loadTagOptions,\n options: tagOptions,\n formatValue: (val) => tagOptionsCache[val]?.label ?? val,\n },\n ], [\n categoryOptions,\n categoryOptionsCache,\n channelOptions,\n channelOptionsCache,\n loadCategoryOptions,\n loadChannelOptions,\n loadTagOptions,\n productTypeOptions,\n tagOptions,\n tagOptionsCache,\n t,\n ])\n\n const columns = React.useMemo<ColumnDef<ProductRow>[]>(() => {\n const base: ColumnDef<ProductRow>[] = [\n {\n id: 'media',\n header: '',\n size: 80,\n cell: ({ row }) => (\n <ProductImageCell\n mediaId={row.original.default_media_id}\n mediaUrl={row.original.default_media_url}\n title={row.original.title}\n cropType=\"contain\"\n />\n ),\n meta: { sticky: true },\n },\n {\n accessorKey: 'title',\n header: t('catalog.products.table.title', 'Title'),\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title || '\u2014'}</span>\n {row.original.subtitle ? (\n <span className=\"text-xs text-muted-foreground\">{row.original.subtitle}</span>\n ) : null}\n {row.original.handle ? (\n <span className=\"text-xs text-muted-foreground\">/{row.original.handle}</span>\n ) : null}\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground\">{row.original.description}</span>\n ) : null}\n </div>\n ),\n meta: { sticky: true },\n },\n {\n accessorKey: 'sku',\n header: t('catalog.products.table.sku', 'SKU'),\n cell: ({ getValue }) => {\n const value = getValue()\n return value ? <span className=\"font-mono text-xs\">{String(value)}</span> : <span className=\"text-xs text-muted-foreground\">\u2014</span>\n },\n },\n {\n accessorKey: 'product_type',\n header: t('catalog.products.table.type'),\n cell: ({ row }) => {\n const type = typeof row.original.product_type === 'string' ? row.original.product_type : 'simple'\n const label = productTypeLabelMap.get(type) ?? type\n return <span className=\"text-xs text-muted-foreground\">{label}</span>\n },\n },\n {\n accessorKey: 'is_configurable',\n header: t('catalog.products.table.configurable'),\n cell: ({ row }) => <BooleanIcon value={!!row.original.is_configurable} />,\n },\n {\n accessorKey: 'is_active',\n header: t('catalog.products.table.active'),\n cell: ({ row }) => <BooleanIcon value={!!row.original.is_active} />,\n },\n {\n accessorKey: 'pricing',\n header: t('catalog.products.table.price'),\n cell: ({ row }) => renderPrice(row.original.pricing, row.original.primary_currency_code),\n },\n {\n accessorKey: 'offers',\n header: t('catalog.products.table.channels'),\n cell: ({ row }) => renderOffers(row.original.offers),\n },\n {\n accessorKey: 'updated_at',\n header: t('catalog.products.table.updatedAt'),\n cell: ({ row }) => <span className=\"text-xs text-muted-foreground\">{formatDate(row.original.updated_at)}</span>,\n },\n ]\n return applyCustomFieldVisibility(base, customFieldDefs)\n }, [customFieldDefs, productTypeLabelMap, t])\n\n const handleSearchChange = React.useCallback((value: string) => {\n setSearch(value)\n setPage(1)\n }, [])\n\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n setFilterValues(values)\n setPage(1)\n }, [])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n }, [])\n\n const handleCustomFieldsetFilterChange = React.useCallback(\n (value: string | null) => {\n if (value === customFieldsetFilter) return\n setCustomFieldsetFilter(value)\n setFilterValues((prev) => {\n const entries = Object.entries(prev)\n if (!entries.some(([key]) => key.startsWith('cf_'))) return prev\n const next: FilterValues = {}\n entries.forEach(([key, val]) => {\n if (!key.startsWith('cf_')) next[key] = val\n })\n return next\n })\n setPage(1)\n },\n [customFieldsetFilter],\n )\n\n const handleRefresh = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const queryParams = React.useMemo(() => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(PAGE_SIZE))\n if (search.trim()) params.set('search', search.trim())\n const sort = sorting[0]\n if (sort?.id) {\n params.set('sortField', sort.id)\n params.set('sortDir', sort.desc ? 'desc' : 'asc')\n }\n const status = filterValues.status\n if (typeof status === 'string' && status.trim()) {\n params.set('status', status.trim())\n }\n if (filterValues.isActive === true) params.set('isActive', 'true')\n if (filterValues.isActive === false) params.set('isActive', 'false')\n if (filterValues.configurable === true) params.set('configurable', 'true')\n if (filterValues.configurable === false) params.set('configurable', 'false')\n if (typeof filterValues.productType === 'string' && filterValues.productType.trim()) {\n params.set('productType', filterValues.productType.trim())\n }\n if (Array.isArray(filterValues.channelIds) && filterValues.channelIds.length) {\n const values = filterValues.channelIds\n .map((value) => (typeof value === 'string' ? value : null))\n .filter((value): value is string => !!value)\n if (values.length) params.set('channelIds', values.join(','))\n }\n if (Array.isArray(filterValues.categoryIds) && filterValues.categoryIds.length) {\n const values = filterValues.categoryIds\n .map((value) => (typeof value === 'string' ? value : null))\n .filter((value): value is string => !!value)\n if (values.length) params.set('categoryIds', values.join(','))\n }\n if (Array.isArray(filterValues.tagIds) && filterValues.tagIds.length) {\n const values = filterValues.tagIds\n .map((value) => (typeof value === 'string' ? value : null))\n .filter((value): value is string => !!value)\n if (values.length) params.set('tagIds', values.join(','))\n }\n Object.entries(filterValues).forEach(([key, value]) => {\n if (!key.startsWith('cf_') || value == null) return\n if (Array.isArray(value)) {\n const entries = value\n .map((entry) => (typeof entry === 'string' ? entry.trim() : String(entry || '').trim()))\n .filter((entry) => entry.length > 0)\n if (entries.length) params.set(key, entries.join(','))\n } else if (typeof value === 'object' && value !== null && ('from' in (value as Record<string, unknown>) || 'to' in (value as Record<string, unknown>))) {\n const range = value as { from?: string; to?: string }\n if (typeof range.from === 'string' && range.from.trim().length) {\n params.set(`${key}:from`, range.from.trim())\n }\n if (typeof range.to === 'string' && range.to.trim().length) {\n params.set(`${key}:to`, range.to.trim())\n }\n } else if (typeof value === 'string' && value.trim()) {\n params.set(key, value.trim())\n }\n })\n if (typeof customFieldsetFilter === 'string' && customFieldsetFilter.trim().length > 0) {\n params.set('customFieldset', customFieldsetFilter.trim())\n }\n return params.toString()\n }, [customFieldsetFilter, filterValues, page, search, sorting])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setCacheStatus(null)\n try {\n const fallback: ProductsResponse = { items: [], total: 0, totalPages: 1 }\n const call = await apiCall<ProductsResponse>(\n `/api/catalog/products?${queryParams}`,\n undefined,\n { fallback },\n )\n if (!call.ok) {\n const message = t('catalog.products.list.error.load', 'Failed to load products')\n flash(message, 'error')\n if (!cancelled) setCacheStatus(null)\n return\n }\n const payload = call.result ?? fallback\n if (cancelled) return\n setCacheStatus(call.cacheStatus ?? null)\n const items = Array.isArray(payload.items) ? payload.items : []\n const normalized = items.filter((item): item is ProductRow => typeof item?.id === 'string')\n setRows(normalized)\n setTotal(typeof payload.total === 'number' ? payload.total : normalized.length)\n setTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : 1)\n } catch (error) {\n if (!cancelled) {\n setCacheStatus(null)\n const message =\n error instanceof Error\n ? error.message\n : t('catalog.products.list.error.load', 'Failed to load products')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => {\n cancelled = true\n }\n }, [queryParams, reloadToken, scopeVersion, t])\n\n const handleDelete = React.useCallback(async (row: ProductRow) => {\n const confirmed = await confirm({\n title: t('catalog.products.list.deleteConfirm', 'Delete this product?'),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const headers = buildOptimisticLockHeader(typeof row.updated_at === 'string' ? row.updated_at : null)\n await withScopedApiRequestHeaders(headers, () => (\n deleteCrud('catalog/products', row.id, {\n errorMessage: t('catalog.products.list.error.delete', 'Failed to delete product'),\n })\n ))\n flash(t('catalog.products.flash.deleted', 'Product deleted'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('catalog.products.list.error.delete', 'Failed to delete product')\n flash(message, 'error')\n }\n }, [confirm, t])\n\n React.useEffect(() => {\n if (!onSnapshotChange) return\n onSnapshotChange({ search, filterValues, total })\n }, [onSnapshotChange, search, filterValues, total])\n\n const currentParams = React.useMemo(() => Object.fromEntries(new URLSearchParams(queryParams)), [queryParams])\n\n const exportConfig = React.useMemo(() => ({\n view: {\n getUrl: (format: DataTableExportFormat) =>\n buildCrudExportUrl('catalog/products', { ...currentParams, exportScope: 'view' }, format),\n },\n full: {\n getUrl: (format: DataTableExportFormat) =>\n buildCrudExportUrl('catalog/products', { ...currentParams, exportScope: 'full', all: 'true' }, format),\n },\n }), [currentParams])\n\n return (\n <>\n <DataTable<ProductRow>\n title={t('catalog.products.page.title', 'Products & services')}\n entityId={ENTITY_ID}\n customFieldFilterKeyExtras={[scopeVersion, reloadToken]}\n refreshButton={{\n label: t('catalog.products.actions.refresh', 'Refresh'),\n onRefresh: handleRefresh,\n isRefreshing: isLoading,\n }}\n actions={(\n <div className=\"flex items-center gap-2\">\n {extraActions}\n <Button asChild>\n <Link href=\"/backend/catalog/products/create\">\n {t('catalog.products.actions.create', 'Create')}\n </Link>\n </Button>\n </div>\n )}\n columns={columns}\n data={rows}\n emptyState={(\n <ListEmptyState\n entityName={t('catalog.products.page.title', 'Products & services')}\n createHref=\"/backend/catalog/products/create\"\n createLabel={t('catalog.products.actions.create', 'Create')}\n />\n )}\n searchValue={search}\n onSearchChange={handleSearchChange}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n onCustomFieldFilterFieldsetChange={handleCustomFieldsetFilterChange}\n sorting={sorting}\n onSortingChange={setSorting}\n injectionSpotId=\"data-table:catalog.products\"\n injectionContext={{\n search,\n filters: filterValues,\n customFieldset: customFieldsetFilter,\n page,\n sorting,\n scopeVersion,\n // Step 5.15: surface `total` so the merchandising AI widget\n // (rendered in `data-table:catalog.products:header`) can build\n // a selection-aware pageContext per spec \u00A710.1 without taking a\n // dependency on the host page.\n total,\n totalMatching: total,\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n cacheStatus,\n }}\n exporter={exportConfig}\n isLoading={isLoading}\n perspective={{ tableId: 'catalog.products.list' }}\n stickyActionsColumn\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('catalog.products.table.actions.edit', 'Edit'),\n href: `/backend/catalog/products/${row.id}`,\n },\n {\n id: 'delete',\n label: t('catalog.products.table.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => {\n void handleDelete(row)\n },\n },\n ]}\n />\n )}\n />\n {ConfirmDialogElement}\n </>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { DataTable, type DataTableExportFormat } from '@open-mercato/ui/backend/DataTable'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { deleteCrud, buildCrudExportUrl } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useCustomFieldDefs } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { applyCustomFieldVisibility } from '@open-mercato/ui/backend/utils/customFieldColumns'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport type { FilterOption } from '@open-mercato/ui/backend/FilterOverlay'\nimport { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'\nimport { markdownToPlainText } from '@open-mercato/ui/backend/markdown/markdownToPlainText'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { E } from '#generated/entities.ids.generated'\nimport { ProductImageCell } from './ProductImageCell'\n\ntype PricingScope = {\n variant_id?: string | null\n offer_id?: string | null\n channel_id?: string | null\n user_id?: string | null\n user_group_id?: string | null\n customer_id?: string | null\n customer_group_id?: string | null\n}\n\ntype PricingInfo = {\n kind?: string | null\n price_kind_id?: string | null\n price_kind_code?: string | null\n currency_code?: string | null\n unit_price_net?: string | null\n unit_price_gross?: string | null\n min_quantity?: number | null\n max_quantity?: number | null\n tax_rate?: string | null\n scope?: PricingScope | null\n} | null\n\ntype OfferInfo = {\n id: string\n channelId: string\n channelName?: string | null\n channelCode?: string | null\n title: string\n description?: string | null\n isActive: boolean\n}\n\nexport type ProductRow = {\n id: string\n title: string\n subtitle?: string | null\n description?: string | null\n sku?: string | null\n handle?: string | null\n product_type?: string | null\n status_entry_id?: string | null\n primary_currency_code?: string | null\n default_unit?: string | null\n default_media_id?: string | null\n default_media_url?: string | null\n is_configurable?: boolean\n is_active?: boolean\n metadata?: Record<string, unknown> | null\n custom_fieldset_code?: string | null\n created_at?: string\n updated_at?: string\n offers?: OfferInfo[]\n pricing?: PricingInfo\n} & Record<string, unknown>\n\ntype ProductsResponse = {\n items?: ProductRow[]\n total?: number\n totalPages?: number\n}\n\nconst PAGE_SIZE = 25\nconst ENTITY_ID = E.catalog.catalog_product\n\nfunction formatDate(value?: string): string {\n if (!value) return '\u2014'\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return '\u2014'\n return date.toLocaleDateString()\n}\n\nfunction renderOffers(offers: OfferInfo[] | undefined): React.ReactNode {\n if (!offers || offers.length === 0) return <span className=\"text-xs text-muted-foreground\">\u2014</span>\n const visible = offers.slice(0, 3)\n return (\n <div className=\"flex flex-wrap gap-1\">\n {visible.map((offer) => {\n const label =\n typeof offer.channelName === 'string' && offer.channelName.trim().length\n ? offer.channelName.trim()\n : typeof offer.title === 'string' && offer.title.trim().length\n ? offer.title.trim()\n : offer.channelId\n const badgeTitle =\n typeof offer.channelCode === 'string' && offer.channelCode.trim().length ? offer.channelCode : undefined\n return (\n <span\n key={offer.id}\n className={`inline-flex items-center rounded-full border px-2 py-0.5 text-xs ${\n offer.isActive ? 'bg-secondary/80 text-secondary-foreground' : 'bg-muted text-muted-foreground'\n }`}\n title={badgeTitle}\n >\n {label}\n </span>\n )\n })}\n {offers.length > visible.length ? (\n <span className=\"text-xs text-muted-foreground\">+{offers.length - visible.length}</span>\n ) : null}\n </div>\n )\n}\n\nfunction renderPrice(pricing: PricingInfo | undefined, currency?: string | null, fallback = '\u2014'): React.ReactNode {\n if (!pricing) return <span className=\"text-xs text-muted-foreground\">{fallback}</span>\n const unit = pricing.unit_price_net ?? pricing.unit_price_gross\n if (unit == null) return <span className=\"text-xs text-muted-foreground\">{fallback}</span>\n const formatted = `${currency ?? pricing.currency_code ?? ''} ${unit}`\n const kind = pricing.kind ?? 'list'\n return (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{formatted.trim()}</span>\n <span className=\"text-xs text-muted-foreground\">{kind}</span>\n </div>\n )\n}\n\nexport type ProductsDataTableSnapshot = {\n search: string\n filterValues: FilterValues\n total: number\n}\n\nexport type ProductsDataTableProps = {\n /**\n * Extra actions rendered alongside the built-in Create button in the\n * DataTable header. Used by the Step 4.9 AI merchandising sheet\n * trigger without coupling DataTable to the AI module.\n */\n extraActions?: React.ReactNode\n /**\n * Optional callback invoked whenever the table's search / filter /\n * total-matching snapshot changes. Used by the Step 4.9 AI merchandising\n * sheet to form a selection-aware pageContext per spec \u00A710.1.\n */\n onSnapshotChange?: (snapshot: ProductsDataTableSnapshot) => void\n}\n\nexport default function ProductsDataTable({\n extraActions,\n onSnapshotChange,\n}: ProductsDataTableProps = {}) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const scopeVersion = useOrganizationScopeVersion()\n const [rows, setRows] = React.useState<ProductRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [cacheStatus, setCacheStatus] = React.useState<'hit' | 'miss' | null>(null)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'title', desc: false }])\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(false)\n const [reloadToken, setReloadToken] = React.useState(0)\n // Step 5.18 (spec \u00A710 line 836, D18 demo): refresh the list when a\n // catalog.product.* event arrives via the DOM event bridge. Confirmed\n // AI bulk mutations (one `ai.action.confirmed` + one\n // `catalog.product.updated` per record) and direct API writes both\n // surface here so the table reflects the new state without a manual\n // reload.\n useAppEvent('catalog.product.*', () => {\n setReloadToken((token) => token + 1)\n })\n const [customFieldsetFilter, setCustomFieldsetFilter] = React.useState<string | null>(null)\n const { data: customFieldDefs = [] } = useCustomFieldDefs(ENTITY_ID, {\n keyExtras: [scopeVersion, reloadToken],\n })\n const [channelOptionsCache, setChannelOptionsCache] = React.useState<Record<string, FilterOption>>({})\n const [categoryOptionsCache, setCategoryOptionsCache] = React.useState<Record<string, FilterOption>>({})\n const [tagOptionsCache, setTagOptionsCache] = React.useState<Record<string, FilterOption>>({})\n\n const registerOptions = React.useCallback(\n (\n setter: React.Dispatch<React.SetStateAction<Record<string, FilterOption>>>,\n options: FilterOption[]\n ) => {\n setter((prev) => {\n const next = { ...prev }\n options.forEach((opt) => {\n if (opt.value) next[opt.value] = opt\n })\n return next\n })\n },\n []\n )\n\n const registerChannelOptions = React.useCallback(\n (options: FilterOption[]) => registerOptions(setChannelOptionsCache, options),\n [registerOptions]\n )\n const registerCategoryOptions = React.useCallback(\n (options: FilterOption[]) => registerOptions(setCategoryOptionsCache, options),\n [registerOptions]\n )\n const registerTagOptions = React.useCallback(\n (options: FilterOption[]) => registerOptions(setTagOptionsCache, options),\n [registerOptions]\n )\n\n const channelOptions = React.useMemo(() => Object.values(channelOptionsCache), [channelOptionsCache])\n const categoryOptions = React.useMemo(() => Object.values(categoryOptionsCache), [categoryOptionsCache])\n const tagOptions = React.useMemo(() => Object.values(tagOptionsCache), [tagOptionsCache])\n\n const loadChannelOptions = React.useCallback(\n async (term?: string): Promise<FilterOption[]> => {\n try {\n const params = new URLSearchParams({ pageSize: '100', isActive: 'true' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; name?: string; code?: string }> }>(\n `/api/sales/channels?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.channelsLoadError', 'Failed to load channels') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label =\n typeof entry.name === 'string'\n ? entry.name\n : typeof entry.code === 'string'\n ? entry.code\n : value\n return { value, label, description: typeof entry.code === 'string' ? entry.code : undefined }\n })\n .filter((option) => !!option) as FilterOption[]\n registerChannelOptions(options)\n return options\n } catch {\n return []\n }\n },\n [registerChannelOptions, t],\n )\n\n const loadCategoryOptions = React.useCallback(\n async (term?: string): Promise<FilterOption[]> => {\n try {\n const params = new URLSearchParams({ pageSize: '200', view: 'manage' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; name?: string; parentName?: string | null }> }>(\n `/api/catalog/categories?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.categoriesLoadError', 'Failed to load categories') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label = typeof entry.name === 'string' && entry.name.trim().length ? entry.name : value\n const description =\n typeof entry.parentName === 'string' && entry.parentName.trim().length ? entry.parentName : null\n return { value, label, description }\n })\n .filter((option) => !!option) as FilterOption[]\n registerCategoryOptions(options)\n return options\n } catch {\n return []\n }\n },\n [registerCategoryOptions, t],\n )\n\n const loadTagOptions = React.useCallback(\n async (term?: string): Promise<FilterOption[]> => {\n try {\n const params = new URLSearchParams({ pageSize: '100' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; label?: string }> }>(\n `/api/catalog/tags?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.tagsLoadError', 'Failed to load tags') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label = typeof entry.label === 'string' && entry.label.trim().length ? entry.label : value\n return { value, label }\n })\n .filter((option) => !!option) as FilterOption[]\n registerTagOptions(options)\n return options\n } catch {\n return []\n }\n },\n [registerTagOptions, t],\n )\n\n const productTypeOptions = React.useMemo<FilterOption[]>(() => [\n { value: 'simple', label: t('catalog.products.types.simple', 'Simple') },\n { value: 'configurable', label: t('catalog.products.types.configurable', 'Configurable') },\n { value: 'virtual', label: t('catalog.products.types.virtual', 'Virtual') },\n { value: 'downloadable', label: t('catalog.products.types.downloadable', 'Downloadable') },\n {\n value: 'bundle',\n label: `${t('catalog.products.types.bundle', 'Bundle')} (${t('common.comingSoon', 'Coming soon')})`,\n },\n {\n value: 'grouped',\n label: `${t('catalog.products.types.grouped', 'Grouped')} (${t('common.comingSoon', 'Coming soon')})`,\n },\n ], [t])\n\n const productTypeLabelMap = React.useMemo(() => {\n const map = new Map<string, string>()\n productTypeOptions.forEach((opt) => map.set(opt.value, opt.label))\n return map\n }, [productTypeOptions])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n { id: 'status', label: t('catalog.products.filters.status'), type: 'text' },\n { id: 'isActive', label: t('catalog.products.filters.active'), type: 'checkbox' },\n { id: 'configurable', label: t('catalog.products.filters.configurable'), type: 'checkbox' },\n { id: 'productType', label: t('catalog.products.filters.productType', 'Type'), type: 'select', options: productTypeOptions },\n {\n id: 'channelIds',\n label: t('catalog.products.filters.channels'),\n type: 'tags',\n loadOptions: loadChannelOptions,\n options: channelOptions,\n formatValue: (val) => channelOptionsCache[val]?.label ?? val,\n formatDescription: (val) => channelOptionsCache[val]?.description ?? null,\n },\n {\n id: 'categoryIds',\n label: t('catalog.products.filters.categories', 'Categories'),\n type: 'tags',\n loadOptions: loadCategoryOptions,\n options: categoryOptions,\n formatValue: (val) => categoryOptionsCache[val]?.label ?? val,\n formatDescription: (val) => categoryOptionsCache[val]?.description ?? null,\n },\n {\n id: 'tagIds',\n label: t('catalog.products.filters.tags', 'Tags'),\n type: 'tags',\n loadOptions: loadTagOptions,\n options: tagOptions,\n formatValue: (val) => tagOptionsCache[val]?.label ?? val,\n },\n ], [\n categoryOptions,\n categoryOptionsCache,\n channelOptions,\n channelOptionsCache,\n loadCategoryOptions,\n loadChannelOptions,\n loadTagOptions,\n productTypeOptions,\n tagOptions,\n tagOptionsCache,\n t,\n ])\n\n const columns = React.useMemo<ColumnDef<ProductRow>[]>(() => {\n const base: ColumnDef<ProductRow>[] = [\n {\n id: 'media',\n header: '',\n size: 80,\n cell: ({ row }) => (\n <ProductImageCell\n mediaId={row.original.default_media_id}\n mediaUrl={row.original.default_media_url}\n title={row.original.title}\n cropType=\"contain\"\n />\n ),\n meta: { sticky: true },\n },\n {\n accessorKey: 'title',\n header: t('catalog.products.table.title', 'Title'),\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title || '\u2014'}</span>\n {row.original.subtitle ? (\n <span className=\"text-xs text-muted-foreground\">{row.original.subtitle}</span>\n ) : null}\n {row.original.handle ? (\n <span className=\"text-xs text-muted-foreground\">/{row.original.handle}</span>\n ) : null}\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground\">{markdownToPlainText(row.original.description)}</span>\n ) : null}\n </div>\n ),\n meta: { sticky: true },\n },\n {\n accessorKey: 'sku',\n header: t('catalog.products.table.sku', 'SKU'),\n cell: ({ getValue }) => {\n const value = getValue()\n return value ? <span className=\"font-mono text-xs\">{String(value)}</span> : <span className=\"text-xs text-muted-foreground\">\u2014</span>\n },\n },\n {\n accessorKey: 'product_type',\n header: t('catalog.products.table.type'),\n cell: ({ row }) => {\n const type = typeof row.original.product_type === 'string' ? row.original.product_type : 'simple'\n const label = productTypeLabelMap.get(type) ?? type\n return <span className=\"text-xs text-muted-foreground\">{label}</span>\n },\n },\n {\n accessorKey: 'is_configurable',\n header: t('catalog.products.table.configurable'),\n cell: ({ row }) => <BooleanIcon value={!!row.original.is_configurable} />,\n },\n {\n accessorKey: 'is_active',\n header: t('catalog.products.table.active'),\n cell: ({ row }) => <BooleanIcon value={!!row.original.is_active} />,\n },\n {\n accessorKey: 'pricing',\n header: t('catalog.products.table.price'),\n cell: ({ row }) => renderPrice(row.original.pricing, row.original.primary_currency_code),\n },\n {\n accessorKey: 'offers',\n header: t('catalog.products.table.channels'),\n cell: ({ row }) => renderOffers(row.original.offers),\n },\n {\n accessorKey: 'updated_at',\n header: t('catalog.products.table.updatedAt'),\n cell: ({ row }) => <span className=\"text-xs text-muted-foreground\">{formatDate(row.original.updated_at)}</span>,\n },\n ]\n return applyCustomFieldVisibility(base, customFieldDefs)\n }, [customFieldDefs, productTypeLabelMap, t])\n\n const handleSearchChange = React.useCallback((value: string) => {\n setSearch(value)\n setPage(1)\n }, [])\n\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n setFilterValues(values)\n setPage(1)\n }, [])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n }, [])\n\n const handleCustomFieldsetFilterChange = React.useCallback(\n (value: string | null) => {\n if (value === customFieldsetFilter) return\n setCustomFieldsetFilter(value)\n setFilterValues((prev) => {\n const entries = Object.entries(prev)\n if (!entries.some(([key]) => key.startsWith('cf_'))) return prev\n const next: FilterValues = {}\n entries.forEach(([key, val]) => {\n if (!key.startsWith('cf_')) next[key] = val\n })\n return next\n })\n setPage(1)\n },\n [customFieldsetFilter],\n )\n\n const handleRefresh = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const queryParams = React.useMemo(() => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(PAGE_SIZE))\n if (search.trim()) params.set('search', search.trim())\n const sort = sorting[0]\n if (sort?.id) {\n params.set('sortField', sort.id)\n params.set('sortDir', sort.desc ? 'desc' : 'asc')\n }\n const status = filterValues.status\n if (typeof status === 'string' && status.trim()) {\n params.set('status', status.trim())\n }\n if (filterValues.isActive === true) params.set('isActive', 'true')\n if (filterValues.isActive === false) params.set('isActive', 'false')\n if (filterValues.configurable === true) params.set('configurable', 'true')\n if (filterValues.configurable === false) params.set('configurable', 'false')\n if (typeof filterValues.productType === 'string' && filterValues.productType.trim()) {\n params.set('productType', filterValues.productType.trim())\n }\n if (Array.isArray(filterValues.channelIds) && filterValues.channelIds.length) {\n const values = filterValues.channelIds\n .map((value) => (typeof value === 'string' ? value : null))\n .filter((value): value is string => !!value)\n if (values.length) params.set('channelIds', values.join(','))\n }\n if (Array.isArray(filterValues.categoryIds) && filterValues.categoryIds.length) {\n const values = filterValues.categoryIds\n .map((value) => (typeof value === 'string' ? value : null))\n .filter((value): value is string => !!value)\n if (values.length) params.set('categoryIds', values.join(','))\n }\n if (Array.isArray(filterValues.tagIds) && filterValues.tagIds.length) {\n const values = filterValues.tagIds\n .map((value) => (typeof value === 'string' ? value : null))\n .filter((value): value is string => !!value)\n if (values.length) params.set('tagIds', values.join(','))\n }\n Object.entries(filterValues).forEach(([key, value]) => {\n if (!key.startsWith('cf_') || value == null) return\n if (Array.isArray(value)) {\n const entries = value\n .map((entry) => (typeof entry === 'string' ? entry.trim() : String(entry || '').trim()))\n .filter((entry) => entry.length > 0)\n if (entries.length) params.set(key, entries.join(','))\n } else if (typeof value === 'object' && value !== null && ('from' in (value as Record<string, unknown>) || 'to' in (value as Record<string, unknown>))) {\n const range = value as { from?: string; to?: string }\n if (typeof range.from === 'string' && range.from.trim().length) {\n params.set(`${key}:from`, range.from.trim())\n }\n if (typeof range.to === 'string' && range.to.trim().length) {\n params.set(`${key}:to`, range.to.trim())\n }\n } else if (typeof value === 'string' && value.trim()) {\n params.set(key, value.trim())\n }\n })\n if (typeof customFieldsetFilter === 'string' && customFieldsetFilter.trim().length > 0) {\n params.set('customFieldset', customFieldsetFilter.trim())\n }\n return params.toString()\n }, [customFieldsetFilter, filterValues, page, search, sorting])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setCacheStatus(null)\n try {\n const fallback: ProductsResponse = { items: [], total: 0, totalPages: 1 }\n const call = await apiCall<ProductsResponse>(\n `/api/catalog/products?${queryParams}`,\n undefined,\n { fallback },\n )\n if (!call.ok) {\n const message = t('catalog.products.list.error.load', 'Failed to load products')\n flash(message, 'error')\n if (!cancelled) setCacheStatus(null)\n return\n }\n const payload = call.result ?? fallback\n if (cancelled) return\n setCacheStatus(call.cacheStatus ?? null)\n const items = Array.isArray(payload.items) ? payload.items : []\n const normalized = items.filter((item): item is ProductRow => typeof item?.id === 'string')\n setRows(normalized)\n setTotal(typeof payload.total === 'number' ? payload.total : normalized.length)\n setTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : 1)\n } catch (error) {\n if (!cancelled) {\n setCacheStatus(null)\n const message =\n error instanceof Error\n ? error.message\n : t('catalog.products.list.error.load', 'Failed to load products')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => {\n cancelled = true\n }\n }, [queryParams, reloadToken, scopeVersion, t])\n\n const handleDelete = React.useCallback(async (row: ProductRow) => {\n const confirmed = await confirm({\n title: t('catalog.products.list.deleteConfirm', 'Delete this product?'),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const headers = buildOptimisticLockHeader(typeof row.updated_at === 'string' ? row.updated_at : null)\n await withScopedApiRequestHeaders(headers, () => (\n deleteCrud('catalog/products', row.id, {\n errorMessage: t('catalog.products.list.error.delete', 'Failed to delete product'),\n })\n ))\n flash(t('catalog.products.flash.deleted', 'Product deleted'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : t('catalog.products.list.error.delete', 'Failed to delete product')\n flash(message, 'error')\n }\n }, [confirm, t])\n\n React.useEffect(() => {\n if (!onSnapshotChange) return\n onSnapshotChange({ search, filterValues, total })\n }, [onSnapshotChange, search, filterValues, total])\n\n const currentParams = React.useMemo(() => Object.fromEntries(new URLSearchParams(queryParams)), [queryParams])\n\n const exportConfig = React.useMemo(() => ({\n view: {\n getUrl: (format: DataTableExportFormat) =>\n buildCrudExportUrl('catalog/products', { ...currentParams, exportScope: 'view' }, format),\n },\n full: {\n getUrl: (format: DataTableExportFormat) =>\n buildCrudExportUrl('catalog/products', { ...currentParams, exportScope: 'full', all: 'true' }, format),\n },\n }), [currentParams])\n\n return (\n <>\n <DataTable<ProductRow>\n title={t('catalog.products.page.title', 'Products & services')}\n entityId={ENTITY_ID}\n customFieldFilterKeyExtras={[scopeVersion, reloadToken]}\n refreshButton={{\n label: t('catalog.products.actions.refresh', 'Refresh'),\n onRefresh: handleRefresh,\n isRefreshing: isLoading,\n }}\n actions={(\n <div className=\"flex items-center gap-2\">\n {extraActions}\n <Button asChild>\n <Link href=\"/backend/catalog/products/create\">\n {t('catalog.products.actions.create', 'Create')}\n </Link>\n </Button>\n </div>\n )}\n columns={columns}\n data={rows}\n emptyState={(\n <ListEmptyState\n entityName={t('catalog.products.page.title', 'Products & services')}\n createHref=\"/backend/catalog/products/create\"\n createLabel={t('catalog.products.actions.create', 'Create')}\n />\n )}\n searchValue={search}\n onSearchChange={handleSearchChange}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n onCustomFieldFilterFieldsetChange={handleCustomFieldsetFilterChange}\n sorting={sorting}\n onSortingChange={setSorting}\n injectionSpotId=\"data-table:catalog.products\"\n injectionContext={{\n search,\n filters: filterValues,\n customFieldset: customFieldsetFilter,\n page,\n sorting,\n scopeVersion,\n // Step 5.15: surface `total` so the merchandising AI widget\n // (rendered in `data-table:catalog.products:header`) can build\n // a selection-aware pageContext per spec \u00A710.1 without taking a\n // dependency on the host page.\n total,\n totalMatching: total,\n }}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n cacheStatus,\n }}\n exporter={exportConfig}\n isLoading={isLoading}\n perspective={{ tableId: 'catalog.products.list' }}\n stickyActionsColumn\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('catalog.products.table.actions.edit', 'Edit'),\n href: `/backend/catalog/products/${row.id}`,\n },\n {\n id: 'delete',\n label: t('catalog.products.table.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => {\n void handleDelete(row)\n },\n },\n ]}\n />\n )}\n />\n {ConfirmDialogElement}\n </>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -273,9 +273,9 @@ async function getLinkedTodo(ctx) {
273
273
  function buildCustomerUrl(kind, id) {
274
274
  if (!id) return null;
275
275
  const encoded = encodeURIComponent(id);
276
- if (kind === "person") return `/backend/customers/people/${encoded}`;
277
- if (kind === "company") return `/backend/customers/companies/${encoded}`;
278
- return `/backend/customers/companies/${encoded}`;
276
+ if (kind === "person") return `/backend/customers/people-v2/${encoded}`;
277
+ if (kind === "company") return `/backend/customers/companies-v2/${encoded}`;
278
+ return `/backend/customers/companies-v2/${encoded}`;
279
279
  }
280
280
  function formatDealValue(record) {
281
281
  const amount = record.value_amount ?? record.valueAmount;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/customers/search.ts"],
4
- "sourcesContent": ["import type { QueryCustomFieldSource, QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type {\n SearchModuleConfig,\n SearchBuildContext,\n SearchResultPresenter,\n SearchResultLink,\n SearchIndexSource,\n} from '@open-mercato/shared/modules/search'\nimport { CUSTOMER_INTERACTION_TASK_SOURCE, EXAMPLE_TODO_SOURCE } from './lib/interactionCompatibility'\n\n// =============================================================================\n// Context Types\n// =============================================================================\n\ntype SearchContext = SearchBuildContext & {\n tenantId: string\n queryEngine?: QueryEngine\n}\n\nfunction assertTenantContext(ctx: SearchBuildContext): asserts ctx is SearchContext {\n if (typeof ctx.tenantId !== 'string' || ctx.tenantId.length === 0) {\n throw new Error('[search.customers] Missing tenantId in search build context')\n }\n}\n\ntype CustomerProfileKind = 'person' | 'company'\n\ntype LoadedCustomerEntity = {\n entity: Record<string, unknown> | null\n customFields: Record<string, unknown>\n}\n\n// =============================================================================\n// Caching\n// =============================================================================\n\nconst entityIdCache = new Map<string, LoadedCustomerEntity | null>()\nconst profileEntityCache = new WeakMap<Record<string, unknown>, Partial<Record<CustomerProfileKind, LoadedCustomerEntity | null>>>()\nconst todoCache = new WeakMap<Record<string, unknown>, unknown>()\n\n// =============================================================================\n// Query Configuration\n// =============================================================================\n\nconst CUSTOMER_ENTITY_FIELDS = [\n 'id',\n 'kind',\n 'display_name',\n 'description',\n 'primary_email',\n 'primary_phone',\n 'status',\n 'lifecycle_stage',\n 'owner_user_id',\n 'source',\n 'next_interaction_at',\n 'next_interaction_name',\n 'next_interaction_ref_id',\n 'next_interaction_icon',\n 'next_interaction_color',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n 'deleted_at',\n] satisfies string[]\n\nconst CUSTOMER_CUSTOM_FIELD_SOURCES: QueryCustomFieldSource[] = [\n {\n entityId: 'customers:customer_person_profile',\n table: 'customer_people',\n alias: 'person_profile',\n recordIdColumn: 'id',\n join: { fromField: 'id', toField: 'entity_id' },\n },\n {\n entityId: 'customers:customer_company_profile',\n table: 'customer_companies',\n alias: 'company_profile',\n recordIdColumn: 'id',\n join: { fromField: 'id', toField: 'entity_id' },\n },\n]\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\nfunction extractCustomFieldMap(source: Record<string, unknown> | null | undefined): Record<string, unknown> {\n if (!source) return {}\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(source)) {\n if (value === undefined) continue\n if (key.startsWith('cf:')) {\n result[key.slice(3)] = value\n } else if (key.startsWith('cf_')) {\n result[key.slice(3)] = value\n }\n }\n return result\n}\n\nfunction normalizeCustomerEntity(row: Record<string, unknown>): Record<string, unknown> {\n const normalized: Record<string, unknown> = {\n id: row.id ?? row.entity_id ?? row.entityId ?? null,\n kind: row.kind ?? null,\n }\n const assign = (snake: string, camel?: string) => {\n const value = row[snake] ?? (camel ? row[camel] : undefined)\n if (value !== undefined) {\n normalized[snake] = value\n if (camel) normalized[camel] = value\n }\n }\n assign('display_name', 'displayName')\n assign('description')\n assign('primary_email', 'primaryEmail')\n assign('primary_phone', 'primaryPhone')\n assign('status')\n assign('lifecycle_stage', 'lifecycleStage')\n assign('owner_user_id', 'ownerUserId')\n assign('source')\n assign('next_interaction_at', 'nextInteractionAt')\n assign('next_interaction_name', 'nextInteractionName')\n assign('next_interaction_ref_id', 'nextInteractionRefId')\n assign('next_interaction_icon', 'nextInteractionIcon')\n assign('next_interaction_color', 'nextInteractionColor')\n assign('organization_id', 'organizationId')\n assign('tenant_id', 'tenantId')\n assign('created_at', 'createdAt')\n assign('updated_at', 'updatedAt')\n assign('deleted_at', 'deletedAt')\n return normalized\n}\n\nfunction getProfileCache(record: Record<string, unknown>): Partial<Record<CustomerProfileKind, LoadedCustomerEntity | null>> {\n let cache = profileEntityCache.get(record)\n if (!cache) {\n cache = {}\n profileEntityCache.set(record, cache)\n }\n return cache\n}\n\nfunction subtractCustomFields(\n primary: Record<string, unknown>,\n secondary: Record<string, unknown>,\n): Record<string, unknown> {\n if (!secondary || Object.keys(secondary).length === 0) return {}\n const primaryKeys = new Set(Object.keys(primary))\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(secondary)) {\n if (!primaryKeys.has(key)) {\n result[key] = value\n }\n }\n return result\n}\n\n// =============================================================================\n// Entity Loading Functions\n// =============================================================================\n\ntype CustomerEntityQueryOptions = {\n entityId?: string | null\n profileKind?: CustomerProfileKind\n profileId?: string | null\n}\n\nasync function loadCustomerEntityBundle(ctx: SearchContext, opts: CustomerEntityQueryOptions): Promise<LoadedCustomerEntity | null> {\n if (!ctx.queryEngine) return null\n const filters: Record<string, unknown> = {}\n const resolvedEntityId = typeof opts.entityId === 'string' && opts.entityId.length ? opts.entityId : null\n const resolvedProfileId =\n opts.profileId != null && String(opts.profileId).trim().length > 0 ? String(opts.profileId).trim() : null\n const shouldJoinProfileSource = Boolean(opts.profileKind && resolvedProfileId && !resolvedEntityId)\n if (resolvedEntityId) {\n filters.id = { $eq: resolvedEntityId }\n }\n if (shouldJoinProfileSource) {\n const alias = opts.profileKind === 'person' ? 'person_profile' : 'company_profile'\n filters[`${alias}.id`] = { $eq: resolvedProfileId }\n }\n if (!Object.keys(filters).length) return null\n try {\n const result = await ctx.queryEngine.query('customers:customer_entity', {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? undefined,\n filters,\n includeCustomFields: true,\n ...(shouldJoinProfileSource ? { customFieldSources: CUSTOMER_CUSTOM_FIELD_SOURCES } : {}),\n fields: CUSTOMER_ENTITY_FIELDS,\n page: { page: 1, pageSize: 1 },\n })\n const row = result.items[0] as Record<string, unknown> | undefined\n if (!row) return null\n const entity = normalizeCustomerEntity(row)\n const customFields = extractCustomFieldMap(row)\n return { entity, customFields }\n } catch (error) {\n console.warn('[search.customers] Failed to load customer entity via QueryEngine', {\n entityId: resolvedEntityId ?? null,\n profileKind: opts.profileKind ?? null,\n profileId: resolvedProfileId ?? null,\n error: error instanceof Error ? error.message : error,\n })\n return null\n }\n}\n\nasync function loadCustomerEntityForProfile(ctx: SearchContext, kind: CustomerProfileKind): Promise<LoadedCustomerEntity | null> {\n const cache = getProfileCache(ctx.record)\n if (cache[kind] !== undefined) return cache[kind] ?? null\n const entityIdHint = resolveCustomerEntityId(ctx.record)\n const profileIdRaw = ctx.record.id ?? null\n const profileId = profileIdRaw != null ? String(profileIdRaw) : null\n if (!entityIdHint && !profileId) {\n cache[kind] = null\n return null\n }\n const loaded = await loadCustomerEntityBundle(ctx, {\n entityId: entityIdHint,\n profileKind: kind,\n profileId,\n })\n cache[kind] = loaded ?? null\n const resolvedId = loaded?.entity?.id ?? entityIdHint\n if (resolvedId && typeof resolvedId === 'string') {\n ctx.record.entity_id ??= resolvedId\n ctx.record.entityId ??= resolvedId\n entityIdCache.set(resolvedId, loaded ?? null)\n }\n if (loaded?.entity) {\n if (!ctx.record.entity) ctx.record.entity = loaded.entity\n if (!ctx.record.customer_entity) ctx.record.customer_entity = loaded.entity\n }\n return loaded ?? null\n}\n\nasync function loadCustomerEntityById(ctx: SearchContext, entityId: string | null | undefined): Promise<LoadedCustomerEntity | null> {\n const resolvedId = typeof entityId === 'string' && entityId.length ? entityId : null\n if (!resolvedId) return null\n if (entityIdCache.has(resolvedId)) {\n return entityIdCache.get(resolvedId) ?? null\n }\n const loaded = await loadCustomerEntityBundle(ctx, { entityId: resolvedId })\n entityIdCache.set(resolvedId, loaded ?? null)\n return loaded ?? null\n}\n\nasync function getCustomerEntity(ctx: SearchContext, entityId?: string | null): Promise<Record<string, unknown> | null> {\n const profileCache = profileEntityCache.get(ctx.record)\n if (profileCache) {\n const cached = Object.values(profileCache).find((entry) => {\n if (!entry?.entity) return false\n if (!entityId) return true\n return entry.entity.id === entityId\n })\n if (cached?.entity) return cached.entity\n }\n const inline = getInlineCustomerEntity(ctx.record)\n if (inline && (!entityId || inline.id === entityId)) {\n if (inline.id && typeof inline.id === 'string') {\n entityIdCache.set(inline.id, { entity: inline, customFields: {} })\n }\n return inline\n }\n const resolvedId = entityId ?? resolveCustomerEntityId(ctx.record)\n const loaded = await loadCustomerEntityById(ctx, resolvedId)\n return loaded?.entity ?? null\n}\n\ntype HydratedProfileContext = {\n entity: Record<string, unknown> | null\n entityId: string | null\n profileCustomFields: Record<string, unknown>\n entityCustomFields: Record<string, unknown>\n entityOnlyCustomFields: Record<string, unknown>\n}\n\nasync function hydrateProfileContext(ctx: SearchContext, kind: CustomerProfileKind): Promise<HydratedProfileContext> {\n const profileCustomFields = ctx.customFields ?? {}\n const loaded = await loadCustomerEntityForProfile(ctx, kind)\n let entity = loaded?.entity ?? getInlineCustomerEntity(ctx.record)\n let entityCustomFields = loaded?.customFields ?? {}\n let entityId = (entity?.id as string | undefined) ?? resolveCustomerEntityId(ctx.record)\n if (!entity && entityId) {\n const fetched = await loadCustomerEntityById(ctx, entityId)\n entity = fetched?.entity ?? null\n if (fetched?.customFields) {\n entityCustomFields = Object.keys(entityCustomFields).length ? entityCustomFields : fetched.customFields\n }\n }\n if (!entity && !entityId) {\n entityId = resolveCustomerEntityId(ctx.record)\n }\n if (entity?.id && typeof entity.id === 'string') {\n entityId = entity.id\n ctx.record.entity_id ??= entity.id\n ctx.record.entityId ??= entity.id\n if (!ctx.record.entity) ctx.record.entity = entity\n if (!ctx.record.customer_entity) ctx.record.customer_entity = entity\n }\n const entityOnlyCustomFields = subtractCustomFields(profileCustomFields, entityCustomFields)\n return {\n entity: entity ?? null,\n entityId: entityId ?? null,\n profileCustomFields,\n entityCustomFields,\n entityOnlyCustomFields,\n }\n}\n\nasync function loadRecord(ctx: SearchContext, entityId: string, recordId?: string | null) {\n if (!recordId || !ctx.queryEngine) return null\n const res = await ctx.queryEngine.query(entityId, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? undefined,\n filters: { id: recordId },\n includeCustomFields: true,\n page: { page: 1, pageSize: 1 },\n })\n return res.items[0] as Record<string, unknown> | undefined\n}\n\nfunction resolveCustomerEntityId(record: Record<string, unknown>): string | null {\n const direct =\n record.customer_entity_id ??\n record.entityId ??\n record.entity_id ??\n record.customerEntityId ??\n record.customerEntityID ??\n (typeof record.entity === 'object' && record.entity ? (record.entity as Record<string, unknown>).id : undefined) ??\n (typeof record.customer_entity === 'object' && record.customer_entity ? (record.customer_entity as Record<string, unknown>).id : undefined)\n const value = typeof direct === 'string' && direct.length ? direct : null\n return value\n}\n\nfunction getInlineCustomerEntity(record: Record<string, unknown>): Record<string, unknown> | null {\n const inline =\n (typeof record.entity === 'object' && record.entity) ||\n (typeof record.customer_entity === 'object' && record.customer_entity) ||\n null\n return inline as Record<string, unknown> | null\n}\n\nasync function getLinkedTodo(ctx: SearchContext) {\n if (todoCache.has(ctx.record)) {\n return todoCache.get(ctx.record)\n }\n const sourceRaw = typeof ctx.record.todo_source === 'string' ? ctx.record.todo_source : EXAMPLE_TODO_SOURCE\n const [moduleId, entityName] = sourceRaw.split(':')\n const entityId = moduleId && entityName ? `${moduleId}:${entityName}` : CUSTOMER_INTERACTION_TASK_SOURCE\n const todo = await loadRecord(ctx, entityId, ctx.record.todo_id as string ?? ctx.record.todoId as string)\n todoCache.set(ctx.record, todo ?? null)\n return todo ?? null\n}\n\n// =============================================================================\n// URL and Formatting Helpers\n// =============================================================================\n\nfunction buildCustomerUrl(kind: string | null | undefined, id?: string | null): string | null {\n if (!id) return null\n const encoded = encodeURIComponent(id)\n if (kind === 'person') return `/backend/customers/people/${encoded}`\n if (kind === 'company') return `/backend/customers/companies/${encoded}`\n return `/backend/customers/companies/${encoded}`\n}\n\nfunction formatDealValue(record: Record<string, unknown>): string | undefined {\n const amount = record.value_amount ?? record.valueAmount\n if (!amount) return undefined\n const currency = record.value_currency ?? record.valueCurrency ?? ''\n return currency ? `${amount} ${currency}` : String(amount)\n}\n\nfunction snippet(text: unknown, max = 140): string | undefined {\n if (typeof text !== 'string') return undefined\n const trimmed = text.trim()\n if (!trimmed.length) return undefined\n if (trimmed.length <= max) return trimmed\n return `${trimmed.slice(0, max - 3)}...`\n}\n\nfunction appendLine(lines: string[], label: string, value: unknown) {\n if (value === null || value === undefined) return\n const text = Array.isArray(value)\n ? value.map((item) => (item === null || item === undefined ? '' : String(item))).filter(Boolean).join(', ')\n : (typeof value === 'object' ? JSON.stringify(value) : String(value))\n if (!text.trim()) return\n lines.push(`${label}: ${text}`)\n}\n\nfunction friendlyLabel(input: string): string {\n return input\n .replace(/^cf:/, '')\n .replace(/_/g, ' ')\n .replace(/([a-z])([A-Z])/g, (_, a, b) => `${a} ${b}`)\n .replace(/\\b\\w/g, (char) => char.toUpperCase())\n}\n\nfunction appendCustomFieldLines(lines: string[], customFields: Record<string, unknown>, prefix: string) {\n for (const [key, value] of Object.entries(customFields)) {\n if (value === null || value === undefined) continue\n const label = prefix ? `${prefix} ${friendlyLabel(key)}` : friendlyLabel(key)\n appendLine(lines, label, value)\n }\n}\n\nfunction pickValue(source: Record<string, unknown> | null | undefined, ...keys: string[]): unknown {\n if (!source) return undefined\n for (const key of keys) {\n if (key in source && source[key] != null) return source[key]\n }\n return undefined\n}\n\nfunction pickString(...candidates: unknown[]): string | null {\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length) {\n return candidate.trim()\n }\n }\n return null\n}\n\nfunction pickLabel(...candidates: Array<unknown>): string | null {\n for (const candidate of candidates) {\n if (candidate === null || candidate === undefined) continue\n const value = typeof candidate === 'string' ? candidate : String(candidate)\n const trimmed = value.trim()\n if (trimmed.length) return trimmed\n }\n return null\n}\n\nfunction appendCustomerEntityLines(\n lines: string[],\n entity: Record<string, unknown> | null,\n contactLabel: 'Customer' | 'Primary' = 'Customer',\n) {\n if (!entity) return\n appendLine(lines, 'Customer', pickValue(entity, 'display_name', 'displayName') ?? entity.id)\n appendLine(lines, `${contactLabel} email`, pickValue(entity, 'primary_email', 'primaryEmail'))\n appendLine(lines, `${contactLabel} phone`, pickValue(entity, 'primary_phone', 'primaryPhone'))\n appendLine(lines, 'Lifecycle stage', pickValue(entity, 'lifecycle_stage', 'lifecycleStage'))\n appendLine(lines, 'Status', pickValue(entity, 'status'))\n}\n\nfunction ensureFallbackLines(lines: string[], record: Record<string, unknown>, options: { includeId?: boolean } = {}) {\n if (lines.length) return\n const excluded = new Set(['tenant_id', 'organization_id', 'created_at', 'updated_at', 'deleted_at'])\n for (const [key, value] of Object.entries(record)) {\n if (value === null || value === undefined) continue\n if (excluded.has(key)) continue\n if (key === 'id') continue\n appendLine(lines, friendlyLabel(key), value)\n }\n if (!lines.length && options.includeId !== false) {\n const fallbackId =\n record.id ??\n record.entity_id ??\n record.customer_entity_id ??\n record.entityId ??\n record.customerEntityId ??\n null\n if (fallbackId) {\n appendLine(lines, 'Record ID', fallbackId)\n }\n }\n}\n\n// =============================================================================\n// Presenter Functions\n// =============================================================================\n\nfunction resolvePersonPresenter(\n record: Record<string, unknown>,\n entity: Record<string, unknown> | null,\n customFields: Record<string, unknown>,\n): SearchResultPresenter {\n const fallbackEntityId = resolveCustomerEntityId(record)\n const firstName = record.first_name ?? record.firstName ?? customFields.first_name ?? customFields.firstName ?? ''\n const lastName = record.last_name ?? record.lastName ?? customFields.last_name ?? customFields.lastName ?? ''\n const nameParts = [firstName, lastName].filter(Boolean).join(' ')\n const title =\n (pickValue(entity, 'display_name', 'displayName') as string | undefined) ??\n (record.preferred_name as string | undefined) ??\n (record.preferredName as string | undefined) ??\n (nameParts.length ? nameParts : undefined) ??\n fallbackEntityId ??\n (record.id as string | undefined) ??\n 'Person'\n const subtitlePieces: string[] = []\n const jobTitle = record.job_title ?? record.jobTitle ?? customFields.job_title ?? customFields.jobTitle\n if (jobTitle) subtitlePieces.push(String(jobTitle))\n const department = record.department ?? customFields.department\n if (department) subtitlePieces.push(String(department))\n const primaryEmail = pickValue(entity, 'primary_email', 'primaryEmail')\n if (primaryEmail) subtitlePieces.push(String(primaryEmail))\n const primaryPhone = pickValue(entity, 'primary_phone', 'primaryPhone')\n if (primaryPhone) subtitlePieces.push(String(primaryPhone))\n const summary = snippet(\n (pickValue(entity, 'description') as string | undefined) ??\n (customFields.summary as string | undefined) ??\n (customFields.description as string | undefined),\n )\n if (summary) subtitlePieces.push(summary)\n return {\n title: String(title),\n subtitle: subtitlePieces.length ? subtitlePieces.join(' \u00B7 ') : undefined,\n icon: 'user',\n badge: pickValue(entity, 'display_name', 'displayName') ? 'Person' : undefined,\n }\n}\n\nfunction resolveCompanyPresenter(\n record: Record<string, unknown>,\n entity: Record<string, unknown> | null,\n customFields: Record<string, unknown>,\n): SearchResultPresenter {\n const fallbackEntityId = resolveCustomerEntityId(record)\n const title =\n (pickValue(entity, 'display_name', 'displayName') as string | undefined) ??\n (customFields.display_name as string | undefined) ??\n (customFields.displayName as string | undefined) ??\n (record.brand_name as string | undefined) ??\n (record.legal_name as string | undefined) ??\n (record.domain as string | undefined) ??\n (record.brandName as string | undefined) ??\n (record.legalName as string | undefined) ??\n (entity?.id && entity?.display_name ? entity.display_name as string : undefined) ??\n fallbackEntityId ??\n (record.id as string | undefined) ??\n 'Company'\n const subtitlePieces: string[] = []\n const industry = record.industry\n if (industry) subtitlePieces.push(String(industry))\n const sizeBucket = record.size_bucket ?? record.sizeBucket\n if (sizeBucket) subtitlePieces.push(String(sizeBucket))\n if (entity) {\n const primaryEmail = pickValue(entity, 'primary_email', 'primaryEmail')\n if (primaryEmail) subtitlePieces.push(String(primaryEmail))\n }\n const summary = snippet(\n (pickValue(entity, 'description') as string | undefined) ??\n (customFields.summary as string | undefined) ??\n (customFields.description as string | undefined) ??\n (record.summary as string | undefined) ??\n (record.description as string | undefined),\n )\n if (summary) subtitlePieces.push(summary)\n if (!entity && (!title || title === fallbackEntityId)) {\n console.warn('[search.customers] Missing customer entity during company presenter build', {\n recordId: record.id ?? null,\n entityId: fallbackEntityId,\n recordKeys: Object.keys(record),\n })\n }\n return {\n title: String(title),\n subtitle: subtitlePieces.length ? subtitlePieces.join(' \u00B7 ') : undefined,\n icon: 'building',\n badge: pickValue(entity, 'display_name', 'displayName') ? 'Company' : undefined,\n }\n}\n\nfunction logMissingPresenterTitle(\n kind: 'person' | 'company',\n record: Record<string, unknown>,\n entity: Record<string, unknown> | null,\n presenter: SearchResultPresenter,\n) {\n const fallbackId = record.id ?? record.entity_id ?? resolveCustomerEntityId(record)\n if (!fallbackId) return\n if (presenter.title && presenter.title !== String(fallbackId)) return\n console.warn('[search.customers] Presenter fell back to record id', {\n kind,\n recordId: fallbackId,\n entityId: resolveCustomerEntityId(record),\n entityDisplayName: entity?.display_name ?? null,\n })\n}\n\n// =============================================================================\n// Search Module Configuration\n// =============================================================================\n\nexport const searchConfig: SearchModuleConfig = {\n entities: [\n // =========================================================================\n // Person Profile\n // =========================================================================\n {\n entityId: 'customers:customer_person_profile',\n enabled: true,\n priority: 10,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const lines: string[] = []\n const record = ctx.record\n appendLine(lines, 'Preferred name', record.preferred_name ?? record.preferredName ?? ctx.customFields.preferred_name)\n appendLine(lines, 'First name', record.first_name ?? record.firstName ?? ctx.customFields.first_name)\n appendLine(lines, 'Last name', record.last_name ?? record.lastName ?? ctx.customFields.last_name)\n appendLine(lines, 'Job title', record.job_title ?? record.jobTitle ?? ctx.customFields.job_title)\n appendLine(lines, 'Department', record.department ?? record.department_name ?? record.departmentName ?? ctx.customFields.department)\n appendLine(lines, 'Seniority', record.seniority ?? record.seniority_level ?? record.seniorityLevel ?? ctx.customFields.seniority)\n appendLine(lines, 'Timezone', record.timezone ?? record.time_zone ?? record.timeZone ?? ctx.customFields.timezone)\n appendLine(lines, 'LinkedIn', record.linked_in_url ?? record.linkedInUrl ?? ctx.customFields.linked_in_url)\n appendLine(lines, 'Twitter', record.twitter_url ?? record.twitterUrl ?? ctx.customFields.twitter_url)\n\n const { entity, entityId, profileCustomFields, entityCustomFields, entityOnlyCustomFields } =\n await hydrateProfileContext(ctx, 'person')\n appendCustomFieldLines(lines, profileCustomFields, 'Person custom')\n if (Object.keys(entityOnlyCustomFields).length) {\n appendCustomFieldLines(lines, entityOnlyCustomFields, 'Customer custom')\n }\n if (!entity) {\n console.warn('[search.customers] Failed to load customer entity for person profile', {\n recordId: record.id,\n entityId,\n recordKeys: Object.keys(record),\n })\n }\n appendCustomerEntityLines(lines, entity, 'Customer')\n ensureFallbackLines(lines, record)\n if (!lines.length) return null\n\n if (!entityId) {\n console.warn('[search.customers] person profile missing entity id', {\n recordId: record.id,\n recordKeys: Object.keys(record),\n })\n }\n\n const presenter = resolvePersonPresenter(record, entity, ctx.customFields)\n logMissingPresenterTitle('person', record, entity, presenter)\n const presenterLabel = pickLabel(presenter.title) ?? 'Open person'\n const links: SearchResultLink[] = []\n if (entityId) {\n const href = buildCustomerUrl('person', entityId)\n if (href) {\n links.push({ href, label: presenterLabel, kind: 'primary' })\n }\n }\n\n return {\n text: lines,\n presenter,\n links,\n checksumSource: {\n record: ctx.record,\n customFields: profileCustomFields,\n entity,\n entityCustomFields,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const entity = await getCustomerEntity(ctx, resolveCustomerEntityId(ctx.record))\n return resolvePersonPresenter(ctx.record, entity, ctx.customFields)\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n const entityId = resolveCustomerEntityId(ctx.record)\n return buildCustomerUrl('person', entityId)\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const entityId = resolveCustomerEntityId(ctx.record)\n if (!entityId) return null\n const href = buildCustomerUrl('person', entityId)\n if (!href) return null\n return [{ href: `${href}/edit`, label: 'Edit', kind: 'secondary' }]\n },\n\n fieldPolicy: {\n searchable: [\n 'preferred_name',\n 'first_name',\n 'last_name',\n 'job_title',\n 'department',\n 'seniority',\n 'timezone',\n 'linked_in_url',\n 'twitter_url',\n ],\n hashOnly: ['primary_email', 'primary_phone', 'personal_email'],\n excluded: ['date_of_birth', 'government_id', 'ssn', 'tax_id'],\n },\n aclFeatures: ['customers.people.view'],\n },\n\n // =========================================================================\n // Company Profile\n // =========================================================================\n {\n entityId: 'customers:customer_company_profile',\n enabled: true,\n priority: 10,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const lines: string[] = []\n const record = ctx.record\n appendLine(lines, 'Legal name', record.legal_name ?? record.legalName ?? ctx.customFields.legal_name)\n appendLine(lines, 'Brand name', record.brand_name ?? record.brandName ?? ctx.customFields.brand_name)\n appendLine(lines, 'Domain', record.domain ?? record.website_domain ?? record.websiteDomain ?? ctx.customFields.domain)\n appendLine(lines, 'Website', record.website_url ?? record.websiteUrl ?? ctx.customFields.website_url)\n appendLine(lines, 'Industry', record.industry ?? ctx.customFields.industry)\n appendLine(lines, 'Company size', record.size_bucket ?? record.sizeBucket ?? ctx.customFields.size_bucket)\n appendLine(lines, 'Annual revenue', record.annual_revenue ?? record.annualRevenue ?? ctx.customFields.annual_revenue)\n\n const { entity, entityId, profileCustomFields, entityCustomFields, entityOnlyCustomFields } =\n await hydrateProfileContext(ctx, 'company')\n appendCustomFieldLines(lines, profileCustomFields, 'Company custom')\n if (Object.keys(entityOnlyCustomFields).length) {\n appendCustomFieldLines(lines, entityOnlyCustomFields, 'Customer custom')\n }\n appendCustomerEntityLines(lines, entity, 'Primary')\n ensureFallbackLines(lines, record)\n if (!lines.length) return null\n\n const presenter = resolveCompanyPresenter(record, entity, ctx.customFields)\n logMissingPresenterTitle('company', record, entity, presenter)\n const primaryLabel = pickLabel(presenter.title) ?? 'Open company'\n const links: SearchResultLink[] = []\n if (entityId) {\n const href = buildCustomerUrl('company', entityId)\n if (href) {\n links.push({ href, label: primaryLabel, kind: 'primary' })\n }\n }\n\n return {\n text: lines,\n presenter,\n links,\n checksumSource: {\n record: ctx.record,\n customFields: profileCustomFields,\n entity,\n entityCustomFields,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const entity = await getCustomerEntity(ctx, resolveCustomerEntityId(ctx.record))\n return resolveCompanyPresenter(ctx.record, entity, ctx.customFields)\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n const entityId = resolveCustomerEntityId(ctx.record)\n return buildCustomerUrl('company', entityId)\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const entityId = resolveCustomerEntityId(ctx.record)\n if (!entityId) return null\n const href = buildCustomerUrl('company', entityId)\n if (!href) return null\n return [{ href: `${href}/edit`, label: 'Edit', kind: 'secondary' }]\n },\n\n fieldPolicy: {\n searchable: [\n 'legal_name',\n 'brand_name',\n 'display_name',\n 'domain',\n 'website_url',\n 'industry',\n 'size_bucket',\n 'description',\n ],\n hashOnly: ['tax_id', 'registration_number'],\n excluded: ['bank_account', 'billing_info', 'credit_info'],\n },\n aclFeatures: ['customers.companies.view'],\n },\n\n // =========================================================================\n // Customer Comment\n // =========================================================================\n {\n entityId: 'customers:customer_comment',\n enabled: true,\n priority: 6,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const lines: string[] = []\n if (parent?.display_name) lines.push(`Customer: ${parent.display_name}`)\n lines.push(`Note: ${ctx.record.body ?? ''}`)\n if (ctx.record.appearance_icon) lines.push(`Icon: ${ctx.record.appearance_icon}`)\n if (ctx.record.appearance_color) lines.push(`Color: ${ctx.record.appearance_color}`)\n\n const presenter: SearchResultPresenter | undefined = parent?.display_name\n ? {\n title: parent.display_name as string,\n subtitle: snippet(ctx.record.body),\n icon: parent.kind === 'person' ? 'user' : 'building',\n }\n : undefined\n\n return {\n text: lines,\n presenter,\n checksumSource: {\n body: ctx.record.body,\n entityId: ctx.record.entity_id ?? null,\n updatedAt: ctx.record.updated_at ?? ctx.record.updatedAt ?? null,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const title = (parent?.display_name as string | undefined) ?? 'Customer note'\n return {\n title,\n subtitle: snippet(ctx.record.body),\n icon: 'sticky-note',\n }\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const base = buildCustomerUrl(parent?.kind as string ?? null, (parent?.id ?? ctx.record.entity_id ?? ctx.record.entityId) as string)\n return base ? `${base}#notes` : null\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n assertTenantContext(ctx)\n const links: SearchResultLink[] = []\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const parentUrl = buildCustomerUrl(parent?.kind as string ?? null, (parent?.id ?? ctx.record.entity_id ?? ctx.record.entityId) as string)\n if (parentUrl) {\n links.push({ href: parentUrl, label: (parent?.display_name as string | undefined) ?? 'View customer', kind: 'primary' })\n }\n if (ctx.record.deal_id) {\n const dealUrl = `/backend/customers/deals/${encodeURIComponent(ctx.record.deal_id as string)}`\n links.push({ href: dealUrl, label: 'Open deal', kind: 'secondary' })\n }\n return links.length ? links : null\n },\n\n fieldPolicy: {\n searchable: ['body'],\n hashOnly: [],\n excluded: [],\n },\n aclFeatures: ['customers.activities.view'],\n },\n\n // =========================================================================\n // Customer Deal\n // =========================================================================\n {\n entityId: 'customers:customer_deal',\n enabled: true,\n priority: 8,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n const lines: string[] = []\n const record = ctx.record\n appendLine(lines, 'Title', record.title)\n appendLine(lines, 'Stage', record.pipeline_stage)\n appendLine(lines, 'Status', record.status)\n appendLine(lines, 'Source', record.source)\n const value = formatDealValue(record)\n if (value) appendLine(lines, 'Value', value)\n if (!lines.length) return null\n\n const subtitleParts: string[] = []\n if (record.pipeline_stage) subtitleParts.push(String(record.pipeline_stage))\n if (record.status) subtitleParts.push(String(record.status))\n if (value) subtitleParts.push(value)\n\n return {\n text: lines,\n presenter: {\n title: String(record.title ?? 'Deal'),\n subtitle: subtitleParts.join(' \u00B7 ') || undefined,\n icon: 'briefcase',\n badge: 'Deal',\n },\n checksumSource: {\n title: record.title,\n status: record.status,\n stage: record.pipeline_stage,\n value: value,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n const { record } = ctx\n const title = pickString(record.title as string, 'Deal')\n const subtitleParts: string[] = []\n if (record.pipeline_stage) subtitleParts.push(String(record.pipeline_stage))\n if (record.status) subtitleParts.push(String(record.status))\n const amount = record.value_amount ?? record.valueAmount\n const currency = record.value_currency ?? record.valueCurrency\n if (amount) {\n subtitleParts.push(currency ? `${amount} ${currency}` : String(amount))\n }\n\n return {\n title: title ?? 'Deal',\n subtitle: subtitleParts.length ? subtitleParts.join(' \u00B7 ') : undefined,\n icon: 'briefcase',\n badge: 'Deal',\n }\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n const id = ctx.record.id\n if (!id) return null\n return `/backend/customers/deals/${encodeURIComponent(String(id))}`\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const id = ctx.record.id\n if (!id) return null\n return [\n {\n href: `/backend/customers/deals/${encodeURIComponent(String(id))}/edit`,\n label: 'Edit',\n kind: 'secondary',\n },\n ]\n },\n\n fieldPolicy: {\n searchable: ['title', 'description', 'pipeline_stage', 'status', 'source'],\n hashOnly: [],\n excluded: ['value_amount', 'value_currency'],\n },\n aclFeatures: ['customers.deals.view'],\n },\n\n // =========================================================================\n // Customer Activity\n // =========================================================================\n {\n entityId: 'customers:customer_activity',\n enabled: true,\n priority: 5,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const lines: string[] = []\n if (parent?.display_name) lines.push(`Customer: ${parent.display_name}`)\n if (ctx.record.activity_type) lines.push(`Type: ${ctx.record.activity_type}`)\n if (ctx.record.subject) lines.push(`Subject: ${ctx.record.subject}`)\n if (ctx.record.body) lines.push(`Body: ${ctx.record.body}`)\n\n const presenter: SearchResultPresenter = {\n title: ctx.record.subject ? String(ctx.record.subject) : `Activity: ${ctx.record.activity_type ?? 'update'}`,\n subtitle: (parent?.display_name as string | undefined) ?? snippet(ctx.record.body),\n icon: 'bolt',\n }\n\n return {\n text: lines,\n presenter,\n checksumSource: {\n subject: ctx.record.subject,\n body: ctx.record.body,\n entityId: ctx.record.entity_id ?? null,\n updatedAt: ctx.record.updated_at ?? ctx.record.updatedAt ?? null,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n return {\n title: ctx.record.subject ? String(ctx.record.subject) : `Activity: ${ctx.record.activity_type ?? 'update'}`,\n subtitle: (parent?.display_name as string | undefined) ?? snippet(ctx.record.body),\n icon: 'bolt',\n badge: 'Activity',\n }\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const base = buildCustomerUrl(parent?.kind as string ?? null, (parent?.id ?? ctx.record.entity_id ?? ctx.record.entityId) as string)\n return base ? `${base}#activity-${ctx.record.id ?? ctx.record.activity_id ?? ''}` : null\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const links: SearchResultLink[] = []\n if (ctx.record.deal_id) {\n links.push({\n href: `/backend/customers/deals/${encodeURIComponent(ctx.record.deal_id as string)}`,\n label: 'Open deal',\n kind: 'secondary',\n })\n }\n return links.length ? links : null\n },\n\n fieldPolicy: {\n searchable: ['subject', 'body', 'activity_type'],\n hashOnly: [],\n excluded: [],\n },\n aclFeatures: ['customers.activities.view'],\n },\n\n // =========================================================================\n // Customer Todo Link\n // =========================================================================\n {\n entityId: 'customers:customer_todo_link',\n enabled: true,\n priority: 4,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const todo = await getLinkedTodo(ctx) as Record<string, unknown> | null\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const lines: string[] = []\n if (todo?.title) lines.push(`Todo: ${todo.title}`)\n if (todo?.is_done !== undefined) lines.push(`Status: ${todo.is_done ? 'Done' : 'Open'}`)\n if (parent?.display_name) lines.push(`Customer: ${parent.display_name}`)\n if (!lines.length) return null\n\n return {\n text: lines,\n presenter: todo?.title\n ? { title: todo.title as string, subtitle: parent?.display_name as string | undefined, icon: 'check-square' }\n : undefined,\n checksumSource: {\n todoId: ctx.record.todo_id ?? ctx.record.todoId,\n todoSource: ctx.record.todo_source ?? ctx.record.todoSource,\n entityId: ctx.record.entity_id ?? ctx.record.entityId,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const todo = await getLinkedTodo(ctx) as Record<string, unknown> | null\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n return {\n title: (todo?.title as string | undefined) ?? 'Customer task',\n subtitle: parent?.display_name as string | undefined,\n icon: 'check-square',\n }\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const base = buildCustomerUrl(parent?.kind as string ?? null, (parent?.id ?? ctx.record.entity_id ?? ctx.record.entityId) as string)\n return base ? `${base}#tasks` : null\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const todoId = ctx.record.todo_id ?? ctx.record.todoId\n if (!todoId) return null\n return [{\n href: `/backend/todos/${encodeURIComponent(todoId as string)}/edit`,\n label: 'Open todo',\n kind: 'secondary',\n }]\n },\n\n fieldPolicy: {\n searchable: [],\n hashOnly: [],\n excluded: [],\n },\n aclFeatures: ['customers.activities.view'],\n },\n ],\n}\n\nexport default searchConfig\nexport const config = searchConfig\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { QueryCustomFieldSource, QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type {\n SearchModuleConfig,\n SearchBuildContext,\n SearchResultPresenter,\n SearchResultLink,\n SearchIndexSource,\n} from '@open-mercato/shared/modules/search'\nimport { CUSTOMER_INTERACTION_TASK_SOURCE, EXAMPLE_TODO_SOURCE } from './lib/interactionCompatibility'\n\n// =============================================================================\n// Context Types\n// =============================================================================\n\ntype SearchContext = SearchBuildContext & {\n tenantId: string\n queryEngine?: QueryEngine\n}\n\nfunction assertTenantContext(ctx: SearchBuildContext): asserts ctx is SearchContext {\n if (typeof ctx.tenantId !== 'string' || ctx.tenantId.length === 0) {\n throw new Error('[search.customers] Missing tenantId in search build context')\n }\n}\n\ntype CustomerProfileKind = 'person' | 'company'\n\ntype LoadedCustomerEntity = {\n entity: Record<string, unknown> | null\n customFields: Record<string, unknown>\n}\n\n// =============================================================================\n// Caching\n// =============================================================================\n\nconst entityIdCache = new Map<string, LoadedCustomerEntity | null>()\nconst profileEntityCache = new WeakMap<Record<string, unknown>, Partial<Record<CustomerProfileKind, LoadedCustomerEntity | null>>>()\nconst todoCache = new WeakMap<Record<string, unknown>, unknown>()\n\n// =============================================================================\n// Query Configuration\n// =============================================================================\n\nconst CUSTOMER_ENTITY_FIELDS = [\n 'id',\n 'kind',\n 'display_name',\n 'description',\n 'primary_email',\n 'primary_phone',\n 'status',\n 'lifecycle_stage',\n 'owner_user_id',\n 'source',\n 'next_interaction_at',\n 'next_interaction_name',\n 'next_interaction_ref_id',\n 'next_interaction_icon',\n 'next_interaction_color',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n 'deleted_at',\n] satisfies string[]\n\nconst CUSTOMER_CUSTOM_FIELD_SOURCES: QueryCustomFieldSource[] = [\n {\n entityId: 'customers:customer_person_profile',\n table: 'customer_people',\n alias: 'person_profile',\n recordIdColumn: 'id',\n join: { fromField: 'id', toField: 'entity_id' },\n },\n {\n entityId: 'customers:customer_company_profile',\n table: 'customer_companies',\n alias: 'company_profile',\n recordIdColumn: 'id',\n join: { fromField: 'id', toField: 'entity_id' },\n },\n]\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\nfunction extractCustomFieldMap(source: Record<string, unknown> | null | undefined): Record<string, unknown> {\n if (!source) return {}\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(source)) {\n if (value === undefined) continue\n if (key.startsWith('cf:')) {\n result[key.slice(3)] = value\n } else if (key.startsWith('cf_')) {\n result[key.slice(3)] = value\n }\n }\n return result\n}\n\nfunction normalizeCustomerEntity(row: Record<string, unknown>): Record<string, unknown> {\n const normalized: Record<string, unknown> = {\n id: row.id ?? row.entity_id ?? row.entityId ?? null,\n kind: row.kind ?? null,\n }\n const assign = (snake: string, camel?: string) => {\n const value = row[snake] ?? (camel ? row[camel] : undefined)\n if (value !== undefined) {\n normalized[snake] = value\n if (camel) normalized[camel] = value\n }\n }\n assign('display_name', 'displayName')\n assign('description')\n assign('primary_email', 'primaryEmail')\n assign('primary_phone', 'primaryPhone')\n assign('status')\n assign('lifecycle_stage', 'lifecycleStage')\n assign('owner_user_id', 'ownerUserId')\n assign('source')\n assign('next_interaction_at', 'nextInteractionAt')\n assign('next_interaction_name', 'nextInteractionName')\n assign('next_interaction_ref_id', 'nextInteractionRefId')\n assign('next_interaction_icon', 'nextInteractionIcon')\n assign('next_interaction_color', 'nextInteractionColor')\n assign('organization_id', 'organizationId')\n assign('tenant_id', 'tenantId')\n assign('created_at', 'createdAt')\n assign('updated_at', 'updatedAt')\n assign('deleted_at', 'deletedAt')\n return normalized\n}\n\nfunction getProfileCache(record: Record<string, unknown>): Partial<Record<CustomerProfileKind, LoadedCustomerEntity | null>> {\n let cache = profileEntityCache.get(record)\n if (!cache) {\n cache = {}\n profileEntityCache.set(record, cache)\n }\n return cache\n}\n\nfunction subtractCustomFields(\n primary: Record<string, unknown>,\n secondary: Record<string, unknown>,\n): Record<string, unknown> {\n if (!secondary || Object.keys(secondary).length === 0) return {}\n const primaryKeys = new Set(Object.keys(primary))\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(secondary)) {\n if (!primaryKeys.has(key)) {\n result[key] = value\n }\n }\n return result\n}\n\n// =============================================================================\n// Entity Loading Functions\n// =============================================================================\n\ntype CustomerEntityQueryOptions = {\n entityId?: string | null\n profileKind?: CustomerProfileKind\n profileId?: string | null\n}\n\nasync function loadCustomerEntityBundle(ctx: SearchContext, opts: CustomerEntityQueryOptions): Promise<LoadedCustomerEntity | null> {\n if (!ctx.queryEngine) return null\n const filters: Record<string, unknown> = {}\n const resolvedEntityId = typeof opts.entityId === 'string' && opts.entityId.length ? opts.entityId : null\n const resolvedProfileId =\n opts.profileId != null && String(opts.profileId).trim().length > 0 ? String(opts.profileId).trim() : null\n const shouldJoinProfileSource = Boolean(opts.profileKind && resolvedProfileId && !resolvedEntityId)\n if (resolvedEntityId) {\n filters.id = { $eq: resolvedEntityId }\n }\n if (shouldJoinProfileSource) {\n const alias = opts.profileKind === 'person' ? 'person_profile' : 'company_profile'\n filters[`${alias}.id`] = { $eq: resolvedProfileId }\n }\n if (!Object.keys(filters).length) return null\n try {\n const result = await ctx.queryEngine.query('customers:customer_entity', {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? undefined,\n filters,\n includeCustomFields: true,\n ...(shouldJoinProfileSource ? { customFieldSources: CUSTOMER_CUSTOM_FIELD_SOURCES } : {}),\n fields: CUSTOMER_ENTITY_FIELDS,\n page: { page: 1, pageSize: 1 },\n })\n const row = result.items[0] as Record<string, unknown> | undefined\n if (!row) return null\n const entity = normalizeCustomerEntity(row)\n const customFields = extractCustomFieldMap(row)\n return { entity, customFields }\n } catch (error) {\n console.warn('[search.customers] Failed to load customer entity via QueryEngine', {\n entityId: resolvedEntityId ?? null,\n profileKind: opts.profileKind ?? null,\n profileId: resolvedProfileId ?? null,\n error: error instanceof Error ? error.message : error,\n })\n return null\n }\n}\n\nasync function loadCustomerEntityForProfile(ctx: SearchContext, kind: CustomerProfileKind): Promise<LoadedCustomerEntity | null> {\n const cache = getProfileCache(ctx.record)\n if (cache[kind] !== undefined) return cache[kind] ?? null\n const entityIdHint = resolveCustomerEntityId(ctx.record)\n const profileIdRaw = ctx.record.id ?? null\n const profileId = profileIdRaw != null ? String(profileIdRaw) : null\n if (!entityIdHint && !profileId) {\n cache[kind] = null\n return null\n }\n const loaded = await loadCustomerEntityBundle(ctx, {\n entityId: entityIdHint,\n profileKind: kind,\n profileId,\n })\n cache[kind] = loaded ?? null\n const resolvedId = loaded?.entity?.id ?? entityIdHint\n if (resolvedId && typeof resolvedId === 'string') {\n ctx.record.entity_id ??= resolvedId\n ctx.record.entityId ??= resolvedId\n entityIdCache.set(resolvedId, loaded ?? null)\n }\n if (loaded?.entity) {\n if (!ctx.record.entity) ctx.record.entity = loaded.entity\n if (!ctx.record.customer_entity) ctx.record.customer_entity = loaded.entity\n }\n return loaded ?? null\n}\n\nasync function loadCustomerEntityById(ctx: SearchContext, entityId: string | null | undefined): Promise<LoadedCustomerEntity | null> {\n const resolvedId = typeof entityId === 'string' && entityId.length ? entityId : null\n if (!resolvedId) return null\n if (entityIdCache.has(resolvedId)) {\n return entityIdCache.get(resolvedId) ?? null\n }\n const loaded = await loadCustomerEntityBundle(ctx, { entityId: resolvedId })\n entityIdCache.set(resolvedId, loaded ?? null)\n return loaded ?? null\n}\n\nasync function getCustomerEntity(ctx: SearchContext, entityId?: string | null): Promise<Record<string, unknown> | null> {\n const profileCache = profileEntityCache.get(ctx.record)\n if (profileCache) {\n const cached = Object.values(profileCache).find((entry) => {\n if (!entry?.entity) return false\n if (!entityId) return true\n return entry.entity.id === entityId\n })\n if (cached?.entity) return cached.entity\n }\n const inline = getInlineCustomerEntity(ctx.record)\n if (inline && (!entityId || inline.id === entityId)) {\n if (inline.id && typeof inline.id === 'string') {\n entityIdCache.set(inline.id, { entity: inline, customFields: {} })\n }\n return inline\n }\n const resolvedId = entityId ?? resolveCustomerEntityId(ctx.record)\n const loaded = await loadCustomerEntityById(ctx, resolvedId)\n return loaded?.entity ?? null\n}\n\ntype HydratedProfileContext = {\n entity: Record<string, unknown> | null\n entityId: string | null\n profileCustomFields: Record<string, unknown>\n entityCustomFields: Record<string, unknown>\n entityOnlyCustomFields: Record<string, unknown>\n}\n\nasync function hydrateProfileContext(ctx: SearchContext, kind: CustomerProfileKind): Promise<HydratedProfileContext> {\n const profileCustomFields = ctx.customFields ?? {}\n const loaded = await loadCustomerEntityForProfile(ctx, kind)\n let entity = loaded?.entity ?? getInlineCustomerEntity(ctx.record)\n let entityCustomFields = loaded?.customFields ?? {}\n let entityId = (entity?.id as string | undefined) ?? resolveCustomerEntityId(ctx.record)\n if (!entity && entityId) {\n const fetched = await loadCustomerEntityById(ctx, entityId)\n entity = fetched?.entity ?? null\n if (fetched?.customFields) {\n entityCustomFields = Object.keys(entityCustomFields).length ? entityCustomFields : fetched.customFields\n }\n }\n if (!entity && !entityId) {\n entityId = resolveCustomerEntityId(ctx.record)\n }\n if (entity?.id && typeof entity.id === 'string') {\n entityId = entity.id\n ctx.record.entity_id ??= entity.id\n ctx.record.entityId ??= entity.id\n if (!ctx.record.entity) ctx.record.entity = entity\n if (!ctx.record.customer_entity) ctx.record.customer_entity = entity\n }\n const entityOnlyCustomFields = subtractCustomFields(profileCustomFields, entityCustomFields)\n return {\n entity: entity ?? null,\n entityId: entityId ?? null,\n profileCustomFields,\n entityCustomFields,\n entityOnlyCustomFields,\n }\n}\n\nasync function loadRecord(ctx: SearchContext, entityId: string, recordId?: string | null) {\n if (!recordId || !ctx.queryEngine) return null\n const res = await ctx.queryEngine.query(entityId, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? undefined,\n filters: { id: recordId },\n includeCustomFields: true,\n page: { page: 1, pageSize: 1 },\n })\n return res.items[0] as Record<string, unknown> | undefined\n}\n\nfunction resolveCustomerEntityId(record: Record<string, unknown>): string | null {\n const direct =\n record.customer_entity_id ??\n record.entityId ??\n record.entity_id ??\n record.customerEntityId ??\n record.customerEntityID ??\n (typeof record.entity === 'object' && record.entity ? (record.entity as Record<string, unknown>).id : undefined) ??\n (typeof record.customer_entity === 'object' && record.customer_entity ? (record.customer_entity as Record<string, unknown>).id : undefined)\n const value = typeof direct === 'string' && direct.length ? direct : null\n return value\n}\n\nfunction getInlineCustomerEntity(record: Record<string, unknown>): Record<string, unknown> | null {\n const inline =\n (typeof record.entity === 'object' && record.entity) ||\n (typeof record.customer_entity === 'object' && record.customer_entity) ||\n null\n return inline as Record<string, unknown> | null\n}\n\nasync function getLinkedTodo(ctx: SearchContext) {\n if (todoCache.has(ctx.record)) {\n return todoCache.get(ctx.record)\n }\n const sourceRaw = typeof ctx.record.todo_source === 'string' ? ctx.record.todo_source : EXAMPLE_TODO_SOURCE\n const [moduleId, entityName] = sourceRaw.split(':')\n const entityId = moduleId && entityName ? `${moduleId}:${entityName}` : CUSTOMER_INTERACTION_TASK_SOURCE\n const todo = await loadRecord(ctx, entityId, ctx.record.todo_id as string ?? ctx.record.todoId as string)\n todoCache.set(ctx.record, todo ?? null)\n return todo ?? null\n}\n\n// =============================================================================\n// URL and Formatting Helpers\n// =============================================================================\n\nfunction buildCustomerUrl(kind: string | null | undefined, id?: string | null): string | null {\n if (!id) return null\n const encoded = encodeURIComponent(id)\n if (kind === 'person') return `/backend/customers/people-v2/${encoded}`\n if (kind === 'company') return `/backend/customers/companies-v2/${encoded}`\n return `/backend/customers/companies-v2/${encoded}`\n}\n\nfunction formatDealValue(record: Record<string, unknown>): string | undefined {\n const amount = record.value_amount ?? record.valueAmount\n if (!amount) return undefined\n const currency = record.value_currency ?? record.valueCurrency ?? ''\n return currency ? `${amount} ${currency}` : String(amount)\n}\n\nfunction snippet(text: unknown, max = 140): string | undefined {\n if (typeof text !== 'string') return undefined\n const trimmed = text.trim()\n if (!trimmed.length) return undefined\n if (trimmed.length <= max) return trimmed\n return `${trimmed.slice(0, max - 3)}...`\n}\n\nfunction appendLine(lines: string[], label: string, value: unknown) {\n if (value === null || value === undefined) return\n const text = Array.isArray(value)\n ? value.map((item) => (item === null || item === undefined ? '' : String(item))).filter(Boolean).join(', ')\n : (typeof value === 'object' ? JSON.stringify(value) : String(value))\n if (!text.trim()) return\n lines.push(`${label}: ${text}`)\n}\n\nfunction friendlyLabel(input: string): string {\n return input\n .replace(/^cf:/, '')\n .replace(/_/g, ' ')\n .replace(/([a-z])([A-Z])/g, (_, a, b) => `${a} ${b}`)\n .replace(/\\b\\w/g, (char) => char.toUpperCase())\n}\n\nfunction appendCustomFieldLines(lines: string[], customFields: Record<string, unknown>, prefix: string) {\n for (const [key, value] of Object.entries(customFields)) {\n if (value === null || value === undefined) continue\n const label = prefix ? `${prefix} ${friendlyLabel(key)}` : friendlyLabel(key)\n appendLine(lines, label, value)\n }\n}\n\nfunction pickValue(source: Record<string, unknown> | null | undefined, ...keys: string[]): unknown {\n if (!source) return undefined\n for (const key of keys) {\n if (key in source && source[key] != null) return source[key]\n }\n return undefined\n}\n\nfunction pickString(...candidates: unknown[]): string | null {\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length) {\n return candidate.trim()\n }\n }\n return null\n}\n\nfunction pickLabel(...candidates: Array<unknown>): string | null {\n for (const candidate of candidates) {\n if (candidate === null || candidate === undefined) continue\n const value = typeof candidate === 'string' ? candidate : String(candidate)\n const trimmed = value.trim()\n if (trimmed.length) return trimmed\n }\n return null\n}\n\nfunction appendCustomerEntityLines(\n lines: string[],\n entity: Record<string, unknown> | null,\n contactLabel: 'Customer' | 'Primary' = 'Customer',\n) {\n if (!entity) return\n appendLine(lines, 'Customer', pickValue(entity, 'display_name', 'displayName') ?? entity.id)\n appendLine(lines, `${contactLabel} email`, pickValue(entity, 'primary_email', 'primaryEmail'))\n appendLine(lines, `${contactLabel} phone`, pickValue(entity, 'primary_phone', 'primaryPhone'))\n appendLine(lines, 'Lifecycle stage', pickValue(entity, 'lifecycle_stage', 'lifecycleStage'))\n appendLine(lines, 'Status', pickValue(entity, 'status'))\n}\n\nfunction ensureFallbackLines(lines: string[], record: Record<string, unknown>, options: { includeId?: boolean } = {}) {\n if (lines.length) return\n const excluded = new Set(['tenant_id', 'organization_id', 'created_at', 'updated_at', 'deleted_at'])\n for (const [key, value] of Object.entries(record)) {\n if (value === null || value === undefined) continue\n if (excluded.has(key)) continue\n if (key === 'id') continue\n appendLine(lines, friendlyLabel(key), value)\n }\n if (!lines.length && options.includeId !== false) {\n const fallbackId =\n record.id ??\n record.entity_id ??\n record.customer_entity_id ??\n record.entityId ??\n record.customerEntityId ??\n null\n if (fallbackId) {\n appendLine(lines, 'Record ID', fallbackId)\n }\n }\n}\n\n// =============================================================================\n// Presenter Functions\n// =============================================================================\n\nfunction resolvePersonPresenter(\n record: Record<string, unknown>,\n entity: Record<string, unknown> | null,\n customFields: Record<string, unknown>,\n): SearchResultPresenter {\n const fallbackEntityId = resolveCustomerEntityId(record)\n const firstName = record.first_name ?? record.firstName ?? customFields.first_name ?? customFields.firstName ?? ''\n const lastName = record.last_name ?? record.lastName ?? customFields.last_name ?? customFields.lastName ?? ''\n const nameParts = [firstName, lastName].filter(Boolean).join(' ')\n const title =\n (pickValue(entity, 'display_name', 'displayName') as string | undefined) ??\n (record.preferred_name as string | undefined) ??\n (record.preferredName as string | undefined) ??\n (nameParts.length ? nameParts : undefined) ??\n fallbackEntityId ??\n (record.id as string | undefined) ??\n 'Person'\n const subtitlePieces: string[] = []\n const jobTitle = record.job_title ?? record.jobTitle ?? customFields.job_title ?? customFields.jobTitle\n if (jobTitle) subtitlePieces.push(String(jobTitle))\n const department = record.department ?? customFields.department\n if (department) subtitlePieces.push(String(department))\n const primaryEmail = pickValue(entity, 'primary_email', 'primaryEmail')\n if (primaryEmail) subtitlePieces.push(String(primaryEmail))\n const primaryPhone = pickValue(entity, 'primary_phone', 'primaryPhone')\n if (primaryPhone) subtitlePieces.push(String(primaryPhone))\n const summary = snippet(\n (pickValue(entity, 'description') as string | undefined) ??\n (customFields.summary as string | undefined) ??\n (customFields.description as string | undefined),\n )\n if (summary) subtitlePieces.push(summary)\n return {\n title: String(title),\n subtitle: subtitlePieces.length ? subtitlePieces.join(' \u00B7 ') : undefined,\n icon: 'user',\n badge: pickValue(entity, 'display_name', 'displayName') ? 'Person' : undefined,\n }\n}\n\nfunction resolveCompanyPresenter(\n record: Record<string, unknown>,\n entity: Record<string, unknown> | null,\n customFields: Record<string, unknown>,\n): SearchResultPresenter {\n const fallbackEntityId = resolveCustomerEntityId(record)\n const title =\n (pickValue(entity, 'display_name', 'displayName') as string | undefined) ??\n (customFields.display_name as string | undefined) ??\n (customFields.displayName as string | undefined) ??\n (record.brand_name as string | undefined) ??\n (record.legal_name as string | undefined) ??\n (record.domain as string | undefined) ??\n (record.brandName as string | undefined) ??\n (record.legalName as string | undefined) ??\n (entity?.id && entity?.display_name ? entity.display_name as string : undefined) ??\n fallbackEntityId ??\n (record.id as string | undefined) ??\n 'Company'\n const subtitlePieces: string[] = []\n const industry = record.industry\n if (industry) subtitlePieces.push(String(industry))\n const sizeBucket = record.size_bucket ?? record.sizeBucket\n if (sizeBucket) subtitlePieces.push(String(sizeBucket))\n if (entity) {\n const primaryEmail = pickValue(entity, 'primary_email', 'primaryEmail')\n if (primaryEmail) subtitlePieces.push(String(primaryEmail))\n }\n const summary = snippet(\n (pickValue(entity, 'description') as string | undefined) ??\n (customFields.summary as string | undefined) ??\n (customFields.description as string | undefined) ??\n (record.summary as string | undefined) ??\n (record.description as string | undefined),\n )\n if (summary) subtitlePieces.push(summary)\n if (!entity && (!title || title === fallbackEntityId)) {\n console.warn('[search.customers] Missing customer entity during company presenter build', {\n recordId: record.id ?? null,\n entityId: fallbackEntityId,\n recordKeys: Object.keys(record),\n })\n }\n return {\n title: String(title),\n subtitle: subtitlePieces.length ? subtitlePieces.join(' \u00B7 ') : undefined,\n icon: 'building',\n badge: pickValue(entity, 'display_name', 'displayName') ? 'Company' : undefined,\n }\n}\n\nfunction logMissingPresenterTitle(\n kind: 'person' | 'company',\n record: Record<string, unknown>,\n entity: Record<string, unknown> | null,\n presenter: SearchResultPresenter,\n) {\n const fallbackId = record.id ?? record.entity_id ?? resolveCustomerEntityId(record)\n if (!fallbackId) return\n if (presenter.title && presenter.title !== String(fallbackId)) return\n console.warn('[search.customers] Presenter fell back to record id', {\n kind,\n recordId: fallbackId,\n entityId: resolveCustomerEntityId(record),\n entityDisplayName: entity?.display_name ?? null,\n })\n}\n\n// =============================================================================\n// Search Module Configuration\n// =============================================================================\n\nexport const searchConfig: SearchModuleConfig = {\n entities: [\n // =========================================================================\n // Person Profile\n // =========================================================================\n {\n entityId: 'customers:customer_person_profile',\n enabled: true,\n priority: 10,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const lines: string[] = []\n const record = ctx.record\n appendLine(lines, 'Preferred name', record.preferred_name ?? record.preferredName ?? ctx.customFields.preferred_name)\n appendLine(lines, 'First name', record.first_name ?? record.firstName ?? ctx.customFields.first_name)\n appendLine(lines, 'Last name', record.last_name ?? record.lastName ?? ctx.customFields.last_name)\n appendLine(lines, 'Job title', record.job_title ?? record.jobTitle ?? ctx.customFields.job_title)\n appendLine(lines, 'Department', record.department ?? record.department_name ?? record.departmentName ?? ctx.customFields.department)\n appendLine(lines, 'Seniority', record.seniority ?? record.seniority_level ?? record.seniorityLevel ?? ctx.customFields.seniority)\n appendLine(lines, 'Timezone', record.timezone ?? record.time_zone ?? record.timeZone ?? ctx.customFields.timezone)\n appendLine(lines, 'LinkedIn', record.linked_in_url ?? record.linkedInUrl ?? ctx.customFields.linked_in_url)\n appendLine(lines, 'Twitter', record.twitter_url ?? record.twitterUrl ?? ctx.customFields.twitter_url)\n\n const { entity, entityId, profileCustomFields, entityCustomFields, entityOnlyCustomFields } =\n await hydrateProfileContext(ctx, 'person')\n appendCustomFieldLines(lines, profileCustomFields, 'Person custom')\n if (Object.keys(entityOnlyCustomFields).length) {\n appendCustomFieldLines(lines, entityOnlyCustomFields, 'Customer custom')\n }\n if (!entity) {\n console.warn('[search.customers] Failed to load customer entity for person profile', {\n recordId: record.id,\n entityId,\n recordKeys: Object.keys(record),\n })\n }\n appendCustomerEntityLines(lines, entity, 'Customer')\n ensureFallbackLines(lines, record)\n if (!lines.length) return null\n\n if (!entityId) {\n console.warn('[search.customers] person profile missing entity id', {\n recordId: record.id,\n recordKeys: Object.keys(record),\n })\n }\n\n const presenter = resolvePersonPresenter(record, entity, ctx.customFields)\n logMissingPresenterTitle('person', record, entity, presenter)\n const presenterLabel = pickLabel(presenter.title) ?? 'Open person'\n const links: SearchResultLink[] = []\n if (entityId) {\n const href = buildCustomerUrl('person', entityId)\n if (href) {\n links.push({ href, label: presenterLabel, kind: 'primary' })\n }\n }\n\n return {\n text: lines,\n presenter,\n links,\n checksumSource: {\n record: ctx.record,\n customFields: profileCustomFields,\n entity,\n entityCustomFields,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const entity = await getCustomerEntity(ctx, resolveCustomerEntityId(ctx.record))\n return resolvePersonPresenter(ctx.record, entity, ctx.customFields)\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n const entityId = resolveCustomerEntityId(ctx.record)\n return buildCustomerUrl('person', entityId)\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const entityId = resolveCustomerEntityId(ctx.record)\n if (!entityId) return null\n const href = buildCustomerUrl('person', entityId)\n if (!href) return null\n return [{ href: `${href}/edit`, label: 'Edit', kind: 'secondary' }]\n },\n\n fieldPolicy: {\n searchable: [\n 'preferred_name',\n 'first_name',\n 'last_name',\n 'job_title',\n 'department',\n 'seniority',\n 'timezone',\n 'linked_in_url',\n 'twitter_url',\n ],\n hashOnly: ['primary_email', 'primary_phone', 'personal_email'],\n excluded: ['date_of_birth', 'government_id', 'ssn', 'tax_id'],\n },\n aclFeatures: ['customers.people.view'],\n },\n\n // =========================================================================\n // Company Profile\n // =========================================================================\n {\n entityId: 'customers:customer_company_profile',\n enabled: true,\n priority: 10,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const lines: string[] = []\n const record = ctx.record\n appendLine(lines, 'Legal name', record.legal_name ?? record.legalName ?? ctx.customFields.legal_name)\n appendLine(lines, 'Brand name', record.brand_name ?? record.brandName ?? ctx.customFields.brand_name)\n appendLine(lines, 'Domain', record.domain ?? record.website_domain ?? record.websiteDomain ?? ctx.customFields.domain)\n appendLine(lines, 'Website', record.website_url ?? record.websiteUrl ?? ctx.customFields.website_url)\n appendLine(lines, 'Industry', record.industry ?? ctx.customFields.industry)\n appendLine(lines, 'Company size', record.size_bucket ?? record.sizeBucket ?? ctx.customFields.size_bucket)\n appendLine(lines, 'Annual revenue', record.annual_revenue ?? record.annualRevenue ?? ctx.customFields.annual_revenue)\n\n const { entity, entityId, profileCustomFields, entityCustomFields, entityOnlyCustomFields } =\n await hydrateProfileContext(ctx, 'company')\n appendCustomFieldLines(lines, profileCustomFields, 'Company custom')\n if (Object.keys(entityOnlyCustomFields).length) {\n appendCustomFieldLines(lines, entityOnlyCustomFields, 'Customer custom')\n }\n appendCustomerEntityLines(lines, entity, 'Primary')\n ensureFallbackLines(lines, record)\n if (!lines.length) return null\n\n const presenter = resolveCompanyPresenter(record, entity, ctx.customFields)\n logMissingPresenterTitle('company', record, entity, presenter)\n const primaryLabel = pickLabel(presenter.title) ?? 'Open company'\n const links: SearchResultLink[] = []\n if (entityId) {\n const href = buildCustomerUrl('company', entityId)\n if (href) {\n links.push({ href, label: primaryLabel, kind: 'primary' })\n }\n }\n\n return {\n text: lines,\n presenter,\n links,\n checksumSource: {\n record: ctx.record,\n customFields: profileCustomFields,\n entity,\n entityCustomFields,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const entity = await getCustomerEntity(ctx, resolveCustomerEntityId(ctx.record))\n return resolveCompanyPresenter(ctx.record, entity, ctx.customFields)\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n const entityId = resolveCustomerEntityId(ctx.record)\n return buildCustomerUrl('company', entityId)\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const entityId = resolveCustomerEntityId(ctx.record)\n if (!entityId) return null\n const href = buildCustomerUrl('company', entityId)\n if (!href) return null\n return [{ href: `${href}/edit`, label: 'Edit', kind: 'secondary' }]\n },\n\n fieldPolicy: {\n searchable: [\n 'legal_name',\n 'brand_name',\n 'display_name',\n 'domain',\n 'website_url',\n 'industry',\n 'size_bucket',\n 'description',\n ],\n hashOnly: ['tax_id', 'registration_number'],\n excluded: ['bank_account', 'billing_info', 'credit_info'],\n },\n aclFeatures: ['customers.companies.view'],\n },\n\n // =========================================================================\n // Customer Comment\n // =========================================================================\n {\n entityId: 'customers:customer_comment',\n enabled: true,\n priority: 6,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const lines: string[] = []\n if (parent?.display_name) lines.push(`Customer: ${parent.display_name}`)\n lines.push(`Note: ${ctx.record.body ?? ''}`)\n if (ctx.record.appearance_icon) lines.push(`Icon: ${ctx.record.appearance_icon}`)\n if (ctx.record.appearance_color) lines.push(`Color: ${ctx.record.appearance_color}`)\n\n const presenter: SearchResultPresenter | undefined = parent?.display_name\n ? {\n title: parent.display_name as string,\n subtitle: snippet(ctx.record.body),\n icon: parent.kind === 'person' ? 'user' : 'building',\n }\n : undefined\n\n return {\n text: lines,\n presenter,\n checksumSource: {\n body: ctx.record.body,\n entityId: ctx.record.entity_id ?? null,\n updatedAt: ctx.record.updated_at ?? ctx.record.updatedAt ?? null,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const title = (parent?.display_name as string | undefined) ?? 'Customer note'\n return {\n title,\n subtitle: snippet(ctx.record.body),\n icon: 'sticky-note',\n }\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const base = buildCustomerUrl(parent?.kind as string ?? null, (parent?.id ?? ctx.record.entity_id ?? ctx.record.entityId) as string)\n return base ? `${base}#notes` : null\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n assertTenantContext(ctx)\n const links: SearchResultLink[] = []\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const parentUrl = buildCustomerUrl(parent?.kind as string ?? null, (parent?.id ?? ctx.record.entity_id ?? ctx.record.entityId) as string)\n if (parentUrl) {\n links.push({ href: parentUrl, label: (parent?.display_name as string | undefined) ?? 'View customer', kind: 'primary' })\n }\n if (ctx.record.deal_id) {\n const dealUrl = `/backend/customers/deals/${encodeURIComponent(ctx.record.deal_id as string)}`\n links.push({ href: dealUrl, label: 'Open deal', kind: 'secondary' })\n }\n return links.length ? links : null\n },\n\n fieldPolicy: {\n searchable: ['body'],\n hashOnly: [],\n excluded: [],\n },\n aclFeatures: ['customers.activities.view'],\n },\n\n // =========================================================================\n // Customer Deal\n // =========================================================================\n {\n entityId: 'customers:customer_deal',\n enabled: true,\n priority: 8,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n const lines: string[] = []\n const record = ctx.record\n appendLine(lines, 'Title', record.title)\n appendLine(lines, 'Stage', record.pipeline_stage)\n appendLine(lines, 'Status', record.status)\n appendLine(lines, 'Source', record.source)\n const value = formatDealValue(record)\n if (value) appendLine(lines, 'Value', value)\n if (!lines.length) return null\n\n const subtitleParts: string[] = []\n if (record.pipeline_stage) subtitleParts.push(String(record.pipeline_stage))\n if (record.status) subtitleParts.push(String(record.status))\n if (value) subtitleParts.push(value)\n\n return {\n text: lines,\n presenter: {\n title: String(record.title ?? 'Deal'),\n subtitle: subtitleParts.join(' \u00B7 ') || undefined,\n icon: 'briefcase',\n badge: 'Deal',\n },\n checksumSource: {\n title: record.title,\n status: record.status,\n stage: record.pipeline_stage,\n value: value,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n const { record } = ctx\n const title = pickString(record.title as string, 'Deal')\n const subtitleParts: string[] = []\n if (record.pipeline_stage) subtitleParts.push(String(record.pipeline_stage))\n if (record.status) subtitleParts.push(String(record.status))\n const amount = record.value_amount ?? record.valueAmount\n const currency = record.value_currency ?? record.valueCurrency\n if (amount) {\n subtitleParts.push(currency ? `${amount} ${currency}` : String(amount))\n }\n\n return {\n title: title ?? 'Deal',\n subtitle: subtitleParts.length ? subtitleParts.join(' \u00B7 ') : undefined,\n icon: 'briefcase',\n badge: 'Deal',\n }\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n const id = ctx.record.id\n if (!id) return null\n return `/backend/customers/deals/${encodeURIComponent(String(id))}`\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const id = ctx.record.id\n if (!id) return null\n return [\n {\n href: `/backend/customers/deals/${encodeURIComponent(String(id))}/edit`,\n label: 'Edit',\n kind: 'secondary',\n },\n ]\n },\n\n fieldPolicy: {\n searchable: ['title', 'description', 'pipeline_stage', 'status', 'source'],\n hashOnly: [],\n excluded: ['value_amount', 'value_currency'],\n },\n aclFeatures: ['customers.deals.view'],\n },\n\n // =========================================================================\n // Customer Activity\n // =========================================================================\n {\n entityId: 'customers:customer_activity',\n enabled: true,\n priority: 5,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const lines: string[] = []\n if (parent?.display_name) lines.push(`Customer: ${parent.display_name}`)\n if (ctx.record.activity_type) lines.push(`Type: ${ctx.record.activity_type}`)\n if (ctx.record.subject) lines.push(`Subject: ${ctx.record.subject}`)\n if (ctx.record.body) lines.push(`Body: ${ctx.record.body}`)\n\n const presenter: SearchResultPresenter = {\n title: ctx.record.subject ? String(ctx.record.subject) : `Activity: ${ctx.record.activity_type ?? 'update'}`,\n subtitle: (parent?.display_name as string | undefined) ?? snippet(ctx.record.body),\n icon: 'bolt',\n }\n\n return {\n text: lines,\n presenter,\n checksumSource: {\n subject: ctx.record.subject,\n body: ctx.record.body,\n entityId: ctx.record.entity_id ?? null,\n updatedAt: ctx.record.updated_at ?? ctx.record.updatedAt ?? null,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n return {\n title: ctx.record.subject ? String(ctx.record.subject) : `Activity: ${ctx.record.activity_type ?? 'update'}`,\n subtitle: (parent?.display_name as string | undefined) ?? snippet(ctx.record.body),\n icon: 'bolt',\n badge: 'Activity',\n }\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const base = buildCustomerUrl(parent?.kind as string ?? null, (parent?.id ?? ctx.record.entity_id ?? ctx.record.entityId) as string)\n return base ? `${base}#activity-${ctx.record.id ?? ctx.record.activity_id ?? ''}` : null\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const links: SearchResultLink[] = []\n if (ctx.record.deal_id) {\n links.push({\n href: `/backend/customers/deals/${encodeURIComponent(ctx.record.deal_id as string)}`,\n label: 'Open deal',\n kind: 'secondary',\n })\n }\n return links.length ? links : null\n },\n\n fieldPolicy: {\n searchable: ['subject', 'body', 'activity_type'],\n hashOnly: [],\n excluded: [],\n },\n aclFeatures: ['customers.activities.view'],\n },\n\n // =========================================================================\n // Customer Todo Link\n // =========================================================================\n {\n entityId: 'customers:customer_todo_link',\n enabled: true,\n priority: 4,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const todo = await getLinkedTodo(ctx) as Record<string, unknown> | null\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const lines: string[] = []\n if (todo?.title) lines.push(`Todo: ${todo.title}`)\n if (todo?.is_done !== undefined) lines.push(`Status: ${todo.is_done ? 'Done' : 'Open'}`)\n if (parent?.display_name) lines.push(`Customer: ${parent.display_name}`)\n if (!lines.length) return null\n\n return {\n text: lines,\n presenter: todo?.title\n ? { title: todo.title as string, subtitle: parent?.display_name as string | undefined, icon: 'check-square' }\n : undefined,\n checksumSource: {\n todoId: ctx.record.todo_id ?? ctx.record.todoId,\n todoSource: ctx.record.todo_source ?? ctx.record.todoSource,\n entityId: ctx.record.entity_id ?? ctx.record.entityId,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n const todo = await getLinkedTodo(ctx) as Record<string, unknown> | null\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n return {\n title: (todo?.title as string | undefined) ?? 'Customer task',\n subtitle: parent?.display_name as string | undefined,\n icon: 'check-square',\n }\n },\n\n resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {\n assertTenantContext(ctx)\n const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)\n const base = buildCustomerUrl(parent?.kind as string ?? null, (parent?.id ?? ctx.record.entity_id ?? ctx.record.entityId) as string)\n return base ? `${base}#tasks` : null\n },\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const todoId = ctx.record.todo_id ?? ctx.record.todoId\n if (!todoId) return null\n return [{\n href: `/backend/todos/${encodeURIComponent(todoId as string)}/edit`,\n label: 'Open todo',\n kind: 'secondary',\n }]\n },\n\n fieldPolicy: {\n searchable: [],\n hashOnly: [],\n excluded: [],\n },\n aclFeatures: ['customers.activities.view'],\n },\n ],\n}\n\nexport default searchConfig\nexport const config = searchConfig\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.5-develop.5187.1.82e5532561",
3
+ "version": "0.6.5-develop.5212.1.b47932beef",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -231,7 +231,7 @@
231
231
  "@mikro-orm/decorators": "^7.1.4",
232
232
  "@mikro-orm/postgresql": "^7.1.4",
233
233
  "@xyflow/react": "^12.11.0",
234
- "ai": "^6.0.197",
234
+ "ai": "^6.0.198",
235
235
  "date-fns": "4.4.0",
236
236
  "date-fns-tz": "^3.2.0",
237
237
  "html-to-text": "^10.0.0",
@@ -239,22 +239,22 @@
239
239
  "pdfjs-dist": "^6.0.227",
240
240
  "resend": "^6.12.4",
241
241
  "sanitize-html": "^2.13.0",
242
- "semver": "^7.8.2",
243
- "svix": "^1.95.1",
242
+ "semver": "^7.8.3",
243
+ "svix": "^1.95.2",
244
244
  "ts-pattern": "^5.0.0",
245
245
  "zod": "^4.4.3"
246
246
  },
247
247
  "peerDependencies": {
248
- "@open-mercato/ai-assistant": "0.6.5-develop.5187.1.82e5532561",
249
- "@open-mercato/shared": "0.6.5-develop.5187.1.82e5532561",
250
- "@open-mercato/ui": "0.6.5-develop.5187.1.82e5532561",
248
+ "@open-mercato/ai-assistant": "0.6.5-develop.5212.1.b47932beef",
249
+ "@open-mercato/shared": "0.6.5-develop.5212.1.b47932beef",
250
+ "@open-mercato/ui": "0.6.5-develop.5212.1.b47932beef",
251
251
  "react": "^19.0.0",
252
252
  "react-dom": "^19.0.0"
253
253
  },
254
254
  "devDependencies": {
255
- "@open-mercato/ai-assistant": "0.6.5-develop.5187.1.82e5532561",
256
- "@open-mercato/shared": "0.6.5-develop.5187.1.82e5532561",
257
- "@open-mercato/ui": "0.6.5-develop.5187.1.82e5532561",
255
+ "@open-mercato/ai-assistant": "0.6.5-develop.5212.1.b47932beef",
256
+ "@open-mercato/shared": "0.6.5-develop.5212.1.b47932beef",
257
+ "@open-mercato/ui": "0.6.5-develop.5212.1.b47932beef",
258
258
  "@testing-library/dom": "^10.4.1",
259
259
  "@testing-library/jest-dom": "^6.9.1",
260
260
  "@testing-library/react": "^16.3.1",
@@ -1,5 +1,5 @@
1
1
  import { promises as fs } from 'fs'
2
- import path from 'path'
2
+ import { resolveContainedPath, resolveLegacyPublicRoot } from '../pathContainment'
3
3
  import type { StorageDriver, StoreFilePayload, StoredFile, ReadFileResult } from './types'
4
4
 
5
5
  export class LegacyPublicStorageDriver implements StorageDriver {
@@ -35,12 +35,6 @@ export class LegacyPublicStorageDriver implements StorageDriver {
35
35
  }
36
36
 
37
37
  private resolveAbsolutePath(storagePath: string): string {
38
- let safeRelative = storagePath.replace(/^\/*/, '')
39
- let prev: string
40
- do {
41
- prev = safeRelative
42
- safeRelative = safeRelative.replace(/\.\.(\/|\\)/g, '')
43
- } while (safeRelative !== prev)
44
- return path.join(process.cwd(), safeRelative)
38
+ return resolveContainedPath(process.cwd(), storagePath, resolveLegacyPublicRoot())
45
39
  }
46
40
  }
@@ -2,6 +2,7 @@ import { promises as fs } from 'fs'
2
2
  import path from 'path'
3
3
  import { randomUUID } from 'crypto'
4
4
  import { resolvePartitionRoot } from '../storage'
5
+ import { resolveContainedPath } from '../pathContainment'
5
6
  import type { StorageDriver, StoreFilePayload, StoredFile, ReadFileResult } from './types'
6
7
 
7
8
  function sanitizeFileName(fileName: string): string {
@@ -67,13 +68,7 @@ export class LocalStorageDriver implements StorageDriver {
67
68
  }
68
69
 
69
70
  private resolveAbsolutePath(partitionCode: string, storagePath: string): string {
70
- let safeRelative = storagePath.replace(/^\/*/, '')
71
- let prev: string
72
- do {
73
- prev = safeRelative
74
- safeRelative = safeRelative.replace(/\.\.(\/|\\)/g, '')
75
- } while (safeRelative !== prev)
76
71
  const root = resolvePartitionRoot(partitionCode)
77
- return path.join(root, safeRelative)
72
+ return resolveContainedPath(root, storagePath)
78
73
  }
79
74
  }
@@ -0,0 +1,42 @@
1
+ import path from 'path'
2
+
3
+ /**
4
+ * Reduce a stored relative path to safe segments: drop leading slashes, empty
5
+ * segments, `.`, and `..`. The result can never contain a traversal segment,
6
+ * so joining it onto a root can never climb above that root.
7
+ */
8
+ export function sanitizeStorageRelativePath(storagePath: string): string {
9
+ return String(storagePath ?? '')
10
+ .split(/[\\/]+/)
11
+ .filter((segment) => segment.length > 0 && segment !== '.' && segment !== '..')
12
+ .join(path.sep)
13
+ }
14
+
15
+ /**
16
+ * Resolve a stored path against `joinRoot` and assert the result stays within
17
+ * `containmentRoot` (defaults to `joinRoot`). Throws when the resolved path
18
+ * escapes the boundary — e.g. a legacy row whose path points outside `public/`.
19
+ */
20
+ export function resolveContainedPath(
21
+ joinRoot: string,
22
+ storagePath: string,
23
+ containmentRoot?: string,
24
+ ): string {
25
+ const base = path.resolve(joinRoot)
26
+ const boundary = path.resolve(containmentRoot ?? joinRoot)
27
+ const candidate = path.resolve(base, sanitizeStorageRelativePath(storagePath))
28
+ const relativeToBoundary = path.relative(boundary, candidate)
29
+ if (relativeToBoundary.startsWith('..') || path.isAbsolute(relativeToBoundary)) {
30
+ throw new Error('[internal] attachment storage path escapes its containment root')
31
+ }
32
+ return candidate
33
+ }
34
+
35
+ /**
36
+ * The fixed sub-root that `legacyPublic` rows are allowed to resolve within.
37
+ * Stored paths include the `public/` prefix (see Migration20251117181353), so
38
+ * they are joined onto `process.cwd()` but constrained to `process.cwd()/public`.
39
+ */
40
+ export function resolveLegacyPublicRoot(): string {
41
+ return path.join(process.cwd(), 'public')
42
+ }
@@ -2,6 +2,7 @@ import { promises as fs } from 'fs'
2
2
  import path from 'path'
3
3
  import { randomUUID } from 'crypto'
4
4
  import { resolvePartitionEnvKey } from './partitionEnv'
5
+ import { resolveContainedPath, resolveLegacyPublicRoot } from './pathContainment'
5
6
 
6
7
  export function resolvePartitionRoot(code: string): string {
7
8
  const envKey = resolvePartitionEnvKey(code)
@@ -74,18 +75,11 @@ export function resolveAttachmentAbsolutePath(
74
75
  storagePath: string,
75
76
  storageDriver?: string | null
76
77
  ): string {
77
- // Remove leading slashes first
78
- let safeRelative = storagePath.replace(/^\/*/, '')
79
- // Remove all ../ (and ..\) path traversal segments, repeatedly until gone
80
- do {
81
- var prev = safeRelative
82
- safeRelative = safeRelative.replace(/\.\.(\/|\\)/g, '')
83
- } while (safeRelative !== prev)
84
78
  if (storageDriver === 'legacyPublic') {
85
- return path.join(process.cwd(), safeRelative)
79
+ return resolveContainedPath(process.cwd(), storagePath, resolveLegacyPublicRoot())
86
80
  }
87
81
  const root = resolvePartitionRoot(partitionCode)
88
- return path.join(root, safeRelative)
82
+ return resolveContainedPath(root, storagePath)
89
83
  }
90
84
 
91
85
  /**
@@ -16,6 +16,7 @@ import { applyCustomFieldVisibility } from '@open-mercato/ui/backend/utils/custo
16
16
  import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
17
17
  import type { FilterOption } from '@open-mercato/ui/backend/FilterOverlay'
18
18
  import { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'
19
+ import { markdownToPlainText } from '@open-mercato/ui/backend/markdown/markdownToPlainText'
19
20
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
20
21
  import { useT } from '@open-mercato/shared/lib/i18n/context'
21
22
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
@@ -416,7 +417,7 @@ export default function ProductsDataTable({
416
417
  <span className="text-xs text-muted-foreground">/{row.original.handle}</span>
417
418
  ) : null}
418
419
  {row.original.description ? (
419
- <span className="text-xs text-muted-foreground">{row.original.description}</span>
420
+ <span className="text-xs text-muted-foreground">{markdownToPlainText(row.original.description)}</span>
420
421
  ) : null}
421
422
  </div>
422
423
  ),
@@ -363,9 +363,9 @@ async function getLinkedTodo(ctx: SearchContext) {
363
363
  function buildCustomerUrl(kind: string | null | undefined, id?: string | null): string | null {
364
364
  if (!id) return null
365
365
  const encoded = encodeURIComponent(id)
366
- if (kind === 'person') return `/backend/customers/people/${encoded}`
367
- if (kind === 'company') return `/backend/customers/companies/${encoded}`
368
- return `/backend/customers/companies/${encoded}`
366
+ if (kind === 'person') return `/backend/customers/people-v2/${encoded}`
367
+ if (kind === 'company') return `/backend/customers/companies-v2/${encoded}`
368
+ return `/backend/customers/companies-v2/${encoded}`
369
369
  }
370
370
 
371
371
  function formatDealValue(record: Record<string, unknown>): string | undefined {