@open-mercato/core 0.6.3-develop.3876.1.d40fe4ec2d → 0.6.3-develop.3881.1.0b590ac4eb

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.
Files changed (107) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/attachments/api/file/[id]/route.js +7 -2
  3. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  4. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -4
  5. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  6. package/dist/modules/audit_logs/services/accessLogService.js +127 -8
  7. package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
  8. package/dist/modules/auth/di.js +17 -3
  9. package/dist/modules/auth/di.js.map +2 -2
  10. package/dist/modules/auth/services/rbacDefaultCache.js +110 -0
  11. package/dist/modules/auth/services/rbacDefaultCache.js.map +7 -0
  12. package/dist/modules/currencies/api/currencies/route.js +3 -4
  13. package/dist/modules/currencies/api/currencies/route.js.map +2 -2
  14. package/dist/modules/currencies/api/exchange-rates/route.js +3 -4
  15. package/dist/modules/currencies/api/exchange-rates/route.js.map +2 -2
  16. package/dist/modules/customers/api/people/route.js +26 -24
  17. package/dist/modules/customers/api/people/route.js.map +2 -2
  18. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +26 -0
  19. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +7 -0
  20. package/dist/modules/directory/utils/organizationScope.js +85 -0
  21. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  22. package/dist/modules/workflows/backend/definitions/[id]/page.js +2 -1
  23. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  24. package/dist/modules/workflows/backend/definitions/create/page.js +4 -2
  25. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  26. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +20 -3
  27. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  28. package/dist/modules/workflows/components/ActivitiesEditor.js +34 -1
  29. package/dist/modules/workflows/components/ActivitiesEditor.js.map +2 -2
  30. package/dist/modules/workflows/components/NodeEditDialog.js +153 -17
  31. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  32. package/dist/modules/workflows/components/StepsEditor.js +31 -0
  33. package/dist/modules/workflows/components/StepsEditor.js.map +2 -2
  34. package/dist/modules/workflows/components/WorkflowGraph.js +3 -2
  35. package/dist/modules/workflows/components/WorkflowGraph.js.map +2 -2
  36. package/dist/modules/workflows/components/nodes/WaitForTimerNode.js +54 -0
  37. package/dist/modules/workflows/components/nodes/WaitForTimerNode.js.map +7 -0
  38. package/dist/modules/workflows/components/nodes/index.js +3 -1
  39. package/dist/modules/workflows/components/nodes/index.js.map +2 -2
  40. package/dist/modules/workflows/data/validators.js +117 -0
  41. package/dist/modules/workflows/data/validators.js.map +2 -2
  42. package/dist/modules/workflows/di.js +5 -1
  43. package/dist/modules/workflows/di.js.map +2 -2
  44. package/dist/modules/workflows/lib/activity-executor.js +42 -1
  45. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  46. package/dist/modules/workflows/lib/activity-queue-types.js.map +2 -2
  47. package/dist/modules/workflows/lib/activity-worker-handler.js +24 -0
  48. package/dist/modules/workflows/lib/activity-worker-handler.js.map +2 -2
  49. package/dist/modules/workflows/lib/duration.js +32 -0
  50. package/dist/modules/workflows/lib/duration.js.map +7 -0
  51. package/dist/modules/workflows/lib/event-logger.js +1 -0
  52. package/dist/modules/workflows/lib/event-logger.js.map +2 -2
  53. package/dist/modules/workflows/lib/format-validation-error.js +12 -0
  54. package/dist/modules/workflows/lib/format-validation-error.js.map +7 -0
  55. package/dist/modules/workflows/lib/graph-utils.js +6 -3
  56. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  57. package/dist/modules/workflows/lib/node-type-icons.js +9 -5
  58. package/dist/modules/workflows/lib/node-type-icons.js.map +2 -2
  59. package/dist/modules/workflows/lib/signal-handler.js +55 -23
  60. package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
  61. package/dist/modules/workflows/lib/step-handler.js +79 -29
  62. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  63. package/dist/modules/workflows/lib/timer-handler.js +159 -0
  64. package/dist/modules/workflows/lib/timer-handler.js.map +7 -0
  65. package/dist/modules/workflows/lib/workflow-executor.js +1 -1
  66. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  67. package/dist/modules/workflows/workers/workflow-activities.worker.js +20 -4
  68. package/dist/modules/workflows/workers/workflow-activities.worker.js.map +2 -2
  69. package/package.json +7 -7
  70. package/src/modules/attachments/api/file/[id]/route.ts +7 -2
  71. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -4
  72. package/src/modules/audit_logs/services/accessLogService.ts +179 -15
  73. package/src/modules/auth/di.ts +26 -3
  74. package/src/modules/auth/services/rbacDefaultCache.ts +145 -0
  75. package/src/modules/currencies/api/currencies/route.ts +3 -4
  76. package/src/modules/currencies/api/exchange-rates/route.ts +3 -4
  77. package/src/modules/customers/api/people/route.ts +27 -25
  78. package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +39 -0
  79. package/src/modules/directory/utils/organizationScope.ts +121 -0
  80. package/src/modules/workflows/backend/definitions/[id]/page.tsx +3 -2
  81. package/src/modules/workflows/backend/definitions/create/page.tsx +4 -2
  82. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +18 -1
  83. package/src/modules/workflows/components/ActivitiesEditor.tsx +40 -0
  84. package/src/modules/workflows/components/NodeEditDialog.tsx +218 -30
  85. package/src/modules/workflows/components/StepsEditor.tsx +36 -0
  86. package/src/modules/workflows/components/WorkflowGraph.tsx +2 -1
  87. package/src/modules/workflows/components/nodes/WaitForTimerNode.tsx +70 -0
  88. package/src/modules/workflows/components/nodes/index.ts +3 -0
  89. package/src/modules/workflows/data/validators.ts +121 -0
  90. package/src/modules/workflows/di.ts +4 -0
  91. package/src/modules/workflows/i18n/de.json +10 -1
  92. package/src/modules/workflows/i18n/en.json +10 -1
  93. package/src/modules/workflows/i18n/es.json +10 -1
  94. package/src/modules/workflows/i18n/pl.json +10 -1
  95. package/src/modules/workflows/lib/activity-executor.ts +86 -2
  96. package/src/modules/workflows/lib/activity-queue-types.ts +18 -11
  97. package/src/modules/workflows/lib/activity-worker-handler.ts +29 -0
  98. package/src/modules/workflows/lib/duration.ts +51 -0
  99. package/src/modules/workflows/lib/event-logger.ts +1 -0
  100. package/src/modules/workflows/lib/format-validation-error.ts +30 -0
  101. package/src/modules/workflows/lib/graph-utils.ts +3 -0
  102. package/src/modules/workflows/lib/node-type-icons.ts +6 -2
  103. package/src/modules/workflows/lib/signal-handler.ts +62 -24
  104. package/src/modules/workflows/lib/step-handler.ts +107 -50
  105. package/src/modules/workflows/lib/timer-handler.ts +213 -0
  106. package/src/modules/workflows/lib/workflow-executor.ts +1 -1
  107. package/src/modules/workflows/workers/workflow-activities.worker.ts +33 -7
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/directory/utils/organizationScope.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport type { AwilixContainer } from 'awilix'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { parseSelectedOrganizationCookie, parseSelectedTenantCookie } from './scopeCookies'\n\nexport { parseSelectedOrganizationCookie, parseSelectedTenantCookie }\n\nexport type OrganizationScope = {\n selectedId: string | null\n filterIds: string[] | null\n allowedIds: string[] | null\n tenantId: string | null\n}\n\nfunction normalizeOrganizationId(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nexport function getSelectedOrganizationFromRequest(req: Request | { cookies?: { get: (name: string) => { value: string } | undefined }; headers?: { get(name: string): string | null } }): string | null {\n const cookieContainer = (req as { cookies?: { get: (name: string) => { value: string } | undefined } }).cookies\n if (cookieContainer && typeof cookieContainer.get === 'function') {\n const val = cookieContainer.get('om_selected_org')?.value\n return val ?? null\n }\n const headerContainer = (req as { headers?: { get(name: string): string | null } }).headers\n const header = typeof headerContainer?.get === 'function' ? headerContainer.get('cookie') : null\n return parseSelectedOrganizationCookie(header)\n}\n\nexport function getSelectedTenantFromRequest(\n req: Request | { cookies?: { get: (name: string) => { value: string } | undefined }; headers?: { get(name: string): string | null } },\n): string | null {\n const cookieContainer = (req as { cookies?: { get: (name: string) => { value: string } | undefined } }).cookies\n if (cookieContainer && typeof cookieContainer.get === 'function') {\n const val = cookieContainer.get('om_selected_tenant')?.value\n return val ?? null\n }\n const headerContainer = (req as { headers?: { get(name: string): string | null } }).headers\n const header = typeof headerContainer?.get === 'function' ? headerContainer.get('cookie') : null\n return parseSelectedTenantCookie(header)\n}\n\nasync function collectWithDescendants(em: EntityManager, tenantId: string, ids: string[]): Promise<Set<string>> {\n if (!ids.length) return new Set()\n const unique = Array.from(new Set(\n ids.map((value) => normalizeOrganizationId(value)).filter((value): value is string => {\n if (!value) return false\n if (isAllOrganizationsSelection(value)) return false\n return true\n })\n ))\n if (!unique.length) return new Set()\n const filter: FilterQuery<Organization> = {\n tenant: tenantId,\n id: { $in: unique },\n deletedAt: null,\n }\n const orgs = await em.find(Organization, filter)\n const set = new Set<string>()\n for (const org of orgs) {\n const id = String(org.id)\n set.add(id)\n if (Array.isArray(org.descendantIds)) {\n for (const desc of org.descendantIds) set.add(String(desc))\n }\n }\n return set\n}\n\nexport async function resolveOrganizationScope({\n em,\n rbac,\n auth,\n selectedId,\n tenantId: tenantIdOverride,\n}: {\n em: EntityManager\n rbac: RbacService\n auth: AuthContext\n selectedId?: string | null\n tenantId?: string | null\n}): Promise<OrganizationScope> {\n if (!auth || !auth.sub) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n const actorTenantId = typeof auth.tenantId === 'string' && auth.tenantId.trim().length > 0 ? auth.tenantId.trim() : null\n const candidateTenantId = typeof tenantIdOverride === 'string' && tenantIdOverride.trim().length > 0\n ? tenantIdOverride.trim()\n : tenantIdOverride === null\n ? null\n : actorTenantId\n if (!candidateTenantId) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n const usingOverride = candidateTenantId !== actorTenantId\n const isSuperAdminActor = auth.isSuperAdmin === true\n const tenantId = usingOverride && actorTenantId && !isSuperAdminActor ? actorTenantId : candidateTenantId\n if (!tenantId) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n const normalizedRequestedSelection = normalizeOrganizationId(selectedId)\n const explicitAllOrgsChoice =\n normalizedRequestedSelection !== null && isAllOrganizationsSelection(normalizedRequestedSelection)\n const normalizedSelectedId = explicitAllOrgsChoice\n ? null\n : normalizedRequestedSelection\n const contextOrgId = actorTenantId && actorTenantId === tenantId ? normalizeOrganizationId(auth.orgId) : null\n const acl = await rbac.loadAcl(auth.sub, { tenantId, organizationId: contextOrgId })\n const aclIsSuperAdmin = acl?.isSuperAdmin === true\n const effectiveSuperAdmin = aclIsSuperAdmin || isSuperAdminActor\n const normalizedAccessible = effectiveSuperAdmin\n ? null\n : Array.isArray(acl?.organizations)\n ? acl.organizations\n .map((value) => normalizeOrganizationId(value))\n .filter((value): value is string => value !== null)\n : null\n const accessibleList = effectiveSuperAdmin\n ? null\n : normalizedAccessible && normalizedAccessible.some((value) => isAllOrganizationsSelection(value))\n ? null\n : normalizedAccessible?.filter((value) => !isAllOrganizationsSelection(value)) ?? null\n\n const accountOrgId = actorTenantId && actorTenantId === tenantId ? normalizeOrganizationId(auth.orgId) : null\n const fallbackOrgId = accountOrgId ?? null\n let fallbackSet: Set<string> | null = null\n const loadFallbackSet = async (): Promise<Set<string> | null> => {\n if (!fallbackOrgId) return null\n if (!fallbackSet) {\n fallbackSet = await collectWithDescendants(em, tenantId, [fallbackOrgId])\n }\n return fallbackSet\n }\n\n let allowedSet: Set<string> | null = null\n if (accessibleList === null) {\n allowedSet = null\n } else if (accessibleList.length === 0) {\n allowedSet = new Set()\n } else {\n allowedSet = await collectWithDescendants(em, tenantId, accessibleList)\n }\n\n if (allowedSet && allowedSet.size === 0 && fallbackOrgId) {\n const computed = await loadFallbackSet()\n if (computed && computed.size > 0) {\n allowedSet = computed\n }\n }\n\n const hasUnrestrictedAccess = effectiveSuperAdmin || (accessibleList === null)\n const noOrgSelection = normalizedSelectedId === null && !explicitAllOrgsChoice\n const widenToAllOrgs =\n (explicitAllOrgsChoice && hasUnrestrictedAccess)\n || (effectiveSuperAdmin && noOrgSelection)\n const initialSelected =\n normalizedSelectedId\n ?? (widenToAllOrgs ? null : accountOrgId ?? null)\n let effectiveSelected: string | null = null\n if (initialSelected) {\n if (allowedSet === null || allowedSet.has(initialSelected)) {\n effectiveSelected = initialSelected\n }\n }\n\n let filterSet: Set<string> | null = null\n if (effectiveSelected) {\n filterSet = await collectWithDescendants(em, tenantId, [effectiveSelected])\n } else if (allowedSet !== null) {\n filterSet = allowedSet\n } else if (widenToAllOrgs) {\n filterSet = null\n } else if (auth.orgId) {\n filterSet = await loadFallbackSet()\n }\n\n if ((!filterSet || filterSet.size === 0) && fallbackOrgId && !widenToAllOrgs) {\n const computed = await loadFallbackSet()\n if (computed && computed.size > 0) {\n filterSet = computed\n if (!effectiveSelected) {\n effectiveSelected = fallbackOrgId\n }\n }\n }\n\n return {\n selectedId: effectiveSelected,\n filterIds: filterSet ? Array.from(filterSet) : null,\n allowedIds: allowedSet ? Array.from(allowedSet) : null,\n tenantId,\n }\n}\n\nexport async function resolveOrganizationScopeForRequest({\n container,\n auth,\n request,\n selectedId,\n tenantId: tenantOverride,\n}: {\n container: AwilixContainer\n auth: AuthContext | null | undefined\n request?: Request | { cookies?: { get: (name: string) => { value: string } | undefined }; headers?: { get(name: string): string | null } }\n selectedId?: string | null\n tenantId?: string | null\n}): Promise<OrganizationScope> {\n if (!auth || !auth.sub) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n\n let em: EntityManager | null = null\n let rbac: RbacService | null = null\n try { em = container.resolve<EntityManager>('em') } catch { em = null }\n try { rbac = container.resolve<RbacService>('rbacService') } catch { rbac = null }\n const normalizeString = (value: unknown): string | null => {\n if (typeof value === 'string' && value.trim().length > 0) return value.trim()\n return null\n }\n if (!em || !rbac) {\n const fallbackSelected = normalizeOrganizationId(selectedId ?? auth.orgId ?? null)\n return {\n selectedId: fallbackSelected,\n filterIds: fallbackSelected ? [fallbackSelected] : null,\n allowedIds: fallbackSelected ? [fallbackSelected] : null,\n tenantId: normalizeString(auth.tenantId),\n }\n }\n\n const actorTenantField = (auth as { actorTenantId?: string | null }).actorTenantId\n const actorTenant = actorTenantField === undefined\n ? normalizeString(auth.tenantId)\n : actorTenantField === null\n ? null\n : normalizeString(actorTenantField)\n const actorOrgField = (auth as { actorOrgId?: string | null }).actorOrgId\n const actorOrgId = actorOrgField === undefined\n ? normalizeString(auth.orgId)\n : actorOrgField === null\n ? null\n : normalizeString(actorOrgField)\n\n const cookieTenant = request ? getSelectedTenantFromRequest(request) : null\n const requestedTenant =\n tenantOverride !== undefined\n ? tenantOverride\n : cookieTenant !== undefined\n ? cookieTenant\n : undefined\n const requestedTenantId = typeof requestedTenant === 'string' && requestedTenant.trim().length > 0 ? requestedTenant.trim() : null\n const isSuperAdminActor = auth.isSuperAdmin === true\n let effectiveTenantId = requestedTenantId ?? actorTenant ?? null\n if (actorTenant && effectiveTenantId && effectiveTenantId !== actorTenant && !isSuperAdminActor) {\n effectiveTenantId = actorTenant\n }\n if (!effectiveTenantId) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n\n const scopedAuth = {\n ...auth,\n tenantId: effectiveTenantId,\n orgId: actorTenant && actorTenant === effectiveTenantId ? actorOrgId ?? null : null,\n }\n\n const rawSelected = selectedId !== undefined ? selectedId : (request ? getSelectedOrganizationFromRequest(request) : null)\n const baseScope = await resolveOrganizationScope({\n em,\n rbac,\n auth: scopedAuth,\n selectedId: rawSelected,\n tenantId: effectiveTenantId,\n })\n\n return baseScope\n}\n\nexport type FeatureCheckContext = {\n organizationId: string | null\n scope: OrganizationScope\n allowedOrganizationIds: string[] | null\n}\n\nexport async function resolveFeatureCheckContext({\n container,\n auth,\n request,\n selectedId,\n tenantId,\n}: {\n container: AwilixContainer\n auth: AuthContext | null | undefined\n request?: Request | { cookies?: { get: (name: string) => { value: string } | undefined } }\n selectedId?: string | null\n tenantId?: string | null\n}): Promise<FeatureCheckContext> {\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request, selectedId, tenantId })\n const allowedOrganizationIds = scope.allowedIds ?? null\n const authOrgId = auth?.orgId ?? null\n const organizationId =\n scope.selectedId\n ?? (authOrgId && (!Array.isArray(allowedOrganizationIds) || allowedOrganizationIds.includes(authOrgId)) ? authOrgId : null)\n ?? (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length ? allowedOrganizationIds[0] : null)\n\n return { organizationId, scope, allowedOrganizationIds }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport type { AwilixContainer } from 'awilix'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport { parseSelectedOrganizationCookie, parseSelectedTenantCookie } from './scopeCookies'\n\nexport { parseSelectedOrganizationCookie, parseSelectedTenantCookie }\n\nexport type OrganizationScope = {\n selectedId: string | null\n filterIds: string[] | null\n allowedIds: string[] | null\n tenantId: string | null\n}\n\n// Phase 4 \u2014 short-TTL cache for resolveOrganizationScopeForRequest.\n// OrganizationScope is a pure function of (userId, tenantId, selectedOrgId,\n// requestedTenant) between membership changes; caching it bypasses 1\n// SELECT on `organizations` per CRUD request. TTL is short (60s default)\n// to keep staleness bounded for membership/visibility changes. Tag-based\n// invalidation kicks the cache when user_organizations or organizations\n// mutate (wired via invalidateOrganizationScopeCacheFor).\nconst ORG_SCOPE_CACHE_KEY_PREFIX = 'org-scope'\n// Phase 4 default-off until the same readiness probe (`GET /api/customers/people`)\n// stays green with the cache layer engaged. Set `OM_ORG_SCOPE_CACHE_TTL_MS=60000`\n// (or any positive integer) to opt in once cross-request safety is re-verified.\nconst ORG_SCOPE_DEFAULT_TTL_MS = 0\n\nfunction resolveOrgScopeTtlMs(): number {\n const raw = process.env.OM_ORG_SCOPE_CACHE_TTL_MS\n if (raw === undefined) return ORG_SCOPE_DEFAULT_TTL_MS\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed < 0) return ORG_SCOPE_DEFAULT_TTL_MS\n return parsed\n}\n\nfunction buildOrgScopeCacheKey(parts: {\n userId: string\n effectiveTenantId: string\n selectedOrgId: string | null\n requestedTenantId: string | null\n}): string {\n const selected = parts.selectedOrgId ?? 'none'\n const requested = parts.requestedTenantId ?? 'none'\n return `${ORG_SCOPE_CACHE_KEY_PREFIX}:${parts.userId}:${parts.effectiveTenantId}:${selected}:${requested}`\n}\n\nfunction buildOrgScopeCacheTags(parts: { userId: string; effectiveTenantId: string }): string[] {\n return [\n `${ORG_SCOPE_CACHE_KEY_PREFIX}:user:${parts.userId}`,\n `${ORG_SCOPE_CACHE_KEY_PREFIX}:tenant:${parts.effectiveTenantId}`,\n ]\n}\n\nfunction isValidCachedScope(value: unknown): value is OrganizationScope {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Partial<OrganizationScope>\n const idOk = (v: unknown) => v === null || typeof v === 'string'\n const arrOk = (v: unknown) => v === null || (Array.isArray(v) && v.every((entry) => typeof entry === 'string'))\n return idOk(record.selectedId) && idOk(record.tenantId) && arrOk(record.filterIds) && arrOk(record.allowedIds)\n}\n\nfunction resolveCacheFromContainer(container: AwilixContainer | null | undefined): CacheStrategy | null {\n if (!container) return null\n try {\n const c = container.resolve('cache') as CacheStrategy | undefined\n if (c && typeof c.get === 'function' && typeof c.set === 'function') return c\n } catch {\n return null\n }\n return null\n}\n\nexport async function invalidateOrganizationScopeCacheForUser(\n container: AwilixContainer,\n userId: string,\n): Promise<void> {\n const cache = resolveCacheFromContainer(container)\n if (!cache?.deleteByTags) return\n try {\n await cache.deleteByTags([`${ORG_SCOPE_CACHE_KEY_PREFIX}:user:${userId}`])\n } catch (err) {\n console.warn('[org-scope:cache] invalidate user failed', err)\n }\n}\n\nexport async function invalidateOrganizationScopeCacheForTenant(\n container: AwilixContainer,\n tenantId: string,\n): Promise<void> {\n const cache = resolveCacheFromContainer(container)\n if (!cache?.deleteByTags) return\n try {\n await cache.deleteByTags([`${ORG_SCOPE_CACHE_KEY_PREFIX}:tenant:${tenantId}`])\n } catch (err) {\n console.warn('[org-scope:cache] invalidate tenant failed', err)\n }\n}\n\nfunction normalizeOrganizationId(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nexport function getSelectedOrganizationFromRequest(req: Request | { cookies?: { get: (name: string) => { value: string } | undefined }; headers?: { get(name: string): string | null } }): string | null {\n const cookieContainer = (req as { cookies?: { get: (name: string) => { value: string } | undefined } }).cookies\n if (cookieContainer && typeof cookieContainer.get === 'function') {\n const val = cookieContainer.get('om_selected_org')?.value\n return val ?? null\n }\n const headerContainer = (req as { headers?: { get(name: string): string | null } }).headers\n const header = typeof headerContainer?.get === 'function' ? headerContainer.get('cookie') : null\n return parseSelectedOrganizationCookie(header)\n}\n\nexport function getSelectedTenantFromRequest(\n req: Request | { cookies?: { get: (name: string) => { value: string } | undefined }; headers?: { get(name: string): string | null } },\n): string | null {\n const cookieContainer = (req as { cookies?: { get: (name: string) => { value: string } | undefined } }).cookies\n if (cookieContainer && typeof cookieContainer.get === 'function') {\n const val = cookieContainer.get('om_selected_tenant')?.value\n return val ?? null\n }\n const headerContainer = (req as { headers?: { get(name: string): string | null } }).headers\n const header = typeof headerContainer?.get === 'function' ? headerContainer.get('cookie') : null\n return parseSelectedTenantCookie(header)\n}\n\nasync function collectWithDescendants(em: EntityManager, tenantId: string, ids: string[]): Promise<Set<string>> {\n if (!ids.length) return new Set()\n const unique = Array.from(new Set(\n ids.map((value) => normalizeOrganizationId(value)).filter((value): value is string => {\n if (!value) return false\n if (isAllOrganizationsSelection(value)) return false\n return true\n })\n ))\n if (!unique.length) return new Set()\n const filter: FilterQuery<Organization> = {\n tenant: tenantId,\n id: { $in: unique },\n deletedAt: null,\n }\n const orgs = await em.find(Organization, filter)\n const set = new Set<string>()\n for (const org of orgs) {\n const id = String(org.id)\n set.add(id)\n if (Array.isArray(org.descendantIds)) {\n for (const desc of org.descendantIds) set.add(String(desc))\n }\n }\n return set\n}\n\nexport async function resolveOrganizationScope({\n em,\n rbac,\n auth,\n selectedId,\n tenantId: tenantIdOverride,\n}: {\n em: EntityManager\n rbac: RbacService\n auth: AuthContext\n selectedId?: string | null\n tenantId?: string | null\n}): Promise<OrganizationScope> {\n if (!auth || !auth.sub) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n const actorTenantId = typeof auth.tenantId === 'string' && auth.tenantId.trim().length > 0 ? auth.tenantId.trim() : null\n const candidateTenantId = typeof tenantIdOverride === 'string' && tenantIdOverride.trim().length > 0\n ? tenantIdOverride.trim()\n : tenantIdOverride === null\n ? null\n : actorTenantId\n if (!candidateTenantId) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n const usingOverride = candidateTenantId !== actorTenantId\n const isSuperAdminActor = auth.isSuperAdmin === true\n const tenantId = usingOverride && actorTenantId && !isSuperAdminActor ? actorTenantId : candidateTenantId\n if (!tenantId) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n const normalizedRequestedSelection = normalizeOrganizationId(selectedId)\n const explicitAllOrgsChoice =\n normalizedRequestedSelection !== null && isAllOrganizationsSelection(normalizedRequestedSelection)\n const normalizedSelectedId = explicitAllOrgsChoice\n ? null\n : normalizedRequestedSelection\n const contextOrgId = actorTenantId && actorTenantId === tenantId ? normalizeOrganizationId(auth.orgId) : null\n const acl = await rbac.loadAcl(auth.sub, { tenantId, organizationId: contextOrgId })\n const aclIsSuperAdmin = acl?.isSuperAdmin === true\n const effectiveSuperAdmin = aclIsSuperAdmin || isSuperAdminActor\n const normalizedAccessible = effectiveSuperAdmin\n ? null\n : Array.isArray(acl?.organizations)\n ? acl.organizations\n .map((value) => normalizeOrganizationId(value))\n .filter((value): value is string => value !== null)\n : null\n const accessibleList = effectiveSuperAdmin\n ? null\n : normalizedAccessible && normalizedAccessible.some((value) => isAllOrganizationsSelection(value))\n ? null\n : normalizedAccessible?.filter((value) => !isAllOrganizationsSelection(value)) ?? null\n\n const accountOrgId = actorTenantId && actorTenantId === tenantId ? normalizeOrganizationId(auth.orgId) : null\n const fallbackOrgId = accountOrgId ?? null\n let fallbackSet: Set<string> | null = null\n const loadFallbackSet = async (): Promise<Set<string> | null> => {\n if (!fallbackOrgId) return null\n if (!fallbackSet) {\n fallbackSet = await collectWithDescendants(em, tenantId, [fallbackOrgId])\n }\n return fallbackSet\n }\n\n let allowedSet: Set<string> | null = null\n if (accessibleList === null) {\n allowedSet = null\n } else if (accessibleList.length === 0) {\n allowedSet = new Set()\n } else {\n allowedSet = await collectWithDescendants(em, tenantId, accessibleList)\n }\n\n if (allowedSet && allowedSet.size === 0 && fallbackOrgId) {\n const computed = await loadFallbackSet()\n if (computed && computed.size > 0) {\n allowedSet = computed\n }\n }\n\n const hasUnrestrictedAccess = effectiveSuperAdmin || (accessibleList === null)\n const noOrgSelection = normalizedSelectedId === null && !explicitAllOrgsChoice\n const widenToAllOrgs =\n (explicitAllOrgsChoice && hasUnrestrictedAccess)\n || (effectiveSuperAdmin && noOrgSelection)\n const initialSelected =\n normalizedSelectedId\n ?? (widenToAllOrgs ? null : accountOrgId ?? null)\n let effectiveSelected: string | null = null\n if (initialSelected) {\n if (allowedSet === null || allowedSet.has(initialSelected)) {\n effectiveSelected = initialSelected\n }\n }\n\n let filterSet: Set<string> | null = null\n if (effectiveSelected) {\n filterSet = await collectWithDescendants(em, tenantId, [effectiveSelected])\n } else if (allowedSet !== null) {\n filterSet = allowedSet\n } else if (widenToAllOrgs) {\n filterSet = null\n } else if (auth.orgId) {\n filterSet = await loadFallbackSet()\n }\n\n if ((!filterSet || filterSet.size === 0) && fallbackOrgId && !widenToAllOrgs) {\n const computed = await loadFallbackSet()\n if (computed && computed.size > 0) {\n filterSet = computed\n if (!effectiveSelected) {\n effectiveSelected = fallbackOrgId\n }\n }\n }\n\n return {\n selectedId: effectiveSelected,\n filterIds: filterSet ? Array.from(filterSet) : null,\n allowedIds: allowedSet ? Array.from(allowedSet) : null,\n tenantId,\n }\n}\n\nexport async function resolveOrganizationScopeForRequest({\n container,\n auth,\n request,\n selectedId,\n tenantId: tenantOverride,\n}: {\n container: AwilixContainer\n auth: AuthContext | null | undefined\n request?: Request | { cookies?: { get: (name: string) => { value: string } | undefined }; headers?: { get(name: string): string | null } }\n selectedId?: string | null\n tenantId?: string | null\n}): Promise<OrganizationScope> {\n if (!auth || !auth.sub) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n\n let em: EntityManager | null = null\n let rbac: RbacService | null = null\n try { em = container.resolve<EntityManager>('em') } catch { em = null }\n try { rbac = container.resolve<RbacService>('rbacService') } catch { rbac = null }\n const normalizeString = (value: unknown): string | null => {\n if (typeof value === 'string' && value.trim().length > 0) return value.trim()\n return null\n }\n if (!em || !rbac) {\n const fallbackSelected = normalizeOrganizationId(selectedId ?? auth.orgId ?? null)\n return {\n selectedId: fallbackSelected,\n filterIds: fallbackSelected ? [fallbackSelected] : null,\n allowedIds: fallbackSelected ? [fallbackSelected] : null,\n tenantId: normalizeString(auth.tenantId),\n }\n }\n\n const actorTenantField = (auth as { actorTenantId?: string | null }).actorTenantId\n const actorTenant = actorTenantField === undefined\n ? normalizeString(auth.tenantId)\n : actorTenantField === null\n ? null\n : normalizeString(actorTenantField)\n const actorOrgField = (auth as { actorOrgId?: string | null }).actorOrgId\n const actorOrgId = actorOrgField === undefined\n ? normalizeString(auth.orgId)\n : actorOrgField === null\n ? null\n : normalizeString(actorOrgField)\n\n const cookieTenant = request ? getSelectedTenantFromRequest(request) : null\n const requestedTenant =\n tenantOverride !== undefined\n ? tenantOverride\n : cookieTenant !== undefined\n ? cookieTenant\n : undefined\n const requestedTenantId = typeof requestedTenant === 'string' && requestedTenant.trim().length > 0 ? requestedTenant.trim() : null\n const isSuperAdminActor = auth.isSuperAdmin === true\n let effectiveTenantId = requestedTenantId ?? actorTenant ?? null\n if (actorTenant && effectiveTenantId && effectiveTenantId !== actorTenant && !isSuperAdminActor) {\n effectiveTenantId = actorTenant\n }\n if (!effectiveTenantId) {\n return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null }\n }\n\n const scopedAuth = {\n ...auth,\n tenantId: effectiveTenantId,\n orgId: actorTenant && actorTenant === effectiveTenantId ? actorOrgId ?? null : null,\n }\n\n const rawSelected = selectedId !== undefined ? selectedId : (request ? getSelectedOrganizationFromRequest(request) : null)\n const normalizedSelectedId = typeof rawSelected === 'string' && rawSelected.trim().length > 0\n ? rawSelected.trim()\n : null\n\n const userId = typeof auth.sub === 'string' && auth.sub.length > 0 ? auth.sub : null\n const ttlMs = resolveOrgScopeTtlMs()\n const cache = ttlMs > 0 ? resolveCacheFromContainer(container) : null\n const cacheKey = userId\n ? buildOrgScopeCacheKey({\n userId,\n effectiveTenantId,\n selectedOrgId: normalizedSelectedId,\n requestedTenantId: requestedTenantId ?? null,\n })\n : null\n\n if (cache && cacheKey && typeof cache.get === 'function') {\n try {\n const cached = await cache.get(cacheKey)\n if (isValidCachedScope(cached)) return cached\n } catch (err) {\n console.warn('[org-scope:cache] read failed', err)\n }\n }\n\n const baseScope = await resolveOrganizationScope({\n em,\n rbac,\n auth: scopedAuth,\n selectedId: rawSelected,\n tenantId: effectiveTenantId,\n })\n\n if (cache && cacheKey && userId && typeof cache.set === 'function') {\n try {\n await cache.set(cacheKey, baseScope, {\n ttl: ttlMs,\n tags: buildOrgScopeCacheTags({ userId, effectiveTenantId }),\n })\n } catch (err) {\n console.warn('[org-scope:cache] write failed', err)\n }\n }\n\n return baseScope\n}\n\nexport type FeatureCheckContext = {\n organizationId: string | null\n scope: OrganizationScope\n allowedOrganizationIds: string[] | null\n}\n\nexport async function resolveFeatureCheckContext({\n container,\n auth,\n request,\n selectedId,\n tenantId,\n}: {\n container: AwilixContainer\n auth: AuthContext | null | undefined\n request?: Request | { cookies?: { get: (name: string) => { value: string } | undefined } }\n selectedId?: string | null\n tenantId?: string | null\n}): Promise<FeatureCheckContext> {\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request, selectedId, tenantId })\n const allowedOrganizationIds = scope.allowedIds ?? null\n const authOrgId = auth?.orgId ?? null\n const organizationId =\n scope.selectedId\n ?? (authOrgId && (!Array.isArray(allowedOrganizationIds) || allowedOrganizationIds.includes(authOrgId)) ? authOrgId : null)\n ?? (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length ? allowedOrganizationIds[0] : null)\n\n return { organizationId, scope, allowedOrganizationIds }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -10,6 +10,7 @@ import { Button } from "@open-mercato/ui/primitives/button";
10
10
  import { Alert, AlertDescription } from "@open-mercato/ui/primitives/alert";
11
11
  import { apiFetch } from "@open-mercato/ui/backend/utils/api";
12
12
  import { readJsonSafe } from "@open-mercato/ui/backend/utils/serverErrors";
13
+ import { formatWorkflowValidationError } from "../../../lib/format-validation-error.js";
13
14
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
14
15
  import { useT } from "@open-mercato/shared/lib/i18n/context";
15
16
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
@@ -81,7 +82,7 @@ function EditWorkflowDefinitionPage() {
81
82
  });
82
83
  if (!response.ok) {
83
84
  const errorBody = await readJsonSafe(response, null);
84
- throw new Error(errorBody?.error || t("workflows.errors.updateFailed"));
85
+ throw new Error(formatWorkflowValidationError(errorBody, t("workflows.errors.updateFailed")));
85
86
  }
86
87
  return response;
87
88
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/workflows/backend/definitions/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter, useParams } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { apiFetch } from '@open-mercato/ui/backend/utils/api'\nimport { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport {\n workflowDefinitionFormSchema,\n createFormGroups,\n createFieldDefinitions,\n parseWorkflowToFormValues,\n buildWorkflowPayload,\n type WorkflowDefinitionFormValues,\n} from '../../../components/formConfig'\nimport { StepsEditor } from '../../../components/StepsEditor'\nimport { TransitionsEditor } from '../../../components/TransitionsEditor'\nimport { DefinitionTriggersEditor } from '../../../components/DefinitionTriggersEditor'\nimport { MobileDefinitionDetail } from '../../../components/mobile/MobileDefinitionDetail'\nimport { useIsMobile } from '@open-mercato/ui/hooks/useIsMobile'\nimport type { WorkflowDefinitionTrigger } from '../../../data/entities'\n\nexport default function EditWorkflowDefinitionPage() {\n const router = useRouter()\n const params = useParams()\n const t = useT()\n const isMobile = useIsMobile()\n\n // Handle catch-all route: params.slug = ['definitions', 'uuid']\n let definitionId: string | undefined\n if (params?.slug && Array.isArray(params.slug)) {\n definitionId = params.slug[1] // Second element is the ID\n } else if (params?.id) {\n definitionId = Array.isArray(params.id) ? params.id[0] : params.id\n }\n\n const { data: definition, isLoading, error } = useQuery({\n queryKey: ['workflow-definition', definitionId],\n queryFn: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}`)\n if (!response.ok) {\n throw new Error(t('workflows.errors.fetchFailed'))\n }\n const result = await response.json()\n return result.data\n },\n enabled: !!definitionId,\n })\n\n const initialValues = React.useMemo(() => {\n if (definition) {\n return parseWorkflowToFormValues(definition)\n }\n return null\n }, [definition])\n\n const [triggers, setTriggers] = React.useState<WorkflowDefinitionTrigger[]>([])\n\n React.useEffect(() => {\n setTriggers(initialValues?.triggers ?? [])\n }, [initialValues])\n\n const source = definition?.source as 'code' | 'code_override' | 'user' | undefined\n const isCodeOnly = source === 'code'\n const isCodeOverride = source === 'code_override'\n\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const mutationContextId = React.useMemo(\n () => `workflows.definitions.detail:${definitionId ?? 'unknown'}`,\n [definitionId],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: mutationContextId,\n })\n\n const handleSubmit = async (values: WorkflowDefinitionFormValues) => {\n const payload = buildWorkflowPayload({ ...values, triggers })\n\n await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string }>(response, null)\n throw new Error(errorBody?.error || t('workflows.errors.updateFailed'))\n }\n return response\n },\n mutationPayload: { resourceId: definitionId, operation: 'update' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'update',\n retryLastMutation,\n },\n })\n\n router.push('/backend/definitions')\n router.refresh()\n }\n\n const handleCustomize = async () => {\n try {\n const result = await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}/customize`, {\n method: 'POST',\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string }>(response, null)\n throw new Error(errorBody?.error || t('workflows.errors.updateFailed'))\n }\n return readJsonSafe<{ data?: { id?: string } }>(response, null)\n },\n mutationPayload: { resourceId: definitionId, operation: 'customize' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'customize',\n retryLastMutation,\n },\n })\n if (result?.data?.id) {\n router.push(`/backend/definitions/${result.data.id}`)\n router.refresh()\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : t('workflows.errors.updateFailed')\n flash(message, 'error')\n }\n }\n\n const handleResetToCode = async () => {\n const confirmed = await confirm({\n title: t('workflows.actions.resetToCode'),\n description: t('workflows.actions.resetConfirm'),\n confirmText: t('workflows.actions.resetToCode'),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n const result = await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}/reset-to-code`, {\n method: 'POST',\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string }>(response, null)\n throw new Error(errorBody?.error || t('workflows.messages.updateFailed'))\n }\n return readJsonSafe<{ data?: { id?: string } }>(response, null)\n },\n mutationPayload: { resourceId: definitionId, operation: 'reset-to-code' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'reset-to-code',\n retryLastMutation,\n },\n })\n flash(t('workflows.messages.updated'), 'success')\n const codeId = result?.data?.id || `code:${definition?.workflowId}`\n router.push(`/backend/definitions/${codeId}`)\n router.refresh()\n } catch {\n flash(t('workflows.messages.updateFailed'), 'error')\n }\n }\n\n const fields = React.useMemo(() => createFieldDefinitions(t), [t])\n\n const formGroups = React.useMemo(\n () => isMobile ? [] : createFormGroups(t, StepsEditor, TransitionsEditor),\n [t, isMobile]\n )\n\n const navigateToVisualEditor = React.useCallback(() => {\n router.push(`/backend/definitions/visual-editor?id=${definitionId}`)\n }, [router, definitionId])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('workflows.edit.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (error || !definition) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{t('workflows.errors.loadFailed')}</p>\n <Button asChild variant=\"outline\">\n <a href=\"/backend/definitions\">{t('workflows.backToList')}</a>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (!initialValues) {\n return null\n }\n\n return (\n <Page>\n <PageBody>\n {isCodeOnly && (\n <Alert variant=\"info\" className=\"mb-4\">\n <AlertDescription className=\"flex items-center justify-between\">\n <span>{t('workflows.source.code.readonlyBanner')}</span>\n <Button variant=\"outline\" size=\"sm\" onClick={handleCustomize}>\n {t('workflows.actions.customize')}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n {isCodeOverride && (\n <Alert variant=\"warning\" className=\"mb-4\">\n <AlertDescription className=\"flex items-center justify-between\">\n <span>{t('workflows.source.code_override.banner')}</span>\n <Button variant=\"outline\" size=\"sm\" onClick={handleResetToCode}>\n {t('workflows.actions.resetToCode')}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n <div className=\"mb-4 p-4 bg-status-info-bg border border-status-info-border rounded-lg flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-start gap-3\">\n <svg className=\"w-5 h-5 text-status-info-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-info-text\">\n {t('workflows.edit.visualEditorAvailable')}\n </p>\n <p className=\"text-xs text-status-info-text mt-0.5\">\n {t('workflows.edit.visualEditorDescription')}\n </p>\n </div>\n </div>\n <Button asChild variant=\"outline\" size=\"sm\" className=\"w-full sm:w-auto border-status-info-border text-status-info-text hover:bg-status-info-bg\">\n <a href={`/backend/definitions/visual-editor?id=${definitionId}`}>\n {t('workflows.actions.openVisualEditor')}\n </a>\n </Button>\n </div>\n <CrudForm\n key={definitionId}\n title={isCodeOnly ? definition?.workflowName || t('workflows.edit.title') : t('workflows.edit.title')}\n backHref=\"/backend/definitions\"\n schema={workflowDefinitionFormSchema}\n fields={fields}\n initialValues={initialValues}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/definitions\"\n groups={formGroups}\n submitLabel={isCodeOnly ? t('workflows.actions.customize') : t('workflows.form.update')}\n {...(isCodeOnly ? { readOnly: true } : {})}\n />\n\n {/* Mobile Steps & Transitions View */}\n {isMobile && initialValues && (\n <div className=\"mt-4\">\n <MobileDefinitionDetail\n values={initialValues}\n onEditStep={navigateToVisualEditor}\n onDeleteStep={navigateToVisualEditor}\n onAddStep={navigateToVisualEditor}\n onEditTransition={navigateToVisualEditor}\n onDeleteTransition={navigateToVisualEditor}\n onAddTransition={navigateToVisualEditor}\n />\n </div>\n )}\n\n {/* Event Triggers Section */}\n <div className=\"mt-8\">\n <DefinitionTriggersEditor\n value={triggers}\n onChange={setTriggers}\n />\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter, useParams } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { apiFetch } from '@open-mercato/ui/backend/utils/api'\nimport { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { formatWorkflowValidationError } from '../../../lib/format-validation-error'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport {\n workflowDefinitionFormSchema,\n createFormGroups,\n createFieldDefinitions,\n parseWorkflowToFormValues,\n buildWorkflowPayload,\n type WorkflowDefinitionFormValues,\n} from '../../../components/formConfig'\nimport { StepsEditor } from '../../../components/StepsEditor'\nimport { TransitionsEditor } from '../../../components/TransitionsEditor'\nimport { DefinitionTriggersEditor } from '../../../components/DefinitionTriggersEditor'\nimport { MobileDefinitionDetail } from '../../../components/mobile/MobileDefinitionDetail'\nimport { useIsMobile } from '@open-mercato/ui/hooks/useIsMobile'\nimport type { WorkflowDefinitionTrigger } from '../../../data/entities'\n\nexport default function EditWorkflowDefinitionPage() {\n const router = useRouter()\n const params = useParams()\n const t = useT()\n const isMobile = useIsMobile()\n\n // Handle catch-all route: params.slug = ['definitions', 'uuid']\n let definitionId: string | undefined\n if (params?.slug && Array.isArray(params.slug)) {\n definitionId = params.slug[1] // Second element is the ID\n } else if (params?.id) {\n definitionId = Array.isArray(params.id) ? params.id[0] : params.id\n }\n\n const { data: definition, isLoading, error } = useQuery({\n queryKey: ['workflow-definition', definitionId],\n queryFn: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}`)\n if (!response.ok) {\n throw new Error(t('workflows.errors.fetchFailed'))\n }\n const result = await response.json()\n return result.data\n },\n enabled: !!definitionId,\n })\n\n const initialValues = React.useMemo(() => {\n if (definition) {\n return parseWorkflowToFormValues(definition)\n }\n return null\n }, [definition])\n\n const [triggers, setTriggers] = React.useState<WorkflowDefinitionTrigger[]>([])\n\n React.useEffect(() => {\n setTriggers(initialValues?.triggers ?? [])\n }, [initialValues])\n\n const source = definition?.source as 'code' | 'code_override' | 'user' | undefined\n const isCodeOnly = source === 'code'\n const isCodeOverride = source === 'code_override'\n\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const mutationContextId = React.useMemo(\n () => `workflows.definitions.detail:${definitionId ?? 'unknown'}`,\n [definitionId],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: mutationContextId,\n })\n\n const handleSubmit = async (values: WorkflowDefinitionFormValues) => {\n const payload = buildWorkflowPayload({ ...values, triggers })\n\n await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string; details?: Array<{ path?: Array<string | number>; message?: string }> }>(response, null)\n throw new Error(formatWorkflowValidationError(errorBody, t('workflows.errors.updateFailed')))\n }\n return response\n },\n mutationPayload: { resourceId: definitionId, operation: 'update' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'update',\n retryLastMutation,\n },\n })\n\n router.push('/backend/definitions')\n router.refresh()\n }\n\n const handleCustomize = async () => {\n try {\n const result = await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}/customize`, {\n method: 'POST',\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string }>(response, null)\n throw new Error(errorBody?.error || t('workflows.errors.updateFailed'))\n }\n return readJsonSafe<{ data?: { id?: string } }>(response, null)\n },\n mutationPayload: { resourceId: definitionId, operation: 'customize' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'customize',\n retryLastMutation,\n },\n })\n if (result?.data?.id) {\n router.push(`/backend/definitions/${result.data.id}`)\n router.refresh()\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : t('workflows.errors.updateFailed')\n flash(message, 'error')\n }\n }\n\n const handleResetToCode = async () => {\n const confirmed = await confirm({\n title: t('workflows.actions.resetToCode'),\n description: t('workflows.actions.resetConfirm'),\n confirmText: t('workflows.actions.resetToCode'),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n const result = await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}/reset-to-code`, {\n method: 'POST',\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string }>(response, null)\n throw new Error(errorBody?.error || t('workflows.messages.updateFailed'))\n }\n return readJsonSafe<{ data?: { id?: string } }>(response, null)\n },\n mutationPayload: { resourceId: definitionId, operation: 'reset-to-code' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'reset-to-code',\n retryLastMutation,\n },\n })\n flash(t('workflows.messages.updated'), 'success')\n const codeId = result?.data?.id || `code:${definition?.workflowId}`\n router.push(`/backend/definitions/${codeId}`)\n router.refresh()\n } catch {\n flash(t('workflows.messages.updateFailed'), 'error')\n }\n }\n\n const fields = React.useMemo(() => createFieldDefinitions(t), [t])\n\n const formGroups = React.useMemo(\n () => isMobile ? [] : createFormGroups(t, StepsEditor, TransitionsEditor),\n [t, isMobile]\n )\n\n const navigateToVisualEditor = React.useCallback(() => {\n router.push(`/backend/definitions/visual-editor?id=${definitionId}`)\n }, [router, definitionId])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('workflows.edit.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (error || !definition) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{t('workflows.errors.loadFailed')}</p>\n <Button asChild variant=\"outline\">\n <a href=\"/backend/definitions\">{t('workflows.backToList')}</a>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (!initialValues) {\n return null\n }\n\n return (\n <Page>\n <PageBody>\n {isCodeOnly && (\n <Alert variant=\"info\" className=\"mb-4\">\n <AlertDescription className=\"flex items-center justify-between\">\n <span>{t('workflows.source.code.readonlyBanner')}</span>\n <Button variant=\"outline\" size=\"sm\" onClick={handleCustomize}>\n {t('workflows.actions.customize')}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n {isCodeOverride && (\n <Alert variant=\"warning\" className=\"mb-4\">\n <AlertDescription className=\"flex items-center justify-between\">\n <span>{t('workflows.source.code_override.banner')}</span>\n <Button variant=\"outline\" size=\"sm\" onClick={handleResetToCode}>\n {t('workflows.actions.resetToCode')}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n <div className=\"mb-4 p-4 bg-status-info-bg border border-status-info-border rounded-lg flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-start gap-3\">\n <svg className=\"w-5 h-5 text-status-info-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-info-text\">\n {t('workflows.edit.visualEditorAvailable')}\n </p>\n <p className=\"text-xs text-status-info-text mt-0.5\">\n {t('workflows.edit.visualEditorDescription')}\n </p>\n </div>\n </div>\n <Button asChild variant=\"outline\" size=\"sm\" className=\"w-full sm:w-auto border-status-info-border text-status-info-text hover:bg-status-info-bg\">\n <a href={`/backend/definitions/visual-editor?id=${definitionId}`}>\n {t('workflows.actions.openVisualEditor')}\n </a>\n </Button>\n </div>\n <CrudForm\n key={definitionId}\n title={isCodeOnly ? definition?.workflowName || t('workflows.edit.title') : t('workflows.edit.title')}\n backHref=\"/backend/definitions\"\n schema={workflowDefinitionFormSchema}\n fields={fields}\n initialValues={initialValues}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/definitions\"\n groups={formGroups}\n submitLabel={isCodeOnly ? t('workflows.actions.customize') : t('workflows.form.update')}\n {...(isCodeOnly ? { readOnly: true } : {})}\n />\n\n {/* Mobile Steps & Transitions View */}\n {isMobile && initialValues && (\n <div className=\"mt-4\">\n <MobileDefinitionDetail\n values={initialValues}\n onEditStep={navigateToVisualEditor}\n onDeleteStep={navigateToVisualEditor}\n onAddStep={navigateToVisualEditor}\n onEditTransition={navigateToVisualEditor}\n onDeleteTransition={navigateToVisualEditor}\n onAddTransition={navigateToVisualEditor}\n />\n </div>\n )}\n\n {/* Event Triggers Section */}\n <div className=\"mt-8\">\n <DefinitionTriggersEditor\n value={triggers}\n onChange={setTriggers}\n />\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["error"]
7
7
  }
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
5
5
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
6
6
  import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
7
7
  import { apiFetch } from "@open-mercato/ui/backend/utils/api";
8
+ import { readJsonSafe } from "@open-mercato/ui/backend/utils/serverErrors";
8
9
  import { useT } from "@open-mercato/shared/lib/i18n/context";
9
10
  import {
10
11
  workflowDefinitionFormSchema,
@@ -17,6 +18,7 @@ import { StepsEditor } from "../../../components/StepsEditor.js";
17
18
  import { TransitionsEditor } from "../../../components/TransitionsEditor.js";
18
19
  import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
19
20
  import { Zap } from "lucide-react";
21
+ import { formatWorkflowValidationError } from "../../../lib/format-validation-error.js";
20
22
  function CreateWorkflowDefinitionPage() {
21
23
  const router = useRouter();
22
24
  const t = useT();
@@ -28,8 +30,8 @@ function CreateWorkflowDefinitionPage() {
28
30
  body: JSON.stringify(payload)
29
31
  });
30
32
  if (!response.ok) {
31
- const error = await response.json();
32
- throw new Error(error.error || t("workflows.errors.createFailed"));
33
+ const errorBody = await readJsonSafe(response, null);
34
+ throw new Error(formatWorkflowValidationError(errorBody, t("workflows.errors.createFailed")));
33
35
  }
34
36
  router.push("/backend/definitions");
35
37
  router.refresh();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/workflows/backend/definitions/create/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { apiFetch } from '@open-mercato/ui/backend/utils/api'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n workflowDefinitionFormSchema,\n createFormGroups,\n createFieldDefinitions,\n defaultFormValues,\n buildWorkflowPayload,\n type WorkflowDefinitionFormValues,\n} from '../../../components/formConfig'\nimport { StepsEditor } from '../../../components/StepsEditor'\nimport { TransitionsEditor } from '../../../components/TransitionsEditor'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Zap } from 'lucide-react'\n\nexport default function CreateWorkflowDefinitionPage() {\n const router = useRouter()\n const t = useT()\n\n const handleSubmit = async (values: WorkflowDefinitionFormValues) => {\n const payload = buildWorkflowPayload(values)\n\n const response = await apiFetch('/api/workflows/definitions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(error.error || t('workflows.errors.createFailed'))\n }\n\n router.push('/backend/definitions')\n router.refresh()\n }\n\n const fields = React.useMemo(() => createFieldDefinitions(t), [t])\n\n const formGroups = React.useMemo(\n () => createFormGroups(t, StepsEditor, TransitionsEditor),\n [t]\n )\n\n return (\n <Page>\n <PageBody>\n <Alert variant=\"info\" className=\"mb-6\">\n <Zap className=\"w-4 h-4\" />\n <AlertTitle>{t('workflows.create.eventTriggersTitle')}</AlertTitle>\n <AlertDescription>\n {t('workflows.create.eventTriggersDescription')}\n </AlertDescription>\n </Alert>\n <CrudForm\n title={t('workflows.create.title')}\n backHref=\"/backend/definitions\"\n schema={workflowDefinitionFormSchema}\n fields={fields}\n initialValues={defaultFormValues}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/definitions\"\n groups={formGroups}\n submitLabel={t('workflows.form.create')}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": ";AAqDQ,SACE,KADF;AAnDR,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAC1B,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,OAAO,kBAAkB,kBAAkB;AACpD,SAAS,WAAW;AAEL,SAAR,+BAAgD;AACrD,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AAEf,QAAM,eAAe,OAAO,WAAyC;AACnE,UAAM,UAAU,qBAAqB,MAAM;AAE3C,UAAM,WAAW,MAAM,SAAS,8BAA8B;AAAA,MAC5D,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM,SAAS,KAAK;AAClC,YAAM,IAAI,MAAM,MAAM,SAAS,EAAE,+BAA+B,CAAC;AAAA,IACnE;AAEA,WAAO,KAAK,sBAAsB;AAClC,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,SAAS,MAAM,QAAQ,MAAM,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC;AAEjE,QAAM,aAAa,MAAM;AAAA,IACvB,MAAM,iBAAiB,GAAG,aAAa,iBAAiB;AAAA,IACxD,CAAC,CAAC;AAAA,EACJ;AAEA,SACE,oBAAC,QACC,+BAAC,YACC;AAAA,yBAAC,SAAM,SAAQ,QAAO,WAAU,QAC9B;AAAA,0BAAC,OAAI,WAAU,WAAU;AAAA,MACzB,oBAAC,cAAY,YAAE,qCAAqC,GAAE;AAAA,MACtD,oBAAC,oBACE,YAAE,2CAA2C,GAChD;AAAA,OACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,wBAAwB;AAAA,QACjC,UAAS;AAAA,QACT,QAAQ;AAAA,QACR;AAAA,QACA,eAAe;AAAA,QACf,UAAU;AAAA,QACV,YAAW;AAAA,QACX,QAAQ;AAAA,QACR,aAAa,EAAE,uBAAuB;AAAA;AAAA,IACxC;AAAA,KACF,GACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { apiFetch } from '@open-mercato/ui/backend/utils/api'\nimport { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n workflowDefinitionFormSchema,\n createFormGroups,\n createFieldDefinitions,\n defaultFormValues,\n buildWorkflowPayload,\n type WorkflowDefinitionFormValues,\n} from '../../../components/formConfig'\nimport { StepsEditor } from '../../../components/StepsEditor'\nimport { TransitionsEditor } from '../../../components/TransitionsEditor'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Zap } from 'lucide-react'\nimport { formatWorkflowValidationError } from '../../../lib/format-validation-error'\n\nexport default function CreateWorkflowDefinitionPage() {\n const router = useRouter()\n const t = useT()\n\n const handleSubmit = async (values: WorkflowDefinitionFormValues) => {\n const payload = buildWorkflowPayload(values)\n\n const response = await apiFetch('/api/workflows/definitions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string; details?: Array<{ path?: Array<string | number>; message?: string }> }>(response, null)\n throw new Error(formatWorkflowValidationError(errorBody, t('workflows.errors.createFailed')))\n }\n\n router.push('/backend/definitions')\n router.refresh()\n }\n\n const fields = React.useMemo(() => createFieldDefinitions(t), [t])\n\n const formGroups = React.useMemo(\n () => createFormGroups(t, StepsEditor, TransitionsEditor),\n [t]\n )\n\n return (\n <Page>\n <PageBody>\n <Alert variant=\"info\" className=\"mb-6\">\n <Zap className=\"w-4 h-4\" />\n <AlertTitle>{t('workflows.create.eventTriggersTitle')}</AlertTitle>\n <AlertDescription>\n {t('workflows.create.eventTriggersDescription')}\n </AlertDescription>\n </Alert>\n <CrudForm\n title={t('workflows.create.title')}\n backHref=\"/backend/definitions\"\n schema={workflowDefinitionFormSchema}\n fields={fields}\n initialValues={defaultFormValues}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/definitions\"\n groups={formGroups}\n submitLabel={t('workflows.form.create')}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": ";AAuDQ,SACE,KADF;AArDR,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAC1B,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAC7B,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,OAAO,kBAAkB,kBAAkB;AACpD,SAAS,WAAW;AACpB,SAAS,qCAAqC;AAE/B,SAAR,+BAAgD;AACrD,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AAEf,QAAM,eAAe,OAAO,WAAyC;AACnE,UAAM,UAAU,qBAAqB,MAAM;AAE3C,UAAM,WAAW,MAAM,SAAS,8BAA8B;AAAA,MAC5D,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,aAAuG,UAAU,IAAI;AAC7I,YAAM,IAAI,MAAM,8BAA8B,WAAW,EAAE,+BAA+B,CAAC,CAAC;AAAA,IAC9F;AAEA,WAAO,KAAK,sBAAsB;AAClC,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,SAAS,MAAM,QAAQ,MAAM,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC;AAEjE,QAAM,aAAa,MAAM;AAAA,IACvB,MAAM,iBAAiB,GAAG,aAAa,iBAAiB;AAAA,IACxD,CAAC,CAAC;AAAA,EACJ;AAEA,SACE,oBAAC,QACC,+BAAC,YACC;AAAA,yBAAC,SAAM,SAAQ,QAAO,WAAU,QAC9B;AAAA,0BAAC,OAAI,WAAU,WAAU;AAAA,MACzB,oBAAC,cAAY,YAAE,qCAAqC,GAAE;AAAA,MACtD,oBAAC,oBACE,YAAE,2CAA2C,GAChD;AAAA,OACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,wBAAwB;AAAA,QACjC,UAAS;AAAA,QACT,QAAQ;AAAA,QACR;AAAA,QACA,eAAe;AAAA,QACf,UAAU;AAAA,QACV,YAAW;AAAA,QACX,QAAQ;AAAA,QACR,aAAa,EAAE,uBAAuB;AAAA;AAAA,IACxC;AAAA,KACF,GACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -855,7 +855,7 @@ function VisualEditorPage() {
855
855
  !isCodeOnly && /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded-lg border bg-card p-3", children: [
856
856
  /* @__PURE__ */ jsx("h2", { className: "mb-2 text-xs font-semibold uppercase text-muted-foreground", children: t("workflows.visualEditor.stepPalette") }),
857
857
  /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-muted-foreground", children: t("workflows.visualEditor.tapToAdd") }),
858
- /* @__PURE__ */ jsx("div", { className: "flex gap-2 overflow-x-auto pb-1", children: ["start", "userTask", "automated", "waitForSignal", "subWorkflow", "end"].map((nodeType) => {
858
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2 overflow-x-auto pb-1", children: ["start", "userTask", "automated", "waitForSignal", "waitForTimer", "subWorkflow", "end"].map((nodeType) => {
859
859
  const Icon = NODE_TYPE_ICONS[nodeType];
860
860
  return /* @__PURE__ */ jsxs(
861
861
  "button",
@@ -936,6 +936,21 @@ function VisualEditorPage() {
936
936
  ]
937
937
  }
938
938
  ),
939
+ /* @__PURE__ */ jsxs(
940
+ "button",
941
+ {
942
+ onClick: () => handleAddNode("waitForTimer"),
943
+ className: "group relative w-full cursor-pointer rounded-xl border-2 border-border bg-background px-4 py-3 text-left transition-all hover:border-muted-foreground/30 hover:shadow-md",
944
+ children: [
945
+ /* @__PURE__ */ jsx("div", { className: `absolute right-2 top-2 ${NODE_TYPE_COLORS.waitForTimer} opacity-60 transition-opacity group-hover:opacity-100`, children: (() => {
946
+ const Icon = NODE_TYPE_ICONS.waitForTimer;
947
+ return /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" });
948
+ })() }),
949
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-foreground", children: NODE_TYPE_LABELS.waitForTimer.title }),
950
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: NODE_TYPE_LABELS.waitForTimer.description })
951
+ ]
952
+ }
953
+ ),
939
954
  /* @__PURE__ */ jsxs(
940
955
  "button",
941
956
  {
@@ -1019,7 +1034,8 @@ function getDefaultLabel(nodeType) {
1019
1034
  userTask: "New User Task",
1020
1035
  automated: "New Automated Task",
1021
1036
  decision: "Decision Point",
1022
- waitForSignal: "Wait for Signal"
1037
+ waitForSignal: "Wait for Signal",
1038
+ waitForTimer: "Wait for Timer"
1023
1039
  };
1024
1040
  return labels[nodeType] || "New Step";
1025
1041
  }
@@ -1030,7 +1046,8 @@ function getDefaultBadge(nodeType) {
1030
1046
  userTask: "User Task",
1031
1047
  automated: "Automated",
1032
1048
  decision: "Decision",
1033
- waitForSignal: "Wait for Signal"
1049
+ waitForSignal: "Wait for Signal",
1050
+ waitForTimer: "Wait for Timer"
1034
1051
  };
1035
1052
  return badges[nodeType] || "Task";
1036
1053
  }