@open-mercato/core 0.6.4-develop.4270.1.a614eb18e6 → 0.6.4-develop.4299.1.af24e08431

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 (30) hide show
  1. package/AGENTS.md +10 -0
  2. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +12 -2
  3. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  4. package/dist/modules/customers/components/detail/schedule/fieldConfig.js +10 -0
  5. package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
  6. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +2 -1
  7. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  8. package/dist/modules/feature_toggles/acl.js +2 -1
  9. package/dist/modules/feature_toggles/acl.js.map +2 -2
  10. package/dist/modules/feature_toggles/api/global/route.js +5 -3
  11. package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
  12. package/dist/modules/feature_toggles/cli.js +7 -1
  13. package/dist/modules/feature_toggles/cli.js.map +2 -2
  14. package/dist/modules/feature_toggles/commands/global.js +9 -0
  15. package/dist/modules/feature_toggles/commands/global.js.map +2 -2
  16. package/dist/modules/feature_toggles/setup.js +8 -1
  17. package/dist/modules/feature_toggles/setup.js.map +2 -2
  18. package/package.json +7 -7
  19. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +7 -1
  20. package/src/modules/customers/components/detail/schedule/fieldConfig.ts +8 -1
  21. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +1 -0
  22. package/src/modules/customers/i18n/de.json +5 -0
  23. package/src/modules/customers/i18n/en.json +5 -0
  24. package/src/modules/customers/i18n/es.json +5 -0
  25. package/src/modules/customers/i18n/pl.json +5 -0
  26. package/src/modules/feature_toggles/acl.ts +2 -1
  27. package/src/modules/feature_toggles/api/global/route.ts +5 -3
  28. package/src/modules/feature_toggles/cli.ts +6 -0
  29. package/src/modules/feature_toggles/commands/global.ts +15 -0
  30. package/src/modules/feature_toggles/setup.ts +8 -1
package/AGENTS.md CHANGED
@@ -606,6 +606,7 @@ const myEnricher: ResponseEnricher = {
606
606
  timeout: 2000, // ms, default 2000
607
607
  fallback: { _mymodule: { count: 0 } },// returned on failure
608
608
  critical: false, // true = error propagates to client
609
+ cacheableOnListHit: false, // see "List cache behavior" below (default false)
609
610
  async enrichOne(record, context) {
610
611
  // Add fields to a single record
611
612
  return { ...record, _mymodule: { count: 42 } }
@@ -629,11 +630,20 @@ const crud = makeCrudRoute({
629
630
  })
630
631
  ```
631
632
 
633
+ ### List cache behavior (`cacheableOnListHit`)
634
+
635
+ When the opt-in CRUD list cache (`ENABLE_CRUD_API_CACHE`) is enabled, the factory stores the **enriched** list payload and partitions cache entries by the active-enricher signature (the ACL/tenant-filtered enricher ids for the caller). On a cache hit it must decide whether to re-run enrichers or serve the stored enriched fields directly:
636
+
637
+ - The cache-hit path **skips re-running enrichers only when every active enricher opted in with `cacheableOnListHit: true`** (record-pure cohort). Otherwise it re-runs all active enrichers on a hit and the cache stores the base (pre-enrichment) payload.
638
+ - Set `cacheableOnListHit: true` **only** when the enricher's output for a record is a pure function of that record's own cached state and is invalidated together with it (e.g. fields derived from the same module's own per-record data). The shipped `example.customer-todo-count` enricher keeps the default `false`: it reads other modules' tables (todos and per-customer priority) the list cache does not invalidate on, so it must re-run on every hit.
639
+ - Leave it `false` (the fail-closed default) for any enricher whose output depends on data the list cache does not invalidate on: cross-module / cross-entity reads (e.g. a product image fetched for a sales line), wall-clock-relative values (e.g. "days in stage"), or aggregates over other tables. These MUST re-run on every request so the response reflects current data.
640
+
632
641
  ### Response Enricher Rules
633
642
 
634
643
  - MUST implement `enrichMany()` for batch endpoints (prevents N+1 queries)
635
644
  - MUST namespace enriched fields with `_moduleName` prefix (e.g. `_example.todoCount`)
636
645
  - MUST use `features` array for ACL gating — enricher runs only if user has all listed features
646
+ - MUST keep `cacheableOnListHit` at `false` (default) unless the enriched output is record-pure and invalidated with the host record — opting in on a cross-module/time-relative enricher serves stale data from the shared list cache
637
647
  - Export fields are stripped: `_meta` and `_`-prefixed fields are removed from CSV/Excel exports
638
648
  - Enrichers run after `CrudHooks.afterList`, before HTTP response serialization
639
649
  - `critical: true` propagates errors to the HTTP response; `false` (default) uses fallback silently
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Users, Phone, Check, Mail, Calendar, AlertTriangle, X } from "lucide-react";
4
+ import { Users, Phone, Check, Mail, Calendar, AlertTriangle, X, StickyNote } from "lucide-react";
5
5
  import { cn } from "@open-mercato/shared/lib/utils";
6
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
7
7
  import { validatePhoneNumber } from "@open-mercato/shared/lib/phone";
@@ -30,7 +30,8 @@ const TYPE_TABS = [
30
30
  { type: "meeting", icon: Users, labelKey: "customers.schedule.types.meeting", fallback: "Meeting" },
31
31
  { type: "call", icon: Phone, labelKey: "customers.schedule.types.call", fallback: "Call" },
32
32
  { type: "task", icon: Check, labelKey: "customers.schedule.types.task", fallback: "Task" },
33
- { type: "email", icon: Mail, labelKey: "customers.schedule.types.email", fallback: "Email" }
33
+ { type: "email", icon: Mail, labelKey: "customers.schedule.types.email", fallback: "Email" },
34
+ { type: "note", icon: StickyNote, labelKey: "customers.schedule.types.note", fallback: "Note" }
34
35
  ];
35
36
  const TYPE_CHROME = {
36
37
  meeting: {
@@ -68,6 +69,15 @@ const TYPE_CHROME = {
68
69
  saveKey: "customers.schedule.email.save",
69
70
  saveFallback: "Send email",
70
71
  saveIcon: Mail
72
+ },
73
+ note: {
74
+ titleKey: "customers.schedule.note.title",
75
+ titleFallback: "Add note",
76
+ subtitleKey: "customers.schedule.note.subtitle",
77
+ subtitleFallback: "Write down a note about this interaction",
78
+ saveKey: "customers.schedule.note.save",
79
+ saveFallback: "Save note",
80
+ saveIcon: StickyNote
71
81
  }
72
82
  };
73
83
  const CALL_DIRECTIONS = [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/ScheduleActivityDialog.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Users, Phone, Check, Mail, Calendar, AlertTriangle, X } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { validatePhoneNumber } from '@open-mercato/shared/lib/phone'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { mapCrudServerErrorToFormErrors } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { PhoneNumberField, SwitchableMarkdownInput } from '@open-mercato/ui/backend/inputs'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n useScheduleFormState,\n FIELD_VISIBILITY,\n getFieldLabel,\n DateTimeFields,\n ParticipantsField,\n LocationField,\n FooterFields,\n LinkedEntitiesField,\n} from './schedule'\nimport type { ActivityType, ScheduleActivityEditData } from './schedule'\n\nconst TYPE_TABS: Array<{ type: ActivityType; icon: React.ComponentType<{ className?: string }>; labelKey: string; fallback: string }> = [\n { type: 'meeting', icon: Users, labelKey: 'customers.schedule.types.meeting', fallback: 'Meeting' },\n { type: 'call', icon: Phone, labelKey: 'customers.schedule.types.call', fallback: 'Call' },\n { type: 'task', icon: Check, labelKey: 'customers.schedule.types.task', fallback: 'Task' },\n { type: 'email', icon: Mail, labelKey: 'customers.schedule.types.email', fallback: 'Email' },\n]\n\ntype DialogChrome = { titleKey: string; titleFallback: string; subtitleKey: string; subtitleFallback: string; saveKey: string; saveFallback: string; saveIcon: React.ComponentType<{ className?: string }> }\n\nconst TYPE_CHROME: Record<ActivityType, DialogChrome> = {\n meeting: {\n titleKey: 'customers.schedule.meeting.title', titleFallback: 'New meeting',\n subtitleKey: 'customers.schedule.meeting.subtitle', subtitleFallback: 'Block time on the calendar with attendees',\n saveKey: 'customers.schedule.meeting.save', saveFallback: 'Save activity', saveIcon: Calendar,\n },\n call: {\n titleKey: 'customers.schedule.call.title', titleFallback: 'Log call',\n subtitleKey: 'customers.schedule.call.subtitle', subtitleFallback: 'Log a call you just had or schedule one',\n saveKey: 'customers.schedule.call.save', saveFallback: 'Log call', saveIcon: Phone,\n },\n task: {\n titleKey: 'customers.schedule.task.title', titleFallback: 'New task',\n subtitleKey: 'customers.schedule.task.subtitle', subtitleFallback: 'Capture something to follow up on',\n saveKey: 'customers.schedule.task.save', saveFallback: 'Save task', saveIcon: Check,\n },\n email: {\n titleKey: 'customers.schedule.email.title', titleFallback: 'Compose email',\n subtitleKey: 'customers.schedule.email.subtitle', subtitleFallback: 'Compose and send a tracked email',\n saveKey: 'customers.schedule.email.save', saveFallback: 'Send email', saveIcon: Mail,\n },\n}\n\nconst CALL_DIRECTIONS: Array<{ key: 'outbound' | 'inbound'; labelKey: string; labelFallback: string; dot: string }> = [\n { key: 'outbound', labelKey: 'customers.schedule.call.direction.outbound', labelFallback: 'Outbound', dot: 'bg-status-info-icon' },\n { key: 'inbound', labelKey: 'customers.schedule.call.direction.inbound', labelFallback: 'Inbound', dot: 'bg-status-success-icon' },\n]\n\nconst CALL_OUTCOMES: Array<{ key: string; labelKey: string; labelFallback: string; dot: string }> = [\n { key: 'connected', labelKey: 'customers.schedule.call.outcome.connected', labelFallback: 'Connected', dot: 'bg-status-success-icon' },\n { key: 'voicemail', labelKey: 'customers.schedule.call.outcome.voicemail', labelFallback: 'Voicemail', dot: 'bg-status-warning-icon' },\n { key: 'noanswer', labelKey: 'customers.schedule.call.outcome.noAnswer', labelFallback: 'No answer', dot: 'bg-muted-foreground' },\n { key: 'busy', labelKey: 'customers.schedule.call.outcome.busy', labelFallback: 'Busy', dot: 'bg-status-warning-icon' },\n { key: 'badnumber', labelKey: 'customers.schedule.call.outcome.badNumber', labelFallback: 'Bad number', dot: 'bg-status-error-icon' },\n]\n\nconst TASK_PRIORITIES: Array<{ key: string; labelKey: string; labelFallback: string; dot: string }> = [\n { key: 'low', labelKey: 'customers.schedule.task.priority.low', labelFallback: 'Low', dot: 'bg-muted-foreground' },\n { key: 'medium', labelKey: 'customers.schedule.task.priority.medium', labelFallback: 'Medium', dot: 'bg-status-info-icon' },\n { key: 'high', labelKey: 'customers.schedule.task.priority.high', labelFallback: 'High', dot: 'bg-status-warning-icon' },\n { key: 'urgent', labelKey: 'customers.schedule.task.priority.urgent', labelFallback: 'Urgent', dot: 'bg-status-error-icon' },\n]\n\ninterface ScheduleActivityDialogProps {\n open: boolean\n onClose: () => void\n entityId: string\n dealId?: string | null\n entityName?: string\n companyName?: string | null\n entityType: 'company' | 'person' | 'deal'\n onActivityCreated?: () => void\n /** When provided, dialog opens in edit mode with pre-filled data */\n editData?: ScheduleActivityEditData | null\n}\n\nexport function ScheduleActivityDialog({\n open,\n onClose,\n entityId,\n dealId = null,\n entityName,\n companyName,\n entityType,\n onActivityCreated,\n editData,\n}: ScheduleActivityDialogProps) {\n const t = useT()\n const state = useScheduleFormState({ open, editData: editData ?? null })\n const visibleFields = FIELD_VISIBILITY[state.activityType]\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const isEditing = Boolean(editData?.id)\n const chrome = TYPE_CHROME[state.activityType]\n const SaveIcon = chrome.saveIcon\n const [callDirection, setCallDirection] = React.useState<'outbound' | 'inbound'>('outbound')\n const [callOutcome, setCallOutcome] = React.useState<string | null>(null)\n const [callPhoneNumber, setCallPhoneNumber] = React.useState('')\n const [callPhoneError, setCallPhoneError] = React.useState<string | null>(null)\n const [taskPriority, setTaskPriority] = React.useState<string>('medium')\n const callPhoneInvalidMessage = React.useMemo(\n () =>\n t(\n 'customers.activities.errors.phoneInvalid',\n 'Enter a valid phone number with country code (e.g. +1 212 555 1234)',\n ),\n [t],\n )\n const translateErrorMessage = React.useCallback(\n (message: string | undefined, fallback: string) => {\n const key = typeof message === 'string' ? message.trim() : ''\n return key ? t(key, key) : fallback\n },\n [t],\n )\n\n React.useEffect(() => {\n if (!open) return\n const raw = editData as (Record<string, unknown> & { customValues?: unknown; phoneNumber?: unknown }) | null | undefined\n const cv = (raw?.customValues && typeof raw.customValues === 'object' ? raw.customValues : null) as Record<string, unknown> | null\n setCallDirection(typeof cv?.callDirection === 'string' && cv.callDirection === 'inbound' ? 'inbound' : 'outbound')\n setCallOutcome(typeof cv?.callOutcome === 'string' ? cv.callOutcome : null)\n // Seed phone number from either top-level `phoneNumber` (newer write path)\n // or legacy `customValues.callPhoneNumber` so previously-saved calls still\n // round-trip on edit (#1808).\n const seededPhone =\n typeof raw?.phoneNumber === 'string' && raw.phoneNumber.trim().length > 0\n ? raw.phoneNumber\n : typeof cv?.callPhoneNumber === 'string'\n ? cv.callPhoneNumber\n : ''\n setCallPhoneNumber(seededPhone)\n setCallPhoneError(null)\n setTaskPriority(typeof cv?.taskPriority === 'string' ? cv.taskPriority : 'medium')\n }, [open, editData])\n\n // Reset per-type chip state when the user switches activity type in create mode.\n // In edit mode, the persisted customValues should win, so we skip the reset.\n React.useEffect(() => {\n if (!open || isEditing) return\n setCallDirection('outbound')\n setCallOutcome(null)\n setCallPhoneNumber('')\n setCallPhoneError(null)\n setTaskPriority('medium')\n }, [state.activityType, open, isEditing])\n\n const handleCallPhoneChange = React.useCallback((next: string | undefined) => {\n setCallPhoneNumber(next ?? '')\n setCallPhoneError(null)\n }, [])\n\n const formSnapshot = React.useMemo(() => JSON.stringify({\n activityType: state.activityType,\n title: state.title,\n date: state.date,\n startTime: state.startTime,\n duration: state.duration,\n allDay: state.allDay,\n description: state.description,\n location: state.location,\n reminderMinutes: state.reminderMinutes,\n visibility: state.visibility,\n participants: state.participants,\n linkedEntities: state.linkedEntities,\n recurrenceEnabled: state.recurrenceEnabled,\n recurrenceDays: state.recurrenceDays,\n recurrenceEndType: state.recurrenceEndType,\n recurrenceCount: state.recurrenceCount,\n recurrenceEndDate: state.recurrenceEndDate,\n guestPermissions: state.guestPermissions,\n }), [\n state.activityType, state.title, state.date, state.startTime, state.duration, state.allDay,\n state.description, state.location, state.reminderMinutes, state.visibility, state.participants,\n state.linkedEntities, state.recurrenceEnabled, state.recurrenceDays, state.recurrenceEndType,\n state.recurrenceCount, state.recurrenceEndDate, state.guestPermissions,\n ])\n const initialSnapshotRef = React.useRef<string | null>(null)\n const snapshotOpenKeyRef = React.useRef<string | null>(null)\n const snapshotSettleCountRef = React.useRef(0)\n const openKey = open ? `${editData?.id ?? 'new'}` : null\n React.useEffect(() => {\n if (!open) {\n initialSnapshotRef.current = null\n snapshotOpenKeyRef.current = null\n snapshotSettleCountRef.current = 0\n return\n }\n if (snapshotOpenKeyRef.current !== openKey) {\n snapshotOpenKeyRef.current = openKey\n snapshotSettleCountRef.current = 0\n initialSnapshotRef.current = null\n }\n if (snapshotSettleCountRef.current < 2) {\n initialSnapshotRef.current = formSnapshot\n snapshotSettleCountRef.current += 1\n }\n }, [open, openKey, formSnapshot])\n\n const isDirty = React.useCallback(() => {\n if (initialSnapshotRef.current == null) return false\n return initialSnapshotRef.current !== formSnapshot\n }, [formSnapshot])\n\n const guardedClose = React.useCallback(async () => {\n if (!isDirty()) {\n onClose()\n return\n }\n const ok = await confirm({\n title: t('customers.schedule.discardConfirm.title', 'Discard unsaved changes?'),\n description: t(\n 'customers.schedule.discardConfirm.description',\n 'You have unsaved edits in this activity. Save them first or continue to discard them.',\n ),\n confirmText: t('customers.schedule.discardConfirm.confirm', 'Discard'),\n cancelText: t('customers.schedule.discardConfirm.cancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (ok) onClose()\n }, [confirm, isDirty, onClose, t])\n\n const mutationContextId = React.useMemo(\n () => `customer-activity:${entityType}:${entityId}`,\n [entityId, entityType],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n entityType: 'company' | 'person' | 'deal'\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: mutationContextId,\n resourceKind:\n entityType === 'company'\n ? 'customers.company'\n : entityType === 'person'\n ? 'customers.person'\n : 'customers.deal',\n resourceId: entityId,\n entityType,\n retryLastMutation,\n }),\n [entityId, entityType, mutationContextId, retryLastMutation],\n )\n const runGuardedMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload: Record<string, unknown>) =>\n runMutation({\n operation,\n mutationPayload,\n context: mutationContext,\n }),\n [mutationContext, runMutation],\n )\n\n // Conflict detection -- debounced check when date/time/duration changes\n React.useEffect(() => {\n if (!open || state.allDay || !state.date || !state.startTime) {\n state.setConflict(null)\n return\n }\n const timer = setTimeout(async () => {\n try {\n const localStart = new Date(`${state.date}T${state.startTime}:00`)\n const params = new URLSearchParams({\n date: state.date,\n startTime: state.startTime,\n duration: String(state.duration),\n })\n if (editData?.id) {\n params.set('excludeId', editData.id)\n }\n if (!Number.isNaN(localStart.getTime())) {\n params.set('timezoneOffsetMinutes', String(-localStart.getTimezoneOffset()))\n }\n const data = await readApiResultOrThrow<{\n hasConflicts: boolean\n conflicts: Array<{ id: string; title: string | null; startTime: string; endTime: string; type: string }>\n }>(`/api/customers/interactions/conflicts?${params.toString()}`)\n if (data?.hasConflicts && Array.isArray(data.conflicts) && data.conflicts.length > 0) {\n const descriptions = data.conflicts\n .map((c) => `${c.startTime}\u2013${c.endTime}: ${c.title ?? c.type}`)\n .join(', ')\n state.setConflict(\n t('customers.schedule.conflict.description', 'Overlaps with: {{items}}', { items: descriptions }),\n )\n } else {\n state.setConflict(null)\n }\n } catch {\n state.setConflict(null)\n }\n }, 500)\n return () => clearTimeout(timer)\n }, [editData?.id, open, state.date, state.startTime, state.duration, state.allDay, t]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const trimmedDate = state.date.trim()\n const trimmedStartTime = state.startTime.trim()\n const trimmedCallPhone = callPhoneNumber.trim()\n const isDateMissing = !trimmedDate\n const isTimeMissing = !state.allDay && !trimmedStartTime\n const isSubmitDisabled =\n state.saving ||\n !state.title.trim() ||\n isDateMissing ||\n isTimeMissing\n\n const handleSave = React.useCallback(async () => {\n if (!state.title.trim()) return\n if (isDateMissing) {\n flash(t('customers.activities.errors.dateRequired', 'Date is required'), 'error')\n return\n }\n if (isTimeMissing) {\n flash(t('customers.activities.errors.timeRequired', 'Time is required'), 'error')\n return\n }\n let phoneNumberForPayload: string | undefined\n if (state.activityType === 'call' && trimmedCallPhone) {\n const phoneValidation = validatePhoneNumber(trimmedCallPhone)\n if (!phoneValidation.valid) {\n setCallPhoneError(callPhoneInvalidMessage)\n flash(callPhoneInvalidMessage, 'error')\n return\n }\n phoneNumberForPayload = phoneValidation.normalized ?? trimmedCallPhone\n setCallPhoneError(null)\n if (phoneNumberForPayload !== callPhoneNumber) {\n setCallPhoneNumber(phoneNumberForPayload)\n }\n }\n state.setSaving(true)\n try {\n const scheduledAt = state.allDay\n ? new Date(`${state.date}T00:00:00`).toISOString()\n : new Date(`${state.date}T${state.startTime}:00`).toISOString()\n\n const recurrenceRule = state.recurrenceEnabled\n ? buildRecurrenceRule(state.recurrenceDays, state.recurrenceEndType, state.recurrenceCount, state.recurrenceEndDate)\n : null\n\n const isSaveEdit = Boolean(editData?.id)\n const customValues: Record<string, unknown> = {}\n if (state.activityType === 'call') {\n customValues.callDirection = callDirection\n if (callOutcome) customValues.callOutcome = callOutcome\n if (phoneNumberForPayload) customValues.callPhoneNumber = phoneNumberForPayload\n }\n if (state.activityType === 'task') {\n customValues.taskPriority = taskPriority\n }\n const payload = {\n ...(isSaveEdit ? { id: editData!.id } : {}),\n entityId,\n dealId,\n interactionType: state.activityType,\n title: state.title.trim(),\n body: state.description.trim() || null,\n status: 'planned',\n date: trimmedDate,\n time: state.allDay ? '00:00' : trimmedStartTime,\n phoneNumber: state.activityType === 'call' ? phoneNumberForPayload : undefined,\n scheduledAt,\n durationMinutes: visibleFields.has('duration') && !state.allDay ? state.duration : null,\n location: visibleFields.has('location') ? (state.location.trim() || null) : null,\n allDay: visibleFields.has('allDay') ? state.allDay : null,\n recurrenceRule: visibleFields.has('recurrence') ? recurrenceRule : null,\n recurrenceEnd: visibleFields.has('recurrence') && state.recurrenceEndType === 'date' && state.recurrenceEndDate\n ? new Date(state.recurrenceEndDate).toISOString()\n : null,\n participants: visibleFields.has('participants') && state.participants.length > 0\n ? state.participants.map((p) => ({ userId: p.userId, name: p.name, email: p.email, status: p.status ?? 'pending' }))\n : null,\n guestPermissions: visibleFields.has('participants') && state.participants.length > 0 ? state.guestPermissions : null,\n linkedEntities: state.linkedEntities.length > 0\n ? state.linkedEntities.map((e) => ({ id: e.id, type: e.type, label: e.label }))\n : null,\n reminderMinutes: visibleFields.has('reminder') ? state.reminderMinutes : null,\n visibility: visibleFields.has('visibility') ? state.visibility : null,\n ...(Object.keys(customValues).length > 0 ? { customValues } : {}),\n }\n await runGuardedMutation(\n () =>\n apiCallOrThrow('/api/customers/interactions', {\n method: isSaveEdit ? 'PUT' : 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n {\n operation: isSaveEdit ? 'updateActivity' : 'createActivity',\n interactionId: editData?.id ?? null,\n interactionType: state.activityType,\n },\n )\n flash(t('customers.schedule.saved', 'Activity scheduled'), 'success')\n onClose()\n // Delay data reload so the dialog can unmount cleanly and Radix restores body scroll\n requestAnimationFrame(() => { onActivityCreated?.() })\n } catch (err) {\n const { message, fieldErrors } = mapCrudServerErrorToFormErrors(err)\n const phoneFieldError = fieldErrors?.phoneNumber\n if (state.activityType === 'call' && phoneFieldError) {\n const translatedPhoneError = translateErrorMessage(phoneFieldError, callPhoneInvalidMessage)\n setCallPhoneError(translatedPhoneError)\n flash(translatedPhoneError, 'error')\n return\n }\n flash(\n translateErrorMessage(message, t('customers.schedule.error', 'Failed to schedule activity')),\n 'error',\n )\n } finally {\n state.setSaving(false)\n }\n }, [callDirection, callOutcome, callPhoneInvalidMessage, callPhoneNumber, isDateMissing, isTimeMissing, state.activityType, state.allDay, state.date, state.description, dealId, state.duration, editData, entityId, state.guestPermissions, state.linkedEntities, state.location, onActivityCreated, onClose, state.participants, state.recurrenceCount, state.recurrenceDays, state.recurrenceEnabled, state.recurrenceEndDate, state.recurrenceEndType, state.reminderMinutes, runGuardedMutation, state.startTime, t, taskPriority, state.title, translateErrorMessage, trimmedCallPhone, trimmedDate, trimmedStartTime, state.visibility, visibleFields]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleKeyDown = React.useCallback((e: React.KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n handleSave()\n }\n }, [handleSave])\n\n return (\n <Dialog open={open} onOpenChange={(o) => { if (!o) void guardedClose() }}>\n {ConfirmDialogElement}\n <DialogContent className=\"flex max-h-[90vh] flex-col overflow-hidden border-border p-0 shadow-xl sm:max-w-[760px] sm:rounded-xl [&>[data-dialog-close]]:hidden\" onKeyDown={handleKeyDown} aria-describedby={undefined}>\n <VisuallyHidden>\n <DialogTitle>{isEditing ? t('customers.schedule.editTitle', 'Edit activity') : t(chrome.titleKey, chrome.titleFallback)}</DialogTitle>\n </VisuallyHidden>\n\n {/* Header */}\n <div className=\"flex shrink-0 items-start justify-between gap-3 border-b border-border bg-background px-6 py-5\">\n <div className=\"flex flex-col gap-1\">\n <h2 className=\"text-lg font-semibold leading-tight tracking-tight text-foreground\">\n {isEditing ? t('customers.schedule.editTitle', 'Edit activity') : t(chrome.titleKey, chrome.titleFallback)}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(chrome.subtitleKey, chrome.subtitleFallback)}\n </p>\n {entityName ? (\n <p className=\"mt-0.5 text-xs text-muted-foreground/80\">\n {t('customers.schedule.context', 'On timeline: {{name}}', { name: entityName })}\n {companyName ? ` \u00B7 ${companyName}` : ''}\n </p>\n ) : null}\n </div>\n <IconButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => { void guardedClose() }} className=\"flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-background\" aria-label={t('customers.schedule.cancel', 'Cancel')}>\n <X className=\"size-4 text-muted-foreground\" />\n </IconButton>\n </div>\n\n <div className=\"flex-1 overflow-y-auto\">\n <div className=\"flex flex-col gap-4 bg-background p-6\">\n\n {/* Conflict warning */}\n {state.conflict && (\n <Alert variant=\"warning\" className=\"rounded-lg\">\n <AlertTriangle className=\"size-5\" />\n <AlertTitle>\n {t('customers.schedule.conflict.title', 'Calendar conflict')}\n </AlertTitle>\n <AlertDescription>{state.conflict}</AlertDescription>\n </Alert>\n )}\n\n {/* Type tabs \u2014 large rectangular tiles per Figma */}\n <div className=\"grid grid-cols-4 gap-2\">\n {TYPE_TABS.map(({ type, icon: Icon, labelKey, fallback }) => {\n const isActive = state.activityType === type\n return (\n <button\n key={type}\n type=\"button\"\n onClick={() => state.setActivityType(type)}\n aria-pressed={isActive}\n className={cn(\n 'flex h-[80px] flex-col items-center justify-center gap-2 rounded-md border text-[14px] font-semibold transition-colors',\n isActive\n ? 'border-transparent bg-foreground text-background'\n : 'border-border bg-card text-muted-foreground hover:border-foreground/40 hover:text-foreground',\n )}\n >\n <Icon className=\"size-[18px]\" />\n {t(labelKey, fallback)}\n </button>\n )\n })}\n </div>\n\n {/* Title */}\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-overline font-semibold text-muted-foreground tracking-wider\">\n {getFieldLabel(state.activityType, 'title', t, 'customers.schedule.titleLabel', 'Title')}\n </label>\n <input\n type=\"text\"\n value={state.title}\n onChange={(e) => state.setTitle(e.target.value)}\n placeholder={\n state.activityType === 'email'\n ? t('customers.schedule.subjectPlaceholder', 'Subject...')\n : t('customers.schedule.titlePlaceholder', 'Activity title...')\n }\n className=\"w-full rounded-md border border-border bg-background px-3 py-2.5 text-sm text-foreground outline-none focus:border-foreground\"\n autoFocus\n />\n </div>\n\n {/* Date/Time/Duration \u2014 placed before per-type chip rows so the call/task\n workflows match Figma 829:50 / 790:280 (date row first, then status chips). */}\n <DateTimeFields\n visible={visibleFields}\n activityType={state.activityType}\n date={state.date}\n setDate={state.setDate}\n startTime={state.startTime}\n setStartTime={state.setStartTime}\n duration={state.duration}\n setDuration={state.setDuration}\n allDay={state.allDay}\n setAllDay={state.setAllDay}\n recurrenceEnabled={state.recurrenceEnabled}\n setRecurrenceEnabled={state.setRecurrenceEnabled}\n recurrenceDays={state.recurrenceDays}\n toggleRecurrenceDay={state.toggleRecurrenceDay}\n recurrenceEndType={state.recurrenceEndType}\n setRecurrenceEndType={state.setRecurrenceEndType}\n recurrenceCount={state.recurrenceCount}\n setRecurrenceCount={state.setRecurrenceCount}\n recurrenceEndDate={state.recurrenceEndDate}\n setRecurrenceEndDate={state.setRecurrenceEndDate}\n />\n\n {/* Call: Direction + Outcome chips */}\n {state.activityType === 'call' && (\n <div className=\"flex flex-col gap-3\">\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.call.directionLabel', 'Direction')}\n </label>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {CALL_DIRECTIONS.map((opt) => {\n const isActive = callDirection === opt.key\n return (\n <button\n key={opt.key}\n type=\"button\"\n aria-pressed={isActive}\n onClick={() => setCallDirection(opt.key)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-sm font-medium transition-colors',\n isActive\n ? 'border-transparent bg-foreground text-background'\n : 'border-border bg-card text-muted-foreground hover:border-foreground/40',\n )}\n >\n <span className={cn('inline-block size-1.5 rounded-full', opt.dot)} aria-hidden />\n {t(opt.labelKey, opt.labelFallback)}\n </button>\n )\n })}\n </div>\n </div>\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.call.outcomeLabel', 'Outcome')}\n </label>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {CALL_OUTCOMES.map((opt) => {\n const isActive = callOutcome === opt.key\n return (\n <button\n key={opt.key}\n type=\"button\"\n aria-pressed={isActive}\n onClick={() => setCallOutcome(isActive ? null : opt.key)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-sm font-medium transition-colors',\n isActive\n ? 'border-transparent bg-foreground text-background'\n : 'border-border bg-card text-muted-foreground hover:border-foreground/40',\n )}\n >\n <span className={cn('inline-block size-1.5 rounded-full', opt.dot)} aria-hidden />\n {t(opt.labelKey, opt.labelFallback)}\n </button>\n )\n })}\n </div>\n </div>\n </div>\n )}\n\n {/* Task: Priority chips */}\n {state.activityType === 'task' && (\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.task.priorityLabel', 'Priority')}\n </label>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {TASK_PRIORITIES.map((opt) => {\n const isActive = taskPriority === opt.key\n return (\n <button\n key={opt.key}\n type=\"button\"\n aria-pressed={isActive}\n onClick={() => setTaskPriority(opt.key)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-sm font-medium transition-colors',\n isActive\n ? 'border-transparent bg-foreground text-background'\n : 'border-border bg-card text-muted-foreground hover:border-foreground/40',\n )}\n >\n <span className={cn('inline-block size-1.5 rounded-full', opt.dot)} aria-hidden />\n {t(opt.labelKey, opt.labelFallback)}\n </button>\n )\n })}\n </div>\n </div>\n )}\n\n {/* Participants */}\n <ParticipantsField\n visible={visibleFields}\n activityType={state.activityType}\n participants={state.participants}\n setParticipants={state.setParticipants}\n removeParticipant={state.removeParticipant}\n guestPermissions={state.guestPermissions}\n setGuestPermissions={state.setGuestPermissions}\n />\n\n {/* Location (or phone number for calls) */}\n {state.activityType === 'call' ? (\n <div className=\"flex flex-col gap-1.5\">\n <label htmlFor=\"schedule-call-phone\" className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.call.phoneLabel', 'Phone number')}\n </label>\n <PhoneNumberField\n id=\"schedule-call-phone\"\n value={callPhoneNumber}\n onValueChange={handleCallPhoneChange}\n placeholder={t('customers.schedule.call.phonePlaceholder', '+1 555 000 0000')}\n externalError={callPhoneError}\n invalidLabel={callPhoneInvalidMessage}\n minDigits={7}\n />\n </div>\n ) : (\n <LocationField\n visible={visibleFields}\n activityType={state.activityType}\n location={state.location}\n setLocation={state.setLocation}\n />\n )}\n\n {/* Linked Entities */}\n <LinkedEntitiesField\n visible={visibleFields}\n activityType={state.activityType}\n linkedEntities={state.linkedEntities}\n setLinkedEntities={state.setLinkedEntities}\n />\n\n {/* Description */}\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {getFieldLabel(state.activityType, 'description', t, 'customers.schedule.description', 'Description')}\n </label>\n <div className=\"mt-[8px]\">\n <SwitchableMarkdownInput\n value={state.description}\n onChange={state.setDescription}\n isMarkdownEnabled={state.markdownEnabled}\n height={120}\n placeholder={t('customers.schedule.descriptionPlaceholder', 'Add details...')}\n />\n </div>\n </div>\n\n {/* Reminder + Visibility */}\n <FooterFields\n visible={visibleFields}\n activityType={state.activityType}\n reminderMinutes={state.reminderMinutes}\n setReminderMinutes={state.setReminderMinutes}\n visibility={state.visibility}\n setVisibility={state.setVisibility}\n />\n\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"flex shrink-0 items-center justify-end gap-2.5 border-t border-border bg-muted/50 px-6 py-4\">\n <Button type=\"button\" variant=\"outline\" onClick={() => { void guardedClose() }} className=\"rounded-md border border-input bg-background px-5 py-3 text-sm font-semibold text-foreground\">\n {t('customers.schedule.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={isSubmitDisabled} className=\"flex items-center gap-2 rounded-md bg-foreground px-5 py-3 text-sm font-semibold text-background hover:bg-foreground/90 disabled:opacity-50\">\n <SaveIcon className=\"size-3.5\" />\n {state.saving\n ? t('customers.schedule.saving', 'Saving...')\n : isEditing\n ? t('customers.schedule.update', 'Update activity')\n : t(chrome.saveKey, chrome.saveFallback)}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport type { ScheduleActivityEditData }\n\nfunction buildRecurrenceRule(\n days: boolean[],\n endType: 'never' | 'count' | 'date',\n count: number,\n endDate: string,\n): string {\n const dayNames = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']\n const selectedDays = days.map((active, i) => (active ? dayNames[i] : null)).filter(Boolean)\n let rule = `FREQ=WEEKLY;BYDAY=${selectedDays.join(',')}`\n if (endType === 'count') rule += `;COUNT=${count}`\n if (endType === 'date' && endDate) rule += `;UNTIL=${endDate.replace(/-/g, '')}T235959Z`\n return rule\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Users, Phone, Check, Mail, Calendar, AlertTriangle, X, StickyNote } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { validatePhoneNumber } from '@open-mercato/shared/lib/phone'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { mapCrudServerErrorToFormErrors } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { PhoneNumberField, SwitchableMarkdownInput } from '@open-mercato/ui/backend/inputs'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n useScheduleFormState,\n FIELD_VISIBILITY,\n getFieldLabel,\n DateTimeFields,\n ParticipantsField,\n LocationField,\n FooterFields,\n LinkedEntitiesField,\n} from './schedule'\nimport type { ActivityType, ScheduleActivityEditData } from './schedule'\n\nconst TYPE_TABS: Array<{ type: ActivityType; icon: React.ComponentType<{ className?: string }>; labelKey: string; fallback: string }> = [\n { type: 'meeting', icon: Users, labelKey: 'customers.schedule.types.meeting', fallback: 'Meeting' },\n { type: 'call', icon: Phone, labelKey: 'customers.schedule.types.call', fallback: 'Call' },\n { type: 'task', icon: Check, labelKey: 'customers.schedule.types.task', fallback: 'Task' },\n { type: 'email', icon: Mail, labelKey: 'customers.schedule.types.email', fallback: 'Email' },\n { type: 'note', icon: StickyNote, labelKey: 'customers.schedule.types.note', fallback: 'Note' },\n]\n\ntype DialogChrome = { titleKey: string; titleFallback: string; subtitleKey: string; subtitleFallback: string; saveKey: string; saveFallback: string; saveIcon: React.ComponentType<{ className?: string }> }\n\nconst TYPE_CHROME: Record<ActivityType, DialogChrome> = {\n meeting: {\n titleKey: 'customers.schedule.meeting.title', titleFallback: 'New meeting',\n subtitleKey: 'customers.schedule.meeting.subtitle', subtitleFallback: 'Block time on the calendar with attendees',\n saveKey: 'customers.schedule.meeting.save', saveFallback: 'Save activity', saveIcon: Calendar,\n },\n call: {\n titleKey: 'customers.schedule.call.title', titleFallback: 'Log call',\n subtitleKey: 'customers.schedule.call.subtitle', subtitleFallback: 'Log a call you just had or schedule one',\n saveKey: 'customers.schedule.call.save', saveFallback: 'Log call', saveIcon: Phone,\n },\n task: {\n titleKey: 'customers.schedule.task.title', titleFallback: 'New task',\n subtitleKey: 'customers.schedule.task.subtitle', subtitleFallback: 'Capture something to follow up on',\n saveKey: 'customers.schedule.task.save', saveFallback: 'Save task', saveIcon: Check,\n },\n email: {\n titleKey: 'customers.schedule.email.title', titleFallback: 'Compose email',\n subtitleKey: 'customers.schedule.email.subtitle', subtitleFallback: 'Compose and send a tracked email',\n saveKey: 'customers.schedule.email.save', saveFallback: 'Send email', saveIcon: Mail,\n },\n note: {\n titleKey: 'customers.schedule.note.title', titleFallback: 'Add note',\n subtitleKey: 'customers.schedule.note.subtitle', subtitleFallback: 'Write down a note about this interaction',\n saveKey: 'customers.schedule.note.save', saveFallback: 'Save note', saveIcon: StickyNote,\n },\n}\n\nconst CALL_DIRECTIONS: Array<{ key: 'outbound' | 'inbound'; labelKey: string; labelFallback: string; dot: string }> = [\n { key: 'outbound', labelKey: 'customers.schedule.call.direction.outbound', labelFallback: 'Outbound', dot: 'bg-status-info-icon' },\n { key: 'inbound', labelKey: 'customers.schedule.call.direction.inbound', labelFallback: 'Inbound', dot: 'bg-status-success-icon' },\n]\n\nconst CALL_OUTCOMES: Array<{ key: string; labelKey: string; labelFallback: string; dot: string }> = [\n { key: 'connected', labelKey: 'customers.schedule.call.outcome.connected', labelFallback: 'Connected', dot: 'bg-status-success-icon' },\n { key: 'voicemail', labelKey: 'customers.schedule.call.outcome.voicemail', labelFallback: 'Voicemail', dot: 'bg-status-warning-icon' },\n { key: 'noanswer', labelKey: 'customers.schedule.call.outcome.noAnswer', labelFallback: 'No answer', dot: 'bg-muted-foreground' },\n { key: 'busy', labelKey: 'customers.schedule.call.outcome.busy', labelFallback: 'Busy', dot: 'bg-status-warning-icon' },\n { key: 'badnumber', labelKey: 'customers.schedule.call.outcome.badNumber', labelFallback: 'Bad number', dot: 'bg-status-error-icon' },\n]\n\nconst TASK_PRIORITIES: Array<{ key: string; labelKey: string; labelFallback: string; dot: string }> = [\n { key: 'low', labelKey: 'customers.schedule.task.priority.low', labelFallback: 'Low', dot: 'bg-muted-foreground' },\n { key: 'medium', labelKey: 'customers.schedule.task.priority.medium', labelFallback: 'Medium', dot: 'bg-status-info-icon' },\n { key: 'high', labelKey: 'customers.schedule.task.priority.high', labelFallback: 'High', dot: 'bg-status-warning-icon' },\n { key: 'urgent', labelKey: 'customers.schedule.task.priority.urgent', labelFallback: 'Urgent', dot: 'bg-status-error-icon' },\n]\n\ninterface ScheduleActivityDialogProps {\n open: boolean\n onClose: () => void\n entityId: string\n dealId?: string | null\n entityName?: string\n companyName?: string | null\n entityType: 'company' | 'person' | 'deal'\n onActivityCreated?: () => void\n /** When provided, dialog opens in edit mode with pre-filled data */\n editData?: ScheduleActivityEditData | null\n}\n\nexport function ScheduleActivityDialog({\n open,\n onClose,\n entityId,\n dealId = null,\n entityName,\n companyName,\n entityType,\n onActivityCreated,\n editData,\n}: ScheduleActivityDialogProps) {\n const t = useT()\n const state = useScheduleFormState({ open, editData: editData ?? null })\n const visibleFields = FIELD_VISIBILITY[state.activityType]\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const isEditing = Boolean(editData?.id)\n const chrome = TYPE_CHROME[state.activityType]\n const SaveIcon = chrome.saveIcon\n const [callDirection, setCallDirection] = React.useState<'outbound' | 'inbound'>('outbound')\n const [callOutcome, setCallOutcome] = React.useState<string | null>(null)\n const [callPhoneNumber, setCallPhoneNumber] = React.useState('')\n const [callPhoneError, setCallPhoneError] = React.useState<string | null>(null)\n const [taskPriority, setTaskPriority] = React.useState<string>('medium')\n const callPhoneInvalidMessage = React.useMemo(\n () =>\n t(\n 'customers.activities.errors.phoneInvalid',\n 'Enter a valid phone number with country code (e.g. +1 212 555 1234)',\n ),\n [t],\n )\n const translateErrorMessage = React.useCallback(\n (message: string | undefined, fallback: string) => {\n const key = typeof message === 'string' ? message.trim() : ''\n return key ? t(key, key) : fallback\n },\n [t],\n )\n\n React.useEffect(() => {\n if (!open) return\n const raw = editData as (Record<string, unknown> & { customValues?: unknown; phoneNumber?: unknown }) | null | undefined\n const cv = (raw?.customValues && typeof raw.customValues === 'object' ? raw.customValues : null) as Record<string, unknown> | null\n setCallDirection(typeof cv?.callDirection === 'string' && cv.callDirection === 'inbound' ? 'inbound' : 'outbound')\n setCallOutcome(typeof cv?.callOutcome === 'string' ? cv.callOutcome : null)\n // Seed phone number from either top-level `phoneNumber` (newer write path)\n // or legacy `customValues.callPhoneNumber` so previously-saved calls still\n // round-trip on edit (#1808).\n const seededPhone =\n typeof raw?.phoneNumber === 'string' && raw.phoneNumber.trim().length > 0\n ? raw.phoneNumber\n : typeof cv?.callPhoneNumber === 'string'\n ? cv.callPhoneNumber\n : ''\n setCallPhoneNumber(seededPhone)\n setCallPhoneError(null)\n setTaskPriority(typeof cv?.taskPriority === 'string' ? cv.taskPriority : 'medium')\n }, [open, editData])\n\n // Reset per-type chip state when the user switches activity type in create mode.\n // In edit mode, the persisted customValues should win, so we skip the reset.\n React.useEffect(() => {\n if (!open || isEditing) return\n setCallDirection('outbound')\n setCallOutcome(null)\n setCallPhoneNumber('')\n setCallPhoneError(null)\n setTaskPriority('medium')\n }, [state.activityType, open, isEditing])\n\n const handleCallPhoneChange = React.useCallback((next: string | undefined) => {\n setCallPhoneNumber(next ?? '')\n setCallPhoneError(null)\n }, [])\n\n const formSnapshot = React.useMemo(() => JSON.stringify({\n activityType: state.activityType,\n title: state.title,\n date: state.date,\n startTime: state.startTime,\n duration: state.duration,\n allDay: state.allDay,\n description: state.description,\n location: state.location,\n reminderMinutes: state.reminderMinutes,\n visibility: state.visibility,\n participants: state.participants,\n linkedEntities: state.linkedEntities,\n recurrenceEnabled: state.recurrenceEnabled,\n recurrenceDays: state.recurrenceDays,\n recurrenceEndType: state.recurrenceEndType,\n recurrenceCount: state.recurrenceCount,\n recurrenceEndDate: state.recurrenceEndDate,\n guestPermissions: state.guestPermissions,\n }), [\n state.activityType, state.title, state.date, state.startTime, state.duration, state.allDay,\n state.description, state.location, state.reminderMinutes, state.visibility, state.participants,\n state.linkedEntities, state.recurrenceEnabled, state.recurrenceDays, state.recurrenceEndType,\n state.recurrenceCount, state.recurrenceEndDate, state.guestPermissions,\n ])\n const initialSnapshotRef = React.useRef<string | null>(null)\n const snapshotOpenKeyRef = React.useRef<string | null>(null)\n const snapshotSettleCountRef = React.useRef(0)\n const openKey = open ? `${editData?.id ?? 'new'}` : null\n React.useEffect(() => {\n if (!open) {\n initialSnapshotRef.current = null\n snapshotOpenKeyRef.current = null\n snapshotSettleCountRef.current = 0\n return\n }\n if (snapshotOpenKeyRef.current !== openKey) {\n snapshotOpenKeyRef.current = openKey\n snapshotSettleCountRef.current = 0\n initialSnapshotRef.current = null\n }\n if (snapshotSettleCountRef.current < 2) {\n initialSnapshotRef.current = formSnapshot\n snapshotSettleCountRef.current += 1\n }\n }, [open, openKey, formSnapshot])\n\n const isDirty = React.useCallback(() => {\n if (initialSnapshotRef.current == null) return false\n return initialSnapshotRef.current !== formSnapshot\n }, [formSnapshot])\n\n const guardedClose = React.useCallback(async () => {\n if (!isDirty()) {\n onClose()\n return\n }\n const ok = await confirm({\n title: t('customers.schedule.discardConfirm.title', 'Discard unsaved changes?'),\n description: t(\n 'customers.schedule.discardConfirm.description',\n 'You have unsaved edits in this activity. Save them first or continue to discard them.',\n ),\n confirmText: t('customers.schedule.discardConfirm.confirm', 'Discard'),\n cancelText: t('customers.schedule.discardConfirm.cancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (ok) onClose()\n }, [confirm, isDirty, onClose, t])\n\n const mutationContextId = React.useMemo(\n () => `customer-activity:${entityType}:${entityId}`,\n [entityId, entityType],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n entityType: 'company' | 'person' | 'deal'\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: mutationContextId,\n resourceKind:\n entityType === 'company'\n ? 'customers.company'\n : entityType === 'person'\n ? 'customers.person'\n : 'customers.deal',\n resourceId: entityId,\n entityType,\n retryLastMutation,\n }),\n [entityId, entityType, mutationContextId, retryLastMutation],\n )\n const runGuardedMutation = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload: Record<string, unknown>) =>\n runMutation({\n operation,\n mutationPayload,\n context: mutationContext,\n }),\n [mutationContext, runMutation],\n )\n\n // Conflict detection -- debounced check when date/time/duration changes\n React.useEffect(() => {\n if (!open || state.allDay || !state.date || !state.startTime) {\n state.setConflict(null)\n return\n }\n const timer = setTimeout(async () => {\n try {\n const localStart = new Date(`${state.date}T${state.startTime}:00`)\n const params = new URLSearchParams({\n date: state.date,\n startTime: state.startTime,\n duration: String(state.duration),\n })\n if (editData?.id) {\n params.set('excludeId', editData.id)\n }\n if (!Number.isNaN(localStart.getTime())) {\n params.set('timezoneOffsetMinutes', String(-localStart.getTimezoneOffset()))\n }\n const data = await readApiResultOrThrow<{\n hasConflicts: boolean\n conflicts: Array<{ id: string; title: string | null; startTime: string; endTime: string; type: string }>\n }>(`/api/customers/interactions/conflicts?${params.toString()}`)\n if (data?.hasConflicts && Array.isArray(data.conflicts) && data.conflicts.length > 0) {\n const descriptions = data.conflicts\n .map((c) => `${c.startTime}\u2013${c.endTime}: ${c.title ?? c.type}`)\n .join(', ')\n state.setConflict(\n t('customers.schedule.conflict.description', 'Overlaps with: {{items}}', { items: descriptions }),\n )\n } else {\n state.setConflict(null)\n }\n } catch {\n state.setConflict(null)\n }\n }, 500)\n return () => clearTimeout(timer)\n }, [editData?.id, open, state.date, state.startTime, state.duration, state.allDay, t]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const trimmedDate = state.date.trim()\n const trimmedStartTime = state.startTime.trim()\n const trimmedCallPhone = callPhoneNumber.trim()\n const isDateMissing = !trimmedDate\n const isTimeMissing = !state.allDay && !trimmedStartTime\n const isSubmitDisabled =\n state.saving ||\n !state.title.trim() ||\n isDateMissing ||\n isTimeMissing\n\n const handleSave = React.useCallback(async () => {\n if (!state.title.trim()) return\n if (isDateMissing) {\n flash(t('customers.activities.errors.dateRequired', 'Date is required'), 'error')\n return\n }\n if (isTimeMissing) {\n flash(t('customers.activities.errors.timeRequired', 'Time is required'), 'error')\n return\n }\n let phoneNumberForPayload: string | undefined\n if (state.activityType === 'call' && trimmedCallPhone) {\n const phoneValidation = validatePhoneNumber(trimmedCallPhone)\n if (!phoneValidation.valid) {\n setCallPhoneError(callPhoneInvalidMessage)\n flash(callPhoneInvalidMessage, 'error')\n return\n }\n phoneNumberForPayload = phoneValidation.normalized ?? trimmedCallPhone\n setCallPhoneError(null)\n if (phoneNumberForPayload !== callPhoneNumber) {\n setCallPhoneNumber(phoneNumberForPayload)\n }\n }\n state.setSaving(true)\n try {\n const scheduledAt = state.allDay\n ? new Date(`${state.date}T00:00:00`).toISOString()\n : new Date(`${state.date}T${state.startTime}:00`).toISOString()\n\n const recurrenceRule = state.recurrenceEnabled\n ? buildRecurrenceRule(state.recurrenceDays, state.recurrenceEndType, state.recurrenceCount, state.recurrenceEndDate)\n : null\n\n const isSaveEdit = Boolean(editData?.id)\n const customValues: Record<string, unknown> = {}\n if (state.activityType === 'call') {\n customValues.callDirection = callDirection\n if (callOutcome) customValues.callOutcome = callOutcome\n if (phoneNumberForPayload) customValues.callPhoneNumber = phoneNumberForPayload\n }\n if (state.activityType === 'task') {\n customValues.taskPriority = taskPriority\n }\n const payload = {\n ...(isSaveEdit ? { id: editData!.id } : {}),\n entityId,\n dealId,\n interactionType: state.activityType,\n title: state.title.trim(),\n body: state.description.trim() || null,\n status: 'planned',\n date: trimmedDate,\n time: state.allDay ? '00:00' : trimmedStartTime,\n phoneNumber: state.activityType === 'call' ? phoneNumberForPayload : undefined,\n scheduledAt,\n durationMinutes: visibleFields.has('duration') && !state.allDay ? state.duration : null,\n location: visibleFields.has('location') ? (state.location.trim() || null) : null,\n allDay: visibleFields.has('allDay') ? state.allDay : null,\n recurrenceRule: visibleFields.has('recurrence') ? recurrenceRule : null,\n recurrenceEnd: visibleFields.has('recurrence') && state.recurrenceEndType === 'date' && state.recurrenceEndDate\n ? new Date(state.recurrenceEndDate).toISOString()\n : null,\n participants: visibleFields.has('participants') && state.participants.length > 0\n ? state.participants.map((p) => ({ userId: p.userId, name: p.name, email: p.email, status: p.status ?? 'pending' }))\n : null,\n guestPermissions: visibleFields.has('participants') && state.participants.length > 0 ? state.guestPermissions : null,\n linkedEntities: state.linkedEntities.length > 0\n ? state.linkedEntities.map((e) => ({ id: e.id, type: e.type, label: e.label }))\n : null,\n reminderMinutes: visibleFields.has('reminder') ? state.reminderMinutes : null,\n visibility: visibleFields.has('visibility') ? state.visibility : null,\n ...(Object.keys(customValues).length > 0 ? { customValues } : {}),\n }\n await runGuardedMutation(\n () =>\n apiCallOrThrow('/api/customers/interactions', {\n method: isSaveEdit ? 'PUT' : 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n {\n operation: isSaveEdit ? 'updateActivity' : 'createActivity',\n interactionId: editData?.id ?? null,\n interactionType: state.activityType,\n },\n )\n flash(t('customers.schedule.saved', 'Activity scheduled'), 'success')\n onClose()\n // Delay data reload so the dialog can unmount cleanly and Radix restores body scroll\n requestAnimationFrame(() => { onActivityCreated?.() })\n } catch (err) {\n const { message, fieldErrors } = mapCrudServerErrorToFormErrors(err)\n const phoneFieldError = fieldErrors?.phoneNumber\n if (state.activityType === 'call' && phoneFieldError) {\n const translatedPhoneError = translateErrorMessage(phoneFieldError, callPhoneInvalidMessage)\n setCallPhoneError(translatedPhoneError)\n flash(translatedPhoneError, 'error')\n return\n }\n flash(\n translateErrorMessage(message, t('customers.schedule.error', 'Failed to schedule activity')),\n 'error',\n )\n } finally {\n state.setSaving(false)\n }\n }, [callDirection, callOutcome, callPhoneInvalidMessage, callPhoneNumber, isDateMissing, isTimeMissing, state.activityType, state.allDay, state.date, state.description, dealId, state.duration, editData, entityId, state.guestPermissions, state.linkedEntities, state.location, onActivityCreated, onClose, state.participants, state.recurrenceCount, state.recurrenceDays, state.recurrenceEnabled, state.recurrenceEndDate, state.recurrenceEndType, state.reminderMinutes, runGuardedMutation, state.startTime, t, taskPriority, state.title, translateErrorMessage, trimmedCallPhone, trimmedDate, trimmedStartTime, state.visibility, visibleFields]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleKeyDown = React.useCallback((e: React.KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n handleSave()\n }\n }, [handleSave])\n\n return (\n <Dialog open={open} onOpenChange={(o) => { if (!o) void guardedClose() }}>\n {ConfirmDialogElement}\n <DialogContent className=\"flex max-h-[90vh] flex-col overflow-hidden border-border p-0 shadow-xl sm:max-w-[760px] sm:rounded-xl [&>[data-dialog-close]]:hidden\" onKeyDown={handleKeyDown} aria-describedby={undefined}>\n <VisuallyHidden>\n <DialogTitle>{isEditing ? t('customers.schedule.editTitle', 'Edit activity') : t(chrome.titleKey, chrome.titleFallback)}</DialogTitle>\n </VisuallyHidden>\n\n {/* Header */}\n <div className=\"flex shrink-0 items-start justify-between gap-3 border-b border-border bg-background px-6 py-5\">\n <div className=\"flex flex-col gap-1\">\n <h2 className=\"text-lg font-semibold leading-tight tracking-tight text-foreground\">\n {isEditing ? t('customers.schedule.editTitle', 'Edit activity') : t(chrome.titleKey, chrome.titleFallback)}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(chrome.subtitleKey, chrome.subtitleFallback)}\n </p>\n {entityName ? (\n <p className=\"mt-0.5 text-xs text-muted-foreground/80\">\n {t('customers.schedule.context', 'On timeline: {{name}}', { name: entityName })}\n {companyName ? ` \u00B7 ${companyName}` : ''}\n </p>\n ) : null}\n </div>\n <IconButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => { void guardedClose() }} className=\"flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-background\" aria-label={t('customers.schedule.cancel', 'Cancel')}>\n <X className=\"size-4 text-muted-foreground\" />\n </IconButton>\n </div>\n\n <div className=\"flex-1 overflow-y-auto\">\n <div className=\"flex flex-col gap-4 bg-background p-6\">\n\n {/* Conflict warning */}\n {state.conflict && (\n <Alert variant=\"warning\" className=\"rounded-lg\">\n <AlertTriangle className=\"size-5\" />\n <AlertTitle>\n {t('customers.schedule.conflict.title', 'Calendar conflict')}\n </AlertTitle>\n <AlertDescription>{state.conflict}</AlertDescription>\n </Alert>\n )}\n\n {/* Type tabs \u2014 large rectangular tiles per Figma */}\n <div className=\"grid grid-cols-4 gap-2\">\n {TYPE_TABS.map(({ type, icon: Icon, labelKey, fallback }) => {\n const isActive = state.activityType === type\n return (\n <button\n key={type}\n type=\"button\"\n onClick={() => state.setActivityType(type)}\n aria-pressed={isActive}\n className={cn(\n 'flex h-[80px] flex-col items-center justify-center gap-2 rounded-md border text-[14px] font-semibold transition-colors',\n isActive\n ? 'border-transparent bg-foreground text-background'\n : 'border-border bg-card text-muted-foreground hover:border-foreground/40 hover:text-foreground',\n )}\n >\n <Icon className=\"size-[18px]\" />\n {t(labelKey, fallback)}\n </button>\n )\n })}\n </div>\n\n {/* Title */}\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-overline font-semibold text-muted-foreground tracking-wider\">\n {getFieldLabel(state.activityType, 'title', t, 'customers.schedule.titleLabel', 'Title')}\n </label>\n <input\n type=\"text\"\n value={state.title}\n onChange={(e) => state.setTitle(e.target.value)}\n placeholder={\n state.activityType === 'email'\n ? t('customers.schedule.subjectPlaceholder', 'Subject...')\n : t('customers.schedule.titlePlaceholder', 'Activity title...')\n }\n className=\"w-full rounded-md border border-border bg-background px-3 py-2.5 text-sm text-foreground outline-none focus:border-foreground\"\n autoFocus\n />\n </div>\n\n {/* Date/Time/Duration \u2014 placed before per-type chip rows so the call/task\n workflows match Figma 829:50 / 790:280 (date row first, then status chips). */}\n <DateTimeFields\n visible={visibleFields}\n activityType={state.activityType}\n date={state.date}\n setDate={state.setDate}\n startTime={state.startTime}\n setStartTime={state.setStartTime}\n duration={state.duration}\n setDuration={state.setDuration}\n allDay={state.allDay}\n setAllDay={state.setAllDay}\n recurrenceEnabled={state.recurrenceEnabled}\n setRecurrenceEnabled={state.setRecurrenceEnabled}\n recurrenceDays={state.recurrenceDays}\n toggleRecurrenceDay={state.toggleRecurrenceDay}\n recurrenceEndType={state.recurrenceEndType}\n setRecurrenceEndType={state.setRecurrenceEndType}\n recurrenceCount={state.recurrenceCount}\n setRecurrenceCount={state.setRecurrenceCount}\n recurrenceEndDate={state.recurrenceEndDate}\n setRecurrenceEndDate={state.setRecurrenceEndDate}\n />\n\n {/* Call: Direction + Outcome chips */}\n {state.activityType === 'call' && (\n <div className=\"flex flex-col gap-3\">\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.call.directionLabel', 'Direction')}\n </label>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {CALL_DIRECTIONS.map((opt) => {\n const isActive = callDirection === opt.key\n return (\n <button\n key={opt.key}\n type=\"button\"\n aria-pressed={isActive}\n onClick={() => setCallDirection(opt.key)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-sm font-medium transition-colors',\n isActive\n ? 'border-transparent bg-foreground text-background'\n : 'border-border bg-card text-muted-foreground hover:border-foreground/40',\n )}\n >\n <span className={cn('inline-block size-1.5 rounded-full', opt.dot)} aria-hidden />\n {t(opt.labelKey, opt.labelFallback)}\n </button>\n )\n })}\n </div>\n </div>\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.call.outcomeLabel', 'Outcome')}\n </label>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {CALL_OUTCOMES.map((opt) => {\n const isActive = callOutcome === opt.key\n return (\n <button\n key={opt.key}\n type=\"button\"\n aria-pressed={isActive}\n onClick={() => setCallOutcome(isActive ? null : opt.key)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-sm font-medium transition-colors',\n isActive\n ? 'border-transparent bg-foreground text-background'\n : 'border-border bg-card text-muted-foreground hover:border-foreground/40',\n )}\n >\n <span className={cn('inline-block size-1.5 rounded-full', opt.dot)} aria-hidden />\n {t(opt.labelKey, opt.labelFallback)}\n </button>\n )\n })}\n </div>\n </div>\n </div>\n )}\n\n {/* Task: Priority chips */}\n {state.activityType === 'task' && (\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.task.priorityLabel', 'Priority')}\n </label>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {TASK_PRIORITIES.map((opt) => {\n const isActive = taskPriority === opt.key\n return (\n <button\n key={opt.key}\n type=\"button\"\n aria-pressed={isActive}\n onClick={() => setTaskPriority(opt.key)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-sm font-medium transition-colors',\n isActive\n ? 'border-transparent bg-foreground text-background'\n : 'border-border bg-card text-muted-foreground hover:border-foreground/40',\n )}\n >\n <span className={cn('inline-block size-1.5 rounded-full', opt.dot)} aria-hidden />\n {t(opt.labelKey, opt.labelFallback)}\n </button>\n )\n })}\n </div>\n </div>\n )}\n\n {/* Participants */}\n <ParticipantsField\n visible={visibleFields}\n activityType={state.activityType}\n participants={state.participants}\n setParticipants={state.setParticipants}\n removeParticipant={state.removeParticipant}\n guestPermissions={state.guestPermissions}\n setGuestPermissions={state.setGuestPermissions}\n />\n\n {/* Location (or phone number for calls) */}\n {state.activityType === 'call' ? (\n <div className=\"flex flex-col gap-1.5\">\n <label htmlFor=\"schedule-call-phone\" className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.call.phoneLabel', 'Phone number')}\n </label>\n <PhoneNumberField\n id=\"schedule-call-phone\"\n value={callPhoneNumber}\n onValueChange={handleCallPhoneChange}\n placeholder={t('customers.schedule.call.phonePlaceholder', '+1 555 000 0000')}\n externalError={callPhoneError}\n invalidLabel={callPhoneInvalidMessage}\n minDigits={7}\n />\n </div>\n ) : (\n <LocationField\n visible={visibleFields}\n activityType={state.activityType}\n location={state.location}\n setLocation={state.setLocation}\n />\n )}\n\n {/* Linked Entities */}\n <LinkedEntitiesField\n visible={visibleFields}\n activityType={state.activityType}\n linkedEntities={state.linkedEntities}\n setLinkedEntities={state.setLinkedEntities}\n />\n\n {/* Description */}\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {getFieldLabel(state.activityType, 'description', t, 'customers.schedule.description', 'Description')}\n </label>\n <div className=\"mt-[8px]\">\n <SwitchableMarkdownInput\n value={state.description}\n onChange={state.setDescription}\n isMarkdownEnabled={state.markdownEnabled}\n height={120}\n placeholder={t('customers.schedule.descriptionPlaceholder', 'Add details...')}\n />\n </div>\n </div>\n\n {/* Reminder + Visibility */}\n <FooterFields\n visible={visibleFields}\n activityType={state.activityType}\n reminderMinutes={state.reminderMinutes}\n setReminderMinutes={state.setReminderMinutes}\n visibility={state.visibility}\n setVisibility={state.setVisibility}\n />\n\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"flex shrink-0 items-center justify-end gap-2.5 border-t border-border bg-muted/50 px-6 py-4\">\n <Button type=\"button\" variant=\"outline\" onClick={() => { void guardedClose() }} className=\"rounded-md border border-input bg-background px-5 py-3 text-sm font-semibold text-foreground\">\n {t('customers.schedule.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={isSubmitDisabled} className=\"flex items-center gap-2 rounded-md bg-foreground px-5 py-3 text-sm font-semibold text-background hover:bg-foreground/90 disabled:opacity-50\">\n <SaveIcon className=\"size-3.5\" />\n {state.saving\n ? t('customers.schedule.saving', 'Saving...')\n : isEditing\n ? t('customers.schedule.update', 'Update activity')\n : t(chrome.saveKey, chrome.saveFallback)}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport type { ScheduleActivityEditData }\n\nfunction buildRecurrenceRule(\n days: boolean[],\n endType: 'never' | 'count' | 'date',\n count: number,\n endDate: string,\n): string {\n const dayNames = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']\n const selectedDays = days.map((active, i) => (active ? dayNames[i] : null)).filter(Boolean)\n let rule = `FREQ=WEEKLY;BYDAY=${selectedDays.join(',')}`\n if (endType === 'count') rule += `;COUNT=${count}`\n if (endType === 'date' && endDate) rule += `;UNTIL=${endDate.replace(/-/g, '')}T235959Z`\n return rule\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -47,6 +47,12 @@ const FIELD_VISIBILITY = {
47
47
  "description",
48
48
  "reminder",
49
49
  "visibility"
50
+ ]),
51
+ note: /* @__PURE__ */ new Set([
52
+ "title",
53
+ "linkedEntities",
54
+ "description",
55
+ "visibility"
50
56
  ])
51
57
  };
52
58
  const FIELD_LABEL_OVERRIDES = {
@@ -71,6 +77,10 @@ const FIELD_LABEL_OVERRIDES = {
71
77
  participants: { key: "customers.schedule.to", fallback: "To" },
72
78
  linkedEntities: { key: "customers.schedule.connections", fallback: "Connections" },
73
79
  description: { key: "customers.schedule.message", fallback: "Message" }
80
+ },
81
+ note: {
82
+ linkedEntities: { key: "customers.schedule.connections", fallback: "Connections" },
83
+ description: { key: "customers.schedule.note.content", fallback: "Note" }
74
84
  }
75
85
  };
76
86
  function isVisible(type, fieldId) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/components/detail/schedule/fieldConfig.ts"],
4
- "sourcesContent": ["export type ActivityType = 'meeting' | 'call' | 'task' | 'email'\n\nexport type ScheduleFieldId =\n | 'title'\n | 'date'\n | 'startTime'\n | 'duration'\n | 'allDay'\n | 'timezone'\n | 'recurrence'\n | 'participants'\n | 'guestPermissions'\n | 'location'\n | 'linkedEntities'\n | 'description'\n | 'reminder'\n | 'visibility'\n\nexport const FIELD_VISIBILITY: Record<ActivityType, Set<ScheduleFieldId>> = {\n meeting: new Set([\n 'title', 'date', 'startTime', 'duration', 'allDay', 'timezone', 'recurrence',\n 'participants', 'guestPermissions', 'location', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n call: new Set([\n 'title', 'date', 'startTime', 'duration',\n 'participants', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n // Task: now also surfaces Due time (startTime) + Estimate (duration) per Figma 790:280.\n task: new Set([\n 'title', 'date', 'startTime', 'duration',\n 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n // Email: surface participants as TO recipients per Figma 790:510.\n email: new Set([\n 'title', 'date', 'startTime',\n 'participants', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n}\n\ntype LabelOverride = { key: string; fallback: string }\n\n// Per-type section labels (Figma 784:1255 / 829:50 / 790:280 / 790:510).\n// `participants` / `linkedEntities` / `description` resolve via these overrides\n// when present; otherwise the field components fall back to their generic key.\nexport const FIELD_LABEL_OVERRIDES: Partial<\n Record<ActivityType, Partial<Record<ScheduleFieldId, LabelOverride>>>\n> = {\n meeting: {\n participants: { key: 'customers.schedule.attendees', fallback: 'Attendees' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n },\n call: {\n participants: { key: 'customers.schedule.contact', fallback: 'Contact' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.callNotes', fallback: 'Call notes' },\n },\n task: {\n date: { key: 'customers.schedule.dueDate', fallback: 'Due date' },\n startTime: { key: 'customers.schedule.dueTime', fallback: 'Due time' },\n duration: { key: 'customers.schedule.estimate', fallback: 'Estimate' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.details', fallback: 'Details' },\n },\n email: {\n title: { key: 'customers.schedule.subject', fallback: 'Subject' },\n participants: { key: 'customers.schedule.to', fallback: 'To' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.message', fallback: 'Message' },\n },\n}\n\nexport function isVisible(type: ActivityType, fieldId: ScheduleFieldId): boolean {\n return FIELD_VISIBILITY[type].has(fieldId)\n}\n\nexport function getFieldLabel(\n type: ActivityType,\n fieldId: ScheduleFieldId,\n t: (key: string, fallback: string) => string,\n defaultKey: string,\n defaultFallback: string,\n): string {\n const override = FIELD_LABEL_OVERRIDES[type]?.[fieldId]\n if (override) return t(override.key, override.fallback)\n return t(defaultKey, defaultFallback)\n}\n"],
5
- "mappings": "AAkBO,MAAM,mBAA+D;AAAA,EAC1E,SAAS,oBAAI,IAAI;AAAA,IACf;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAAY;AAAA,IAAU;AAAA,IAAY;AAAA,IAChE;AAAA,IAAgB;AAAA,IAAoB;AAAA,IAAY;AAAA,IAAkB;AAAA,IAClE;AAAA,IAAY;AAAA,EACd,CAAC;AAAA,EACD,MAAM,oBAAI,IAAI;AAAA,IACZ;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAC9B;AAAA,IAAgB;AAAA,IAAkB;AAAA,IAClC;AAAA,IAAY;AAAA,EACd,CAAC;AAAA;AAAA,EAED,MAAM,oBAAI,IAAI;AAAA,IACZ;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAC9B;AAAA,IAAkB;AAAA,IAClB;AAAA,IAAY;AAAA,EACd,CAAC;AAAA;AAAA,EAED,OAAO,oBAAI,IAAI;AAAA,IACb;AAAA,IAAS;AAAA,IAAQ;AAAA,IACjB;AAAA,IAAgB;AAAA,IAAkB;AAAA,IAClC;AAAA,IAAY;AAAA,EACd,CAAC;AACH;AAOO,MAAM,wBAET;AAAA,EACF,SAAS;AAAA,IACP,cAAc,EAAE,KAAK,gCAAgC,UAAU,YAAY;AAAA,IAC3E,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,EACnF;AAAA,EACA,MAAM;AAAA,IACJ,cAAc,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,IACvE,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,gCAAgC,UAAU,aAAa;AAAA,EAC7E;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,EAAE,KAAK,8BAA8B,UAAU,WAAW;AAAA,IAChE,WAAW,EAAE,KAAK,8BAA8B,UAAU,WAAW;AAAA,IACrE,UAAU,EAAE,KAAK,+BAA+B,UAAU,WAAW;AAAA,IACrE,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,EACxE;AAAA,EACA,OAAO;AAAA,IACL,OAAO,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,IAChE,cAAc,EAAE,KAAK,yBAAyB,UAAU,KAAK;AAAA,IAC7D,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,EACxE;AACF;AAEO,SAAS,UAAU,MAAoB,SAAmC;AAC/E,SAAO,iBAAiB,IAAI,EAAE,IAAI,OAAO;AAC3C;AAEO,SAAS,cACd,MACA,SACA,GACA,YACA,iBACQ;AACR,QAAM,WAAW,sBAAsB,IAAI,IAAI,OAAO;AACtD,MAAI,SAAU,QAAO,EAAE,SAAS,KAAK,SAAS,QAAQ;AACtD,SAAO,EAAE,YAAY,eAAe;AACtC;",
4
+ "sourcesContent": ["export type ActivityType = 'meeting' | 'call' | 'task' | 'email' | 'note'\n\nexport type ScheduleFieldId =\n | 'title'\n | 'date'\n | 'startTime'\n | 'duration'\n | 'allDay'\n | 'timezone'\n | 'recurrence'\n | 'participants'\n | 'guestPermissions'\n | 'location'\n | 'linkedEntities'\n | 'description'\n | 'reminder'\n | 'visibility'\n\nexport const FIELD_VISIBILITY: Record<ActivityType, Set<ScheduleFieldId>> = {\n meeting: new Set([\n 'title', 'date', 'startTime', 'duration', 'allDay', 'timezone', 'recurrence',\n 'participants', 'guestPermissions', 'location', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n call: new Set([\n 'title', 'date', 'startTime', 'duration',\n 'participants', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n // Task: now also surfaces Due time (startTime) + Estimate (duration) per Figma 790:280.\n task: new Set([\n 'title', 'date', 'startTime', 'duration',\n 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n // Email: surface participants as TO recipients per Figma 790:510.\n email: new Set([\n 'title', 'date', 'startTime',\n 'participants', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n note: new Set([\n 'title', 'linkedEntities', 'description', 'visibility',\n ]),\n}\n\ntype LabelOverride = { key: string; fallback: string }\n\n// Per-type section labels (Figma 784:1255 / 829:50 / 790:280 / 790:510).\n// `participants` / `linkedEntities` / `description` resolve via these overrides\n// when present; otherwise the field components fall back to their generic key.\nexport const FIELD_LABEL_OVERRIDES: Partial<\n Record<ActivityType, Partial<Record<ScheduleFieldId, LabelOverride>>>\n> = {\n meeting: {\n participants: { key: 'customers.schedule.attendees', fallback: 'Attendees' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n },\n call: {\n participants: { key: 'customers.schedule.contact', fallback: 'Contact' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.callNotes', fallback: 'Call notes' },\n },\n task: {\n date: { key: 'customers.schedule.dueDate', fallback: 'Due date' },\n startTime: { key: 'customers.schedule.dueTime', fallback: 'Due time' },\n duration: { key: 'customers.schedule.estimate', fallback: 'Estimate' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.details', fallback: 'Details' },\n },\n email: {\n title: { key: 'customers.schedule.subject', fallback: 'Subject' },\n participants: { key: 'customers.schedule.to', fallback: 'To' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.message', fallback: 'Message' },\n },\n note: {\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.note.content', fallback: 'Note' },\n },\n}\n\nexport function isVisible(type: ActivityType, fieldId: ScheduleFieldId): boolean {\n return FIELD_VISIBILITY[type].has(fieldId)\n}\n\nexport function getFieldLabel(\n type: ActivityType,\n fieldId: ScheduleFieldId,\n t: (key: string, fallback: string) => string,\n defaultKey: string,\n defaultFallback: string,\n): string {\n const override = FIELD_LABEL_OVERRIDES[type]?.[fieldId]\n if (override) return t(override.key, override.fallback)\n return t(defaultKey, defaultFallback)\n}\n"],
5
+ "mappings": "AAkBO,MAAM,mBAA+D;AAAA,EAC1E,SAAS,oBAAI,IAAI;AAAA,IACf;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAAY;AAAA,IAAU;AAAA,IAAY;AAAA,IAChE;AAAA,IAAgB;AAAA,IAAoB;AAAA,IAAY;AAAA,IAAkB;AAAA,IAClE;AAAA,IAAY;AAAA,EACd,CAAC;AAAA,EACD,MAAM,oBAAI,IAAI;AAAA,IACZ;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAC9B;AAAA,IAAgB;AAAA,IAAkB;AAAA,IAClC;AAAA,IAAY;AAAA,EACd,CAAC;AAAA;AAAA,EAED,MAAM,oBAAI,IAAI;AAAA,IACZ;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAC9B;AAAA,IAAkB;AAAA,IAClB;AAAA,IAAY;AAAA,EACd,CAAC;AAAA;AAAA,EAED,OAAO,oBAAI,IAAI;AAAA,IACb;AAAA,IAAS;AAAA,IAAQ;AAAA,IACjB;AAAA,IAAgB;AAAA,IAAkB;AAAA,IAClC;AAAA,IAAY;AAAA,EACd,CAAC;AAAA,EACD,MAAM,oBAAI,IAAI;AAAA,IACZ;AAAA,IAAS;AAAA,IAAkB;AAAA,IAAe;AAAA,EAC5C,CAAC;AACH;AAOO,MAAM,wBAET;AAAA,EACF,SAAS;AAAA,IACP,cAAc,EAAE,KAAK,gCAAgC,UAAU,YAAY;AAAA,IAC3E,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,EACnF;AAAA,EACA,MAAM;AAAA,IACJ,cAAc,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,IACvE,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,gCAAgC,UAAU,aAAa;AAAA,EAC7E;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,EAAE,KAAK,8BAA8B,UAAU,WAAW;AAAA,IAChE,WAAW,EAAE,KAAK,8BAA8B,UAAU,WAAW;AAAA,IACrE,UAAU,EAAE,KAAK,+BAA+B,UAAU,WAAW;AAAA,IACrE,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,EACxE;AAAA,EACA,OAAO;AAAA,IACL,OAAO,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,IAChE,cAAc,EAAE,KAAK,yBAAyB,UAAU,KAAK;AAAA,IAC7D,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,EACxE;AAAA,EACA,MAAM;AAAA,IACJ,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,mCAAmC,UAAU,OAAO;AAAA,EAC1E;AACF;AAEO,SAAS,UAAU,MAAoB,SAAmC;AAC/E,SAAO,iBAAiB,IAAI,EAAE,IAAI,OAAO;AAC3C;AAEO,SAAS,cACd,MACA,SACA,GACA,YACA,iBACQ;AACR,QAAM,WAAW,sBAAsB,IAAI,IAAI,OAAO;AACtD,MAAI,SAAU,QAAO,EAAE,SAAS,KAAK,SAAS,QAAQ;AACtD,SAAO,EAAE,YAAY,eAAe;AACtC;",
6
6
  "names": []
7
7
  }
@@ -11,7 +11,8 @@ const DEFAULT_REMINDER_MINUTES = {
11
11
  meeting: 15,
12
12
  call: 5,
13
13
  task: 1440,
14
- email: 15
14
+ email: 15,
15
+ note: 15
15
16
  };
16
17
  function padDatePart(value) {
17
18
  return String(value).padStart(2, "0");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/components/detail/schedule/useScheduleFormState.ts"],
4
- "sourcesContent": ["import * as React from 'react'\nimport type { ActivityType } from './fieldConfig'\n\nexport type RsvpStatus = 'pending' | 'accepted' | 'declined' | 'tentative'\n\nexport type Participant = {\n userId: string\n name: string\n email?: string\n color?: string\n status?: RsvpStatus\n}\n\nexport type LinkedEntity = {\n id: string\n type: 'company' | 'deal' | 'offer'\n label: string\n}\n\nexport type ScheduleActivityEditData = {\n id: string\n interactionType?: string\n title?: string | null\n body?: string | null\n scheduledAt?: string | null\n /**\n * Historical timestamp for completed activities (status `done`). Required for\n * the edit prefill to restore the original date/time instead of falling back\n * to \"today\" (#1807).\n */\n occurredAt?: string | null\n durationMinutes?: number | null\n location?: string | null\n allDay?: boolean | null\n recurrenceRule?: string | null\n recurrenceEnd?: string | null\n participants?: Array<{ userId: string; name?: string; email?: string; status?: string }> | null\n reminderMinutes?: number | null\n visibility?: string | null\n linkedEntities?: Array<{ id: string; type: string; label: string }> | null\n guestPermissions?: { canInviteOthers?: boolean; canModify?: boolean; canSeeList?: boolean } | null\n}\n\nexport const PARTICIPANT_COLORS = [\n 'bg-chart-emerald',\n 'bg-chart-blue',\n 'bg-chart-orange',\n 'bg-chart-violet',\n 'bg-chart-pink',\n 'bg-chart-teal',\n]\n\n// Per-Figma defaults for the Reminder dropdown when the user picks an activity\n// type. Meeting/email keep the standard 15 min; tasks default to 1 day (1440 min)\n// because they're plan-ahead artefacts; calls default to 5 min as a stand-in for\n// the Figma \"After call ends\" treatment (which would need a non-numeric sentinel\n// in the API contract \u2014 tracked as a follow-up).\nconst DEFAULT_REMINDER_MINUTES: Record<ActivityType, number> = {\n meeting: 15,\n call: 5,\n task: 1440,\n email: 15,\n}\n\nfunction padDatePart(value: number): string {\n return String(value).padStart(2, '0')\n}\n\nfunction formatLocalDateInput(date: Date): string {\n return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`\n}\n\nfunction formatLocalTimeInput(date: Date): string {\n return `${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`\n}\n\ninterface UseScheduleFormStateParams {\n open: boolean\n editData: ScheduleActivityEditData | null | undefined\n}\n\nexport function useScheduleFormState({ open, editData }: UseScheduleFormStateParams) {\n const [activityType, setActivityType] = React.useState<ActivityType>('meeting')\n const [title, setTitle] = React.useState('')\n const [date, setDate] = React.useState(() => formatLocalDateInput(new Date()))\n const [startTime, setStartTime] = React.useState('10:00')\n const [duration, setDuration] = React.useState(30)\n const [allDay, setAllDay] = React.useState(false)\n const [description, setDescription] = React.useState('')\n const [markdownEnabled, setMarkdownEnabled] = React.useState(true)\n const [location, setLocation] = React.useState('')\n const [reminderMinutes, setReminderMinutes] = React.useState(15)\n const [visibility, setVisibility] = React.useState('team')\n const [participants, setParticipants] = React.useState<Participant[]>([])\n const [linkedEntities, setLinkedEntities] = React.useState<LinkedEntity[]>([])\n const [recurrenceEnabled, setRecurrenceEnabled] = React.useState(false)\n const [recurrenceDays, setRecurrenceDays] = React.useState<boolean[]>([true, false, true, false, false, false, false])\n const [recurrenceEndType, setRecurrenceEndType] = React.useState<'never' | 'count' | 'date'>('never')\n const [recurrenceCount, setRecurrenceCount] = React.useState(8)\n const [recurrenceEndDate, setRecurrenceEndDate] = React.useState('')\n const [conflict, setConflict] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [guestPermissions, setGuestPermissions] = React.useState({ canInviteOthers: true, canModify: false, canSeeList: true })\n\n React.useEffect(() => {\n if (open) {\n if (editData) {\n // Edit mode: populate from existing interaction\n const resolvedType = (editData.interactionType as ActivityType) ?? 'meeting'\n setActivityType(resolvedType)\n setTitle(editData.title ?? '')\n // For historical activities the canonical timestamp is `occurredAt`; for\n // planned/future ones it's `scheduledAt`. Without this fallback editing a\n // past activity prefilled to \"today\" instead of its actual moment (#1807).\n // Keep seed values in the user's local timezone, matching the cluster-E\n // local-day convention.\n const sourceTimestamp = editData.occurredAt ?? editData.scheduledAt ?? null\n const seedDate = sourceTimestamp ? new Date(sourceTimestamp) : new Date()\n const seedDateValid = !Number.isNaN(seedDate.getTime())\n const fallbackNow = new Date()\n const dateForForm = seedDateValid ? seedDate : fallbackNow\n setDate(formatLocalDateInput(dateForForm))\n setStartTime(formatLocalTimeInput(dateForForm))\n setDuration(editData.durationMinutes ?? 30)\n setAllDay(editData.allDay ?? false)\n setDescription(editData.body ?? '')\n setLocation(editData.location ?? '')\n // Use per-type default when the editData omits an explicit reminder\n // (the menu-driven \"New X\" flow opens the dialog with `reminderMinutes: null`).\n setReminderMinutes(editData.reminderMinutes ?? DEFAULT_REMINDER_MINUTES[resolvedType])\n setVisibility(editData.visibility ?? 'team')\n setParticipants(\n Array.isArray(editData.participants)\n ? editData.participants.map((p, i) => ({\n userId: p.userId,\n name: p.name ?? p.userId,\n email: p.email,\n color: PARTICIPANT_COLORS[i % PARTICIPANT_COLORS.length],\n status: (p.status ?? 'pending') as RsvpStatus,\n }))\n : [],\n )\n setLinkedEntities(\n Array.isArray(editData.linkedEntities)\n ? editData.linkedEntities.map((e) => ({ id: e.id, type: e.type as LinkedEntity['type'], label: e.label }))\n : [],\n )\n if (editData.recurrenceRule) {\n setRecurrenceEnabled(true)\n // Parse RRULE to set days and end type\n const rule = editData.recurrenceRule\n const byDayMatch = rule.match(/BYDAY=([A-Z,]+)/)\n if (byDayMatch) {\n const dayNames = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']\n const activeDays = byDayMatch[1].split(',')\n setRecurrenceDays(dayNames.map((d) => activeDays.includes(d)))\n }\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const untilMatch = rule.match(/UNTIL=(\\d{8})/)\n if (countMatch) {\n setRecurrenceEndType('count')\n setRecurrenceCount(Number(countMatch[1]))\n } else if (untilMatch) {\n setRecurrenceEndType('date')\n const raw = untilMatch[1]\n setRecurrenceEndDate(`${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`)\n } else {\n setRecurrenceEndType('never')\n }\n } else {\n setRecurrenceEnabled(false)\n }\n if (editData.guestPermissions) {\n setGuestPermissions({\n canInviteOthers: editData.guestPermissions.canInviteOthers ?? true,\n canModify: editData.guestPermissions.canModify ?? false,\n canSeeList: editData.guestPermissions.canSeeList ?? true,\n })\n }\n } else {\n // Create mode: reset all fields\n setActivityType('meeting')\n setTitle('')\n setDate(formatLocalDateInput(new Date()))\n setStartTime('10:00')\n setDuration(30)\n setAllDay(false)\n setDescription('')\n setLocation('')\n setReminderMinutes(DEFAULT_REMINDER_MINUTES.meeting)\n setVisibility('team')\n setParticipants([])\n setLinkedEntities([])\n setRecurrenceEnabled(false)\n }\n setConflict(null)\n }\n return () => {\n // Safety net: restore body scroll if Radix Dialog fails to clean up\n document.body.style.removeProperty('overflow')\n document.body.style.removeProperty('pointer-events')\n }\n }, [open, editData])\n\n // Update the Reminder default when the activity type changes in create mode.\n // Skipped in edit mode (the persisted value wins), and gated by `open` to\n // avoid flipping the default in a closed-but-mounted dialog.\n const lastReminderTypeRef = React.useRef<ActivityType>('meeting')\n React.useEffect(() => {\n if (!open || editData) {\n lastReminderTypeRef.current = activityType\n return\n }\n if (lastReminderTypeRef.current === activityType) return\n lastReminderTypeRef.current = activityType\n setReminderMinutes(DEFAULT_REMINDER_MINUTES[activityType])\n }, [activityType, editData, open])\n\n const removeParticipant = React.useCallback((userId: string) => {\n setParticipants((prev) => prev.filter((p) => p.userId !== userId))\n }, [])\n\n const toggleRecurrenceDay = React.useCallback((index: number) => {\n setRecurrenceDays((prev) => {\n const next = [...prev]\n next[index] = !next[index]\n return next\n })\n }, [])\n\n return {\n activityType,\n setActivityType,\n title,\n setTitle,\n date,\n setDate,\n startTime,\n setStartTime,\n duration,\n setDuration,\n allDay,\n setAllDay,\n description,\n setDescription,\n markdownEnabled,\n setMarkdownEnabled,\n location,\n setLocation,\n reminderMinutes,\n setReminderMinutes,\n visibility,\n setVisibility,\n participants,\n setParticipants,\n linkedEntities,\n setLinkedEntities,\n recurrenceEnabled,\n setRecurrenceEnabled,\n recurrenceDays,\n setRecurrenceDays,\n recurrenceEndType,\n setRecurrenceEndType,\n recurrenceCount,\n setRecurrenceCount,\n recurrenceEndDate,\n setRecurrenceEndDate,\n conflict,\n setConflict,\n saving,\n setSaving,\n guestPermissions,\n setGuestPermissions,\n removeParticipant,\n toggleRecurrenceDay,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react'\nimport type { ActivityType } from './fieldConfig'\n\nexport type RsvpStatus = 'pending' | 'accepted' | 'declined' | 'tentative'\n\nexport type Participant = {\n userId: string\n name: string\n email?: string\n color?: string\n status?: RsvpStatus\n}\n\nexport type LinkedEntity = {\n id: string\n type: 'company' | 'deal' | 'offer'\n label: string\n}\n\nexport type ScheduleActivityEditData = {\n id: string\n interactionType?: string\n title?: string | null\n body?: string | null\n scheduledAt?: string | null\n /**\n * Historical timestamp for completed activities (status `done`). Required for\n * the edit prefill to restore the original date/time instead of falling back\n * to \"today\" (#1807).\n */\n occurredAt?: string | null\n durationMinutes?: number | null\n location?: string | null\n allDay?: boolean | null\n recurrenceRule?: string | null\n recurrenceEnd?: string | null\n participants?: Array<{ userId: string; name?: string; email?: string; status?: string }> | null\n reminderMinutes?: number | null\n visibility?: string | null\n linkedEntities?: Array<{ id: string; type: string; label: string }> | null\n guestPermissions?: { canInviteOthers?: boolean; canModify?: boolean; canSeeList?: boolean } | null\n}\n\nexport const PARTICIPANT_COLORS = [\n 'bg-chart-emerald',\n 'bg-chart-blue',\n 'bg-chart-orange',\n 'bg-chart-violet',\n 'bg-chart-pink',\n 'bg-chart-teal',\n]\n\n// Per-Figma defaults for the Reminder dropdown when the user picks an activity\n// type. Meeting/email keep the standard 15 min; tasks default to 1 day (1440 min)\n// because they're plan-ahead artefacts; calls default to 5 min as a stand-in for\n// the Figma \"After call ends\" treatment (which would need a non-numeric sentinel\n// in the API contract \u2014 tracked as a follow-up).\nconst DEFAULT_REMINDER_MINUTES: Record<ActivityType, number> = {\n meeting: 15,\n call: 5,\n task: 1440,\n email: 15,\n note: 15,\n}\n\nfunction padDatePart(value: number): string {\n return String(value).padStart(2, '0')\n}\n\nfunction formatLocalDateInput(date: Date): string {\n return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`\n}\n\nfunction formatLocalTimeInput(date: Date): string {\n return `${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`\n}\n\ninterface UseScheduleFormStateParams {\n open: boolean\n editData: ScheduleActivityEditData | null | undefined\n}\n\nexport function useScheduleFormState({ open, editData }: UseScheduleFormStateParams) {\n const [activityType, setActivityType] = React.useState<ActivityType>('meeting')\n const [title, setTitle] = React.useState('')\n const [date, setDate] = React.useState(() => formatLocalDateInput(new Date()))\n const [startTime, setStartTime] = React.useState('10:00')\n const [duration, setDuration] = React.useState(30)\n const [allDay, setAllDay] = React.useState(false)\n const [description, setDescription] = React.useState('')\n const [markdownEnabled, setMarkdownEnabled] = React.useState(true)\n const [location, setLocation] = React.useState('')\n const [reminderMinutes, setReminderMinutes] = React.useState(15)\n const [visibility, setVisibility] = React.useState('team')\n const [participants, setParticipants] = React.useState<Participant[]>([])\n const [linkedEntities, setLinkedEntities] = React.useState<LinkedEntity[]>([])\n const [recurrenceEnabled, setRecurrenceEnabled] = React.useState(false)\n const [recurrenceDays, setRecurrenceDays] = React.useState<boolean[]>([true, false, true, false, false, false, false])\n const [recurrenceEndType, setRecurrenceEndType] = React.useState<'never' | 'count' | 'date'>('never')\n const [recurrenceCount, setRecurrenceCount] = React.useState(8)\n const [recurrenceEndDate, setRecurrenceEndDate] = React.useState('')\n const [conflict, setConflict] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [guestPermissions, setGuestPermissions] = React.useState({ canInviteOthers: true, canModify: false, canSeeList: true })\n\n React.useEffect(() => {\n if (open) {\n if (editData) {\n // Edit mode: populate from existing interaction\n const resolvedType = (editData.interactionType as ActivityType) ?? 'meeting'\n setActivityType(resolvedType)\n setTitle(editData.title ?? '')\n // For historical activities the canonical timestamp is `occurredAt`; for\n // planned/future ones it's `scheduledAt`. Without this fallback editing a\n // past activity prefilled to \"today\" instead of its actual moment (#1807).\n // Keep seed values in the user's local timezone, matching the cluster-E\n // local-day convention.\n const sourceTimestamp = editData.occurredAt ?? editData.scheduledAt ?? null\n const seedDate = sourceTimestamp ? new Date(sourceTimestamp) : new Date()\n const seedDateValid = !Number.isNaN(seedDate.getTime())\n const fallbackNow = new Date()\n const dateForForm = seedDateValid ? seedDate : fallbackNow\n setDate(formatLocalDateInput(dateForForm))\n setStartTime(formatLocalTimeInput(dateForForm))\n setDuration(editData.durationMinutes ?? 30)\n setAllDay(editData.allDay ?? false)\n setDescription(editData.body ?? '')\n setLocation(editData.location ?? '')\n // Use per-type default when the editData omits an explicit reminder\n // (the menu-driven \"New X\" flow opens the dialog with `reminderMinutes: null`).\n setReminderMinutes(editData.reminderMinutes ?? DEFAULT_REMINDER_MINUTES[resolvedType])\n setVisibility(editData.visibility ?? 'team')\n setParticipants(\n Array.isArray(editData.participants)\n ? editData.participants.map((p, i) => ({\n userId: p.userId,\n name: p.name ?? p.userId,\n email: p.email,\n color: PARTICIPANT_COLORS[i % PARTICIPANT_COLORS.length],\n status: (p.status ?? 'pending') as RsvpStatus,\n }))\n : [],\n )\n setLinkedEntities(\n Array.isArray(editData.linkedEntities)\n ? editData.linkedEntities.map((e) => ({ id: e.id, type: e.type as LinkedEntity['type'], label: e.label }))\n : [],\n )\n if (editData.recurrenceRule) {\n setRecurrenceEnabled(true)\n // Parse RRULE to set days and end type\n const rule = editData.recurrenceRule\n const byDayMatch = rule.match(/BYDAY=([A-Z,]+)/)\n if (byDayMatch) {\n const dayNames = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']\n const activeDays = byDayMatch[1].split(',')\n setRecurrenceDays(dayNames.map((d) => activeDays.includes(d)))\n }\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const untilMatch = rule.match(/UNTIL=(\\d{8})/)\n if (countMatch) {\n setRecurrenceEndType('count')\n setRecurrenceCount(Number(countMatch[1]))\n } else if (untilMatch) {\n setRecurrenceEndType('date')\n const raw = untilMatch[1]\n setRecurrenceEndDate(`${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`)\n } else {\n setRecurrenceEndType('never')\n }\n } else {\n setRecurrenceEnabled(false)\n }\n if (editData.guestPermissions) {\n setGuestPermissions({\n canInviteOthers: editData.guestPermissions.canInviteOthers ?? true,\n canModify: editData.guestPermissions.canModify ?? false,\n canSeeList: editData.guestPermissions.canSeeList ?? true,\n })\n }\n } else {\n // Create mode: reset all fields\n setActivityType('meeting')\n setTitle('')\n setDate(formatLocalDateInput(new Date()))\n setStartTime('10:00')\n setDuration(30)\n setAllDay(false)\n setDescription('')\n setLocation('')\n setReminderMinutes(DEFAULT_REMINDER_MINUTES.meeting)\n setVisibility('team')\n setParticipants([])\n setLinkedEntities([])\n setRecurrenceEnabled(false)\n }\n setConflict(null)\n }\n return () => {\n // Safety net: restore body scroll if Radix Dialog fails to clean up\n document.body.style.removeProperty('overflow')\n document.body.style.removeProperty('pointer-events')\n }\n }, [open, editData])\n\n // Update the Reminder default when the activity type changes in create mode.\n // Skipped in edit mode (the persisted value wins), and gated by `open` to\n // avoid flipping the default in a closed-but-mounted dialog.\n const lastReminderTypeRef = React.useRef<ActivityType>('meeting')\n React.useEffect(() => {\n if (!open || editData) {\n lastReminderTypeRef.current = activityType\n return\n }\n if (lastReminderTypeRef.current === activityType) return\n lastReminderTypeRef.current = activityType\n setReminderMinutes(DEFAULT_REMINDER_MINUTES[activityType])\n }, [activityType, editData, open])\n\n const removeParticipant = React.useCallback((userId: string) => {\n setParticipants((prev) => prev.filter((p) => p.userId !== userId))\n }, [])\n\n const toggleRecurrenceDay = React.useCallback((index: number) => {\n setRecurrenceDays((prev) => {\n const next = [...prev]\n next[index] = !next[index]\n return next\n })\n }, [])\n\n return {\n activityType,\n setActivityType,\n title,\n setTitle,\n date,\n setDate,\n startTime,\n setStartTime,\n duration,\n setDuration,\n allDay,\n setAllDay,\n description,\n setDescription,\n markdownEnabled,\n setMarkdownEnabled,\n location,\n setLocation,\n reminderMinutes,\n setReminderMinutes,\n visibility,\n setVisibility,\n participants,\n setParticipants,\n linkedEntities,\n setLinkedEntities,\n recurrenceEnabled,\n setRecurrenceEnabled,\n recurrenceDays,\n setRecurrenceDays,\n recurrenceEndType,\n setRecurrenceEndType,\n recurrenceCount,\n setRecurrenceCount,\n recurrenceEndDate,\n setRecurrenceEndDate,\n conflict,\n setConflict,\n saving,\n setSaving,\n guestPermissions,\n setGuestPermissions,\n removeParticipant,\n toggleRecurrenceDay,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  const features = [
2
2
  { id: "feature_toggles.view", title: "View feature toggles", module: "feature_toggles" },
3
- { id: "feature_toggles.manage", title: "Manage feature toggles", module: "feature_toggles" }
3
+ { id: "feature_toggles.manage", title: "Manage per-tenant feature toggle overrides", module: "feature_toggles" },
4
+ { id: "feature_toggles.global.manage", title: "Manage system-wide global feature toggles", module: "feature_toggles" }
4
5
  ];
5
6
  var acl_default = features;
6
7
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/feature_toggles/acl.ts"],
4
- "sourcesContent": ["export const features = [\n { id: 'feature_toggles.view', title: 'View feature toggles', module: 'feature_toggles' },\n { id: 'feature_toggles.manage', title: 'Manage feature toggles', module: 'feature_toggles' },\n]\n\nexport default features\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,wBAAwB,OAAO,wBAAwB,QAAQ,kBAAkB;AAAA,EACvF,EAAE,IAAI,0BAA0B,OAAO,0BAA0B,QAAQ,kBAAkB;AAC7F;AAEA,IAAO,cAAQ;",
4
+ "sourcesContent": ["export const features = [\n { id: 'feature_toggles.view', title: 'View feature toggles', module: 'feature_toggles' },\n { id: 'feature_toggles.manage', title: 'Manage per-tenant feature toggle overrides', module: 'feature_toggles' },\n { id: 'feature_toggles.global.manage', title: 'Manage system-wide global feature toggles', module: 'feature_toggles' },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,wBAAwB,OAAO,wBAAwB,QAAQ,kBAAkB;AAAA,EACvF,EAAE,IAAI,0BAA0B,OAAO,8CAA8C,QAAQ,kBAAkB;AAAA,EAC/G,EAAE,IAAI,iCAAiC,OAAO,6CAA6C,QAAQ,kBAAkB;AACvH;AAEA,IAAO,cAAQ;",
6
6
  "names": []
7
7
  }
@@ -23,9 +23,11 @@ const listQuerySchema = z.object({
23
23
  }).passthrough();
24
24
  const routeMetadata = {
25
25
  GET: { requireAuth: true, requireFeatures: ["feature_toggles.view"] },
26
- POST: { requireAuth: true, requireFeatures: ["feature_toggles.manage"] },
27
- PUT: { requireAuth: true, requireFeatures: ["feature_toggles.manage"] },
28
- DELETE: { requireAuth: true, requireFeatures: ["feature_toggles.manage"] }
26
+ // Global feature toggles are platform-wide (no tenant_id); writing them is
27
+ // restricted to super administrators via the dedicated global feature.
28
+ POST: { requireAuth: true, requireFeatures: ["feature_toggles.global.manage"] },
29
+ PUT: { requireAuth: true, requireFeatures: ["feature_toggles.global.manage"] },
30
+ DELETE: { requireAuth: true, requireFeatures: ["feature_toggles.global.manage"] }
29
31
  };
30
32
  const listFields = [
31
33
  "id",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/feature_toggles/api/global/route.ts"],
4
- "sourcesContent": ["import { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { FeatureToggle } from '../../data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { toggleTypeSchema, toggleCreateSchema, toggleUpdateSchema } from '../../data/validators'\nimport {\n featureTogglesTag,\n featureToggleListResponseSchema,\n featureToggleErrorSchema\n} from '../openapi'\n\nconst rawBodySchema = z.object({}).passthrough()\nconst listQuerySchema = z\n .object({\n page: z.coerce.number().min(1).default(1).describe('Page number for pagination'),\n pageSize: z.coerce.number().min(1).max(200).default(50).describe('Number of items per page (max 200)'),\n search: z.string().optional().describe('Case-insensitive search across identifier, name, description, and category'),\n type: toggleTypeSchema.optional().describe('Filter by toggle type (boolean, string, number, json)'),\n category: z.string().optional().describe('Filter by category (case-insensitive partial match)'),\n name: z.string().optional().describe('Filter by name (case-insensitive partial match)'),\n identifier: z.string().optional().describe('Filter by identifier (case-insensitive partial match)'),\n sortField: z.enum(['id', 'category', 'identifier', 'name', 'createdAt', 'updatedAt', 'type']).optional().describe('Field to sort by'),\n sortDir: z.enum(['asc', 'desc']).optional().describe('Sort direction (ascending or descending)'),\n })\n .passthrough()\n\ntype FeatureToggleListQuery = z.infer<typeof listQuerySchema>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['feature_toggles.view'] },\n POST: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },\n}\n\nconst listFields = [\n 'id',\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'type',\n 'default_value',\n 'created_at',\n 'updated_at',\n]\n\nconst buildFilters = (query: FeatureToggleListQuery): Record<string, unknown> => {\n const filters: Record<string, unknown> = {}\n const search = query.search?.trim()\n if (search && search.length > 0) {\n const escaped = escapeLikePattern(search)\n const pattern = `%${escaped}%`\n filters.$or = [\n { identifier: { $ilike: pattern } },\n { name: { $ilike: pattern } },\n { description: { $ilike: pattern } },\n { category: { $ilike: pattern } },\n ]\n }\n const category = query.category?.trim()\n if (category && category.length > 0) {\n filters.category = { $ilike: `%${escapeLikePattern(category)}%` }\n }\n const name = query.name?.trim()\n if (name && name.length > 0) {\n filters.name = { $ilike: `%${escapeLikePattern(name)}%` }\n }\n const identifier = query.identifier?.trim()\n if (identifier && identifier.length > 0) {\n filters.identifier = { $ilike: `%${escapeLikePattern(identifier)}%` }\n }\n const type = query.type?.trim()\n if (type && type.length > 0) {\n filters.type = { $eq: query.type }\n }\n return filters\n}\n\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: FeatureToggle,\n idField: 'id',\n orgField: null,\n tenantField: \"tenantId\",\n softDeleteField: 'deletedAt'\n },\n indexer: { entityType: E.feature_toggles.feature_toggle },\n list: {\n schema: listQuerySchema,\n entityId: E.feature_toggles.feature_toggle,\n fields: listFields,\n sortFieldMap: {\n id: 'id',\n category: 'category',\n identifier: 'identifier',\n name: 'name',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n type: 'type',\n },\n transformItem: (item: Record<string, unknown>) => {\n if (!item) return item\n return {\n id: item.id,\n identifier: item.identifier,\n name: item.name,\n description: item.description ?? null,\n category: item.category ?? null,\n type: item.type,\n defaultValue: item.default_value,\n created_at: item.created_at,\n updated_at: item.updated_at,\n }\n },\n buildFilters: async (query) => buildFilters(query),\n },\n actions: {\n create: {\n commandId: 'feature_toggles.global.create',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 201,\n },\n update: {\n commandId: 'feature_toggles.global.update',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 200,\n },\n delete: {\n commandId: 'feature_toggles.global.delete',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 200,\n },\n }\n})\n\nexport const metadata = crud.metadata\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst createResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst updateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst deleteResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: featureTogglesTag,\n summary: 'Global feature toggle management',\n methods: {\n GET: {\n summary: 'List global feature toggles',\n description: 'Returns all global feature toggles with filtering and pagination. Requires superadmin role.',\n query: listQuerySchema,\n responses: [\n { status: 200, description: 'Feature toggles collection', schema: featureToggleListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n ],\n },\n POST: {\n summary: 'Create global feature toggle',\n description: 'Creates a new global feature toggle. Requires superadmin role.',\n requestBody: {\n contentType: 'application/json',\n schema: toggleCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Feature toggle created',\n schema: createResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update global feature toggle',\n description: 'Updates an existing global feature toggle. Requires superadmin role.',\n requestBody: {\n contentType: 'application/json',\n schema: toggleUpdateSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Feature toggle updated',\n schema: updateResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Feature toggle not found', schema: featureToggleErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete global feature toggle',\n description: 'Soft deletes a global feature toggle by ID. Requires superadmin role.',\n query: z.object({ id: z.string().uuid().describe('Feature toggle identifier') }),\n responses: [\n { status: 200, description: 'Feature toggle deleted', schema: deleteResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifier', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Feature toggle not found', schema: featureToggleErrorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { FeatureToggle } from '../../data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { toggleTypeSchema, toggleCreateSchema, toggleUpdateSchema } from '../../data/validators'\nimport {\n featureTogglesTag,\n featureToggleListResponseSchema,\n featureToggleErrorSchema\n} from '../openapi'\n\nconst rawBodySchema = z.object({}).passthrough()\nconst listQuerySchema = z\n .object({\n page: z.coerce.number().min(1).default(1).describe('Page number for pagination'),\n pageSize: z.coerce.number().min(1).max(200).default(50).describe('Number of items per page (max 200)'),\n search: z.string().optional().describe('Case-insensitive search across identifier, name, description, and category'),\n type: toggleTypeSchema.optional().describe('Filter by toggle type (boolean, string, number, json)'),\n category: z.string().optional().describe('Filter by category (case-insensitive partial match)'),\n name: z.string().optional().describe('Filter by name (case-insensitive partial match)'),\n identifier: z.string().optional().describe('Filter by identifier (case-insensitive partial match)'),\n sortField: z.enum(['id', 'category', 'identifier', 'name', 'createdAt', 'updatedAt', 'type']).optional().describe('Field to sort by'),\n sortDir: z.enum(['asc', 'desc']).optional().describe('Sort direction (ascending or descending)'),\n })\n .passthrough()\n\ntype FeatureToggleListQuery = z.infer<typeof listQuerySchema>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['feature_toggles.view'] },\n // Global feature toggles are platform-wide (no tenant_id); writing them is\n // restricted to super administrators via the dedicated global feature.\n POST: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },\n}\n\nconst listFields = [\n 'id',\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'type',\n 'default_value',\n 'created_at',\n 'updated_at',\n]\n\nconst buildFilters = (query: FeatureToggleListQuery): Record<string, unknown> => {\n const filters: Record<string, unknown> = {}\n const search = query.search?.trim()\n if (search && search.length > 0) {\n const escaped = escapeLikePattern(search)\n const pattern = `%${escaped}%`\n filters.$or = [\n { identifier: { $ilike: pattern } },\n { name: { $ilike: pattern } },\n { description: { $ilike: pattern } },\n { category: { $ilike: pattern } },\n ]\n }\n const category = query.category?.trim()\n if (category && category.length > 0) {\n filters.category = { $ilike: `%${escapeLikePattern(category)}%` }\n }\n const name = query.name?.trim()\n if (name && name.length > 0) {\n filters.name = { $ilike: `%${escapeLikePattern(name)}%` }\n }\n const identifier = query.identifier?.trim()\n if (identifier && identifier.length > 0) {\n filters.identifier = { $ilike: `%${escapeLikePattern(identifier)}%` }\n }\n const type = query.type?.trim()\n if (type && type.length > 0) {\n filters.type = { $eq: query.type }\n }\n return filters\n}\n\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: FeatureToggle,\n idField: 'id',\n orgField: null,\n tenantField: \"tenantId\",\n softDeleteField: 'deletedAt'\n },\n indexer: { entityType: E.feature_toggles.feature_toggle },\n list: {\n schema: listQuerySchema,\n entityId: E.feature_toggles.feature_toggle,\n fields: listFields,\n sortFieldMap: {\n id: 'id',\n category: 'category',\n identifier: 'identifier',\n name: 'name',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n type: 'type',\n },\n transformItem: (item: Record<string, unknown>) => {\n if (!item) return item\n return {\n id: item.id,\n identifier: item.identifier,\n name: item.name,\n description: item.description ?? null,\n category: item.category ?? null,\n type: item.type,\n defaultValue: item.default_value,\n created_at: item.created_at,\n updated_at: item.updated_at,\n }\n },\n buildFilters: async (query) => buildFilters(query),\n },\n actions: {\n create: {\n commandId: 'feature_toggles.global.create',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 201,\n },\n update: {\n commandId: 'feature_toggles.global.update',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 200,\n },\n delete: {\n commandId: 'feature_toggles.global.delete',\n schema: rawBodySchema,\n response: ({ result }) => ({ id: result?.toggleId ?? result?.id ?? null }),\n status: 200,\n },\n }\n})\n\nexport const metadata = crud.metadata\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst createResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst updateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst deleteResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: featureTogglesTag,\n summary: 'Global feature toggle management',\n methods: {\n GET: {\n summary: 'List global feature toggles',\n description: 'Returns all global feature toggles with filtering and pagination. Requires superadmin role.',\n query: listQuerySchema,\n responses: [\n { status: 200, description: 'Feature toggles collection', schema: featureToggleListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n ],\n },\n POST: {\n summary: 'Create global feature toggle',\n description: 'Creates a new global feature toggle. Requires superadmin role.',\n requestBody: {\n contentType: 'application/json',\n schema: toggleCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Feature toggle created',\n schema: createResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update global feature toggle',\n description: 'Updates an existing global feature toggle. Requires superadmin role.',\n requestBody: {\n contentType: 'application/json',\n schema: toggleUpdateSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Feature toggle updated',\n schema: updateResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Feature toggle not found', schema: featureToggleErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete global feature toggle',\n description: 'Soft deletes a global feature toggle by ID. Requires superadmin role.',\n query: z.object({ id: z.string().uuid().describe('Feature toggle identifier') }),\n responses: [\n { status: 200, description: 'Feature toggle deleted', schema: deleteResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifier', schema: featureToggleErrorSchema },\n { status: 401, description: 'Unauthorized', schema: featureToggleErrorSchema },\n { status: 403, description: 'Forbidden - superadmin role required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Feature toggle not found', schema: featureToggleErrorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -81,7 +81,13 @@ function buildCommandContext(container) {
81
81
  organizationScope: null,
82
82
  selectedOrganizationId: null,
83
83
  organizationIds: null,
84
- request: void 0
84
+ request: void 0,
85
+ // CLI invocations (seed-defaults, toggle-create/update/delete) are trusted
86
+ // server-side calls with no end-user actor. Global feature toggles are a
87
+ // platform-wide table whose writes are restricted to super admins (#2266);
88
+ // this flag lets the command-level guard recognize the system caller so
89
+ // `yarn initialize` can seed defaults without an authenticated super admin.
90
+ systemActor: true
85
91
  };
86
92
  }
87
93
  const createToggle = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/feature_toggles/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { FeatureToggle } from './data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { toggleCreateSchemaList } from './data/validators'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\ntype ParsedArgs = Record<string, string | boolean>\n\n// ESM equivalent of __dirname\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\nconst defaultFilePath = path.resolve(__dirname, 'defaults.json')\n\nfunction parseArgs(rest: string[]): ParsedArgs {\n const args: ParsedArgs = {}\n for (let index = 0; index < rest.length; index += 1) {\n const part = rest[index]\n if (!part?.startsWith('--')) continue\n const [rawKey, rawValue] = part.slice(2).split('=')\n if (!rawKey) continue\n if (rawValue !== undefined) {\n args[rawKey] = rawValue\n continue\n }\n const next = rest[index + 1]\n if (next && !next.startsWith('--')) {\n args[rawKey] = next\n index += 1\n continue\n }\n args[rawKey] = true\n }\n return args\n}\n\nfunction stringOption(args: ParsedArgs, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (typeof raw !== 'string') continue\n const trimmed = raw.trim()\n if (trimmed.length > 0) return trimmed\n }\n return undefined\n}\n\nfunction booleanOption(args: ParsedArgs, ...keys: string[]): boolean | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (raw === undefined) continue\n if (raw === true) return true\n if (raw === false) return false\n if (typeof raw === 'string') {\n const trimmed = raw.trim()\n if (!trimmed) return true\n const parsed = parseBooleanToken(trimmed)\n if (parsed !== null) return parsed\n }\n }\n return undefined\n}\n\nfunction parseValue(type: string, value: string | undefined): any {\n if (value === undefined) return undefined\n\n switch (type) {\n case 'boolean':\n if (value === 'true' || value === '1') return true\n if (value === 'false' || value === '0') return false\n return Boolean(value)\n case 'number':\n const num = Number(value)\n if (isNaN(num)) throw new Error(`Invalid number value: ${value}`)\n return num\n case 'json':\n try {\n return JSON.parse(value)\n } catch (e) {\n throw new Error(`Invalid JSON value: ${value}`)\n }\n case 'string':\n default:\n return value\n }\n}\n\nfunction buildCommandContext(container: Awaited<ReturnType<typeof createRequestContainer>>): CommandRuntimeContext {\n return {\n container,\n auth: null,\n organizationScope: null,\n selectedOrganizationId: null,\n organizationIds: null,\n request: undefined as any,\n }\n}\n\nconst createToggle: ModuleCli = {\n command: 'toggle-create',\n async run(rest) {\n const args = parseArgs(rest)\n const identifier = stringOption(args, 'identifier', 'id')\n const name = stringOption(args, 'name')\n\n if (!identifier || !name) {\n console.error('Usage: mercato feature_toggles toggle-create --identifier <id> --name <name> [--type boolean|string|number|json] [--defaultValue <value>] [--category <value>] [--description <value>]')\n return\n }\n\n const type = stringOption(args, 'type') || 'boolean'\n const defaultValueRaw = stringOption(args, 'defaultValue')\n const category = stringOption(args, 'category')\n const description = stringOption(args, 'description')\n\n\n let defaultValue: any = undefined\n try {\n defaultValue = parseValue(type, defaultValueRaw)\n } catch (e: any) {\n console.error(e.message)\n return\n }\n\n const container = await createRequestContainer()\n try {\n const commandBus = container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(container)\n const { result } = await commandBus.execute('feature_toggles.global.create', {\n input: {\n identifier,\n name,\n type,\n defaultValue: defaultValue,\n category: category ?? null,\n description: description ?? null,\n\n },\n ctx,\n })\n console.log('\u2705 Feature toggle created:', \"Identifier: \" + identifier)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst updateToggle: ModuleCli = {\n command: 'toggle-update',\n async run(rest) {\n const args = parseArgs(rest)\n const identifier = stringOption(args, 'identifier')\n\n if (!identifier) {\n console.error('Usage: mercato feature_toggles toggle-update --identifier <id> [--name <name>] [--defaultValue <value>] [--category <value>] [--description <value>]')\n return\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const toggle = await em.findOne(FeatureToggle, { identifier })\n if (!toggle) {\n console.error('Feature toggle not found:', identifier)\n return\n }\n\n const name = stringOption(args, 'name')\n const defaultValueRaw = stringOption(args, 'defaultValue')\n const category = stringOption(args, 'category')\n const description = stringOption(args, 'description')\n\n\n let defaultValue: any = undefined\n if (defaultValueRaw !== undefined) {\n try {\n defaultValue = parseValue(toggle.type, defaultValueRaw)\n } catch (e: any) {\n console.error(e.message)\n return\n }\n }\n\n const toggleId = toggle.id\n try {\n const commandBus = container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(container)\n await commandBus.execute('feature_toggles.global.update', {\n input: {\n ...(toggleId ? { id: toggleId } : {}),\n ...(identifier ? { identifier } : {}),\n ...(name ? { name } : {}),\n ...(defaultValue !== undefined ? { defaultValue } : {}),\n ...(category !== undefined ? { category } : {}),\n ...(description !== undefined ? { description } : {}),\n\n },\n ctx,\n })\n console.log('\u2705 Feature toggle updated:', \"Identifier: \" + identifier)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst deleteToggle: ModuleCli = {\n command: 'toggle-delete',\n async run(rest) {\n const args = parseArgs(rest)\n const identifier = stringOption(args, 'identifier')\n if (!identifier) {\n console.error('Usage: mercato feature_toggles toggle-delete --identifier <id>')\n return\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const toggle = await em.findOne(FeatureToggle, { identifier })\n if (!toggle) {\n console.error('Feature toggle not found:', identifier)\n return\n }\n const toggleId = toggle.id\n try {\n const commandBus = container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(container)\n await commandBus.execute('feature_toggles.global.delete', {\n input: {\n ...(toggleId ? { id: toggleId } : {}),\n ...(identifier ? { identifier } : {}),\n },\n ctx,\n })\n console.log('\u2705 Feature toggle deleted:', identifier ?? toggleId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst setOverrideValue: ModuleCli = {\n command: 'override-set-value',\n async run(rest) {\n const args = parseArgs(rest)\n const identifier = stringOption(args, 'identifier')\n const tenantId = stringOption(args, 'tenantId', 'tenant', 'tenantId')\n const valueRaw = stringOption(args, 'value')\n\n if (!identifier || !tenantId) {\n console.error('Usage: mercato feature_toggles override-set-value --identifier <id> --tenantId <uuid> --value <value>')\n return\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const toggle = await em.findOne(FeatureToggle, { identifier })\n if (!toggle) {\n console.error('Feature toggle not found:', identifier)\n return\n }\n\n let value: any = undefined\n if (valueRaw !== undefined) {\n try {\n value = parseValue(toggle.type, valueRaw)\n } catch (e: any) {\n console.error(e.message)\n return\n }\n }\n\n const toggleId = toggle.id\n\n try {\n const commandBus = container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(container)\n await commandBus.execute('feature_toggles.overrides.changeState', {\n input: {\n toggleId,\n tenantId,\n isOverride: true,\n overrideValue: value,\n },\n ctx,\n })\n console.log('\u2705 Feature toggle override updated:', identifier, \"Tenant ID: \" + tenantId, \"Value: \" + (valueRaw || 'unchanged'))\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedDefaults: ModuleCli = {\n command: 'seed-defaults',\n async run(rest) {\n const args = parseArgs(rest)\n const filePathFromArgs = stringOption(args, 'filePath')\n const filePath = filePathFromArgs ?? defaultFilePath\n const raw = fs.readFileSync(filePath, 'utf8')\n const parsedJson = JSON.parse(raw)\n const data = Array.isArray(parsedJson) ? parsedJson : parsedJson.toggles\n\n // We can't strictly use toggleCreateSchemaList because it might validate types strictly, \n // but the input JSON from file might have raw values that need refinement if we were going via CLI args.\n // However, since we are reading from JSON, we can assume the types match what zod expects for the literal types.\n // Let's rely on the schema to validate the structure.\n const toggles = toggleCreateSchemaList.parse(data)\n\n const container = await createRequestContainer()\n const commandBus = container.resolve('commandBus') as CommandBus\n const em = container.resolve('em') as EntityManager\n const ctx = buildCommandContext(container)\n let created = 0\n let skipped = 0\n\n for (const toggle of toggles) {\n const existing = await em.findOne(FeatureToggle, { identifier: toggle.identifier, deletedAt: null })\n if (existing) {\n skipped += 1\n continue\n }\n await commandBus.execute('feature_toggles.global.create', {\n input: {\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description ?? null,\n category: toggle.category ?? null,\n\n type: toggle.type,\n defaultValue: toggle.defaultValue,\n },\n ctx,\n })\n created += 1\n console.log(`\u2705 Created feature toggle ${toggle.identifier}`)\n }\n console.log(`\u2705 Feature toggle defaults seeded (created: ${created}, skipped: ${skipped})`)\n },\n}\n\nexport default [createToggle, updateToggle, deleteToggle, setOverrideValue, seedDefaults]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { FeatureToggle } from './data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { toggleCreateSchemaList } from './data/validators'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\ntype ParsedArgs = Record<string, string | boolean>\n\n// ESM equivalent of __dirname\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\nconst defaultFilePath = path.resolve(__dirname, 'defaults.json')\n\nfunction parseArgs(rest: string[]): ParsedArgs {\n const args: ParsedArgs = {}\n for (let index = 0; index < rest.length; index += 1) {\n const part = rest[index]\n if (!part?.startsWith('--')) continue\n const [rawKey, rawValue] = part.slice(2).split('=')\n if (!rawKey) continue\n if (rawValue !== undefined) {\n args[rawKey] = rawValue\n continue\n }\n const next = rest[index + 1]\n if (next && !next.startsWith('--')) {\n args[rawKey] = next\n index += 1\n continue\n }\n args[rawKey] = true\n }\n return args\n}\n\nfunction stringOption(args: ParsedArgs, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (typeof raw !== 'string') continue\n const trimmed = raw.trim()\n if (trimmed.length > 0) return trimmed\n }\n return undefined\n}\n\nfunction booleanOption(args: ParsedArgs, ...keys: string[]): boolean | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (raw === undefined) continue\n if (raw === true) return true\n if (raw === false) return false\n if (typeof raw === 'string') {\n const trimmed = raw.trim()\n if (!trimmed) return true\n const parsed = parseBooleanToken(trimmed)\n if (parsed !== null) return parsed\n }\n }\n return undefined\n}\n\nfunction parseValue(type: string, value: string | undefined): any {\n if (value === undefined) return undefined\n\n switch (type) {\n case 'boolean':\n if (value === 'true' || value === '1') return true\n if (value === 'false' || value === '0') return false\n return Boolean(value)\n case 'number':\n const num = Number(value)\n if (isNaN(num)) throw new Error(`Invalid number value: ${value}`)\n return num\n case 'json':\n try {\n return JSON.parse(value)\n } catch (e) {\n throw new Error(`Invalid JSON value: ${value}`)\n }\n case 'string':\n default:\n return value\n }\n}\n\nfunction buildCommandContext(container: Awaited<ReturnType<typeof createRequestContainer>>): CommandRuntimeContext {\n return {\n container,\n auth: null,\n organizationScope: null,\n selectedOrganizationId: null,\n organizationIds: null,\n request: undefined as any,\n // CLI invocations (seed-defaults, toggle-create/update/delete) are trusted\n // server-side calls with no end-user actor. Global feature toggles are a\n // platform-wide table whose writes are restricted to super admins (#2266);\n // this flag lets the command-level guard recognize the system caller so\n // `yarn initialize` can seed defaults without an authenticated super admin.\n systemActor: true,\n }\n}\n\nconst createToggle: ModuleCli = {\n command: 'toggle-create',\n async run(rest) {\n const args = parseArgs(rest)\n const identifier = stringOption(args, 'identifier', 'id')\n const name = stringOption(args, 'name')\n\n if (!identifier || !name) {\n console.error('Usage: mercato feature_toggles toggle-create --identifier <id> --name <name> [--type boolean|string|number|json] [--defaultValue <value>] [--category <value>] [--description <value>]')\n return\n }\n\n const type = stringOption(args, 'type') || 'boolean'\n const defaultValueRaw = stringOption(args, 'defaultValue')\n const category = stringOption(args, 'category')\n const description = stringOption(args, 'description')\n\n\n let defaultValue: any = undefined\n try {\n defaultValue = parseValue(type, defaultValueRaw)\n } catch (e: any) {\n console.error(e.message)\n return\n }\n\n const container = await createRequestContainer()\n try {\n const commandBus = container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(container)\n const { result } = await commandBus.execute('feature_toggles.global.create', {\n input: {\n identifier,\n name,\n type,\n defaultValue: defaultValue,\n category: category ?? null,\n description: description ?? null,\n\n },\n ctx,\n })\n console.log('\u2705 Feature toggle created:', \"Identifier: \" + identifier)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst updateToggle: ModuleCli = {\n command: 'toggle-update',\n async run(rest) {\n const args = parseArgs(rest)\n const identifier = stringOption(args, 'identifier')\n\n if (!identifier) {\n console.error('Usage: mercato feature_toggles toggle-update --identifier <id> [--name <name>] [--defaultValue <value>] [--category <value>] [--description <value>]')\n return\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const toggle = await em.findOne(FeatureToggle, { identifier })\n if (!toggle) {\n console.error('Feature toggle not found:', identifier)\n return\n }\n\n const name = stringOption(args, 'name')\n const defaultValueRaw = stringOption(args, 'defaultValue')\n const category = stringOption(args, 'category')\n const description = stringOption(args, 'description')\n\n\n let defaultValue: any = undefined\n if (defaultValueRaw !== undefined) {\n try {\n defaultValue = parseValue(toggle.type, defaultValueRaw)\n } catch (e: any) {\n console.error(e.message)\n return\n }\n }\n\n const toggleId = toggle.id\n try {\n const commandBus = container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(container)\n await commandBus.execute('feature_toggles.global.update', {\n input: {\n ...(toggleId ? { id: toggleId } : {}),\n ...(identifier ? { identifier } : {}),\n ...(name ? { name } : {}),\n ...(defaultValue !== undefined ? { defaultValue } : {}),\n ...(category !== undefined ? { category } : {}),\n ...(description !== undefined ? { description } : {}),\n\n },\n ctx,\n })\n console.log('\u2705 Feature toggle updated:', \"Identifier: \" + identifier)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst deleteToggle: ModuleCli = {\n command: 'toggle-delete',\n async run(rest) {\n const args = parseArgs(rest)\n const identifier = stringOption(args, 'identifier')\n if (!identifier) {\n console.error('Usage: mercato feature_toggles toggle-delete --identifier <id>')\n return\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const toggle = await em.findOne(FeatureToggle, { identifier })\n if (!toggle) {\n console.error('Feature toggle not found:', identifier)\n return\n }\n const toggleId = toggle.id\n try {\n const commandBus = container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(container)\n await commandBus.execute('feature_toggles.global.delete', {\n input: {\n ...(toggleId ? { id: toggleId } : {}),\n ...(identifier ? { identifier } : {}),\n },\n ctx,\n })\n console.log('\u2705 Feature toggle deleted:', identifier ?? toggleId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst setOverrideValue: ModuleCli = {\n command: 'override-set-value',\n async run(rest) {\n const args = parseArgs(rest)\n const identifier = stringOption(args, 'identifier')\n const tenantId = stringOption(args, 'tenantId', 'tenant', 'tenantId')\n const valueRaw = stringOption(args, 'value')\n\n if (!identifier || !tenantId) {\n console.error('Usage: mercato feature_toggles override-set-value --identifier <id> --tenantId <uuid> --value <value>')\n return\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const toggle = await em.findOne(FeatureToggle, { identifier })\n if (!toggle) {\n console.error('Feature toggle not found:', identifier)\n return\n }\n\n let value: any = undefined\n if (valueRaw !== undefined) {\n try {\n value = parseValue(toggle.type, valueRaw)\n } catch (e: any) {\n console.error(e.message)\n return\n }\n }\n\n const toggleId = toggle.id\n\n try {\n const commandBus = container.resolve('commandBus') as CommandBus\n const ctx = buildCommandContext(container)\n await commandBus.execute('feature_toggles.overrides.changeState', {\n input: {\n toggleId,\n tenantId,\n isOverride: true,\n overrideValue: value,\n },\n ctx,\n })\n console.log('\u2705 Feature toggle override updated:', identifier, \"Tenant ID: \" + tenantId, \"Value: \" + (valueRaw || 'unchanged'))\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedDefaults: ModuleCli = {\n command: 'seed-defaults',\n async run(rest) {\n const args = parseArgs(rest)\n const filePathFromArgs = stringOption(args, 'filePath')\n const filePath = filePathFromArgs ?? defaultFilePath\n const raw = fs.readFileSync(filePath, 'utf8')\n const parsedJson = JSON.parse(raw)\n const data = Array.isArray(parsedJson) ? parsedJson : parsedJson.toggles\n\n // We can't strictly use toggleCreateSchemaList because it might validate types strictly, \n // but the input JSON from file might have raw values that need refinement if we were going via CLI args.\n // However, since we are reading from JSON, we can assume the types match what zod expects for the literal types.\n // Let's rely on the schema to validate the structure.\n const toggles = toggleCreateSchemaList.parse(data)\n\n const container = await createRequestContainer()\n const commandBus = container.resolve('commandBus') as CommandBus\n const em = container.resolve('em') as EntityManager\n const ctx = buildCommandContext(container)\n let created = 0\n let skipped = 0\n\n for (const toggle of toggles) {\n const existing = await em.findOne(FeatureToggle, { identifier: toggle.identifier, deletedAt: null })\n if (existing) {\n skipped += 1\n continue\n }\n await commandBus.execute('feature_toggles.global.create', {\n input: {\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description ?? null,\n category: toggle.category ?? null,\n\n type: toggle.type,\n defaultValue: toggle.defaultValue,\n },\n ctx,\n })\n created += 1\n console.log(`\u2705 Created feature toggle ${toggle.identifier}`)\n }\n console.log(`\u2705 Feature toggle defaults seeded (created: ${created}, skipped: ${skipped})`)\n },\n}\n\nexport default [createToggle, updateToggle, deleteToggle, setOverrideValue, seedDefaults]\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,12 @@ import { registerCommand } from "@open-mercato/shared/lib/commands";
5
5
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
6
6
  import { buildChanges, requireId } from "@open-mercato/shared/lib/commands/helpers";
7
7
  import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
8
+ function assertGlobalToggleSuperAdmin(ctx) {
9
+ if (ctx.systemActor === true) return;
10
+ if (ctx.auth?.isSuperAdmin !== true) {
11
+ throw new CrudHttpError(403, { error: "Global feature toggles can only be managed by a super administrator." });
12
+ }
13
+ }
8
14
  async function loadToggleSnapshot(em, id) {
9
15
  const toggle = await em.findOne(FeatureToggle, { id });
10
16
  if (!toggle) return null;
@@ -30,6 +36,7 @@ async function loadOverrideSnapshots(em, toggleId) {
30
36
  const createToggleCommand = {
31
37
  id: "feature_toggles.global.create",
32
38
  async execute(rawInput, ctx) {
39
+ assertGlobalToggleSuperAdmin(ctx);
33
40
  const parsed = toggleCreateSchema.parse(rawInput);
34
41
  const em = ctx.container.resolve("em").fork();
35
42
  const toggle = em.create(FeatureToggle, {
@@ -90,6 +97,7 @@ const updateToggleCommand = {
90
97
  return snapshot ? { before: snapshot } : {};
91
98
  },
92
99
  async execute(rawInput, ctx) {
100
+ assertGlobalToggleSuperAdmin(ctx);
93
101
  const parsed = toggleUpdateSchema.parse(rawInput);
94
102
  const em = ctx.container.resolve("em").fork();
95
103
  const toggle = await em.findOne(FeatureToggle, { id: parsed.id });
@@ -179,6 +187,7 @@ const deleteToggleCommand = {
179
187
  return snapshot ? { before: snapshot, overrides } : {};
180
188
  },
181
189
  async execute(input, ctx) {
190
+ assertGlobalToggleSuperAdmin(ctx);
182
191
  const id = requireId(input, "Feature toggle id required");
183
192
  const em = ctx.container.resolve("em").fork();
184
193
  const toggle = await em.findOne(FeatureToggle, { id });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/feature_toggles/commands/global.ts"],
4
- "sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { ToggleCreateInput, toggleCreateSchema, ToggleUpdateInput, toggleUpdateSchema } from '../data/validators'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { buildChanges, requireId } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { FeatureTogglesService } from '../lib/feature-flag-check'\n\ntype ToggleSnapshot = {\n id: string\n identifier: string\n name: string\n description: string | null\n category: string | null\n type: 'boolean' | 'string' | 'number' | 'json'\n defaultValue: any\n}\n\ntype OverrideSnapshot = {\n id: string\n toggleId: string\n tenantId: string\n value?: any\n}\n\ntype ToggleUndoPayload = {\n after?: ToggleSnapshot | null\n before?: ToggleSnapshot | null\n overrides?: OverrideSnapshot[]\n}\n\nasync function loadToggleSnapshot(em: EntityManager, id: string): Promise<ToggleSnapshot | null> {\n const toggle = await em.findOne(FeatureToggle, { id })\n if (!toggle) return null\n return {\n id: toggle.id,\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description ?? null,\n category: toggle.category ?? null,\n type: toggle.type ?? 'boolean',\n defaultValue: toggle.defaultValue ?? null,\n }\n}\n\nasync function loadOverrideSnapshots(em: EntityManager, toggleId: string): Promise<OverrideSnapshot[]> {\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n return overrides.map(o => ({\n id: o.id,\n toggleId: o.toggle.id,\n tenantId: o.tenantId,\n value: o.value,\n }))\n}\n\nconst createToggleCommand: CommandHandler<ToggleCreateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.create',\n async execute(rawInput, ctx) {\n const parsed = toggleCreateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = em.create(FeatureToggle, {\n identifier: parsed.identifier,\n name: parsed.name,\n description: parsed.description,\n category: parsed.category,\n type: parsed.type,\n defaultValue: parsed.defaultValue,\n })\n em.persist(toggle)\n await em.flush()\n\n return { toggleId: toggle.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadToggleSnapshot(em, result.toggleId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadToggleSnapshot(em, result.toggleId)\n return {\n actionLabel: translate('feature_toggles.audit.toggles.create', 'Create toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: result.toggleId,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const toggleId = logEntry?.resourceId ?? null\n if (!toggleId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n if (overrides.length > 0) {\n em.remove(overrides)\n }\n const toggle = await em.findOne(FeatureToggle, { id: toggleId })\n if (toggle) {\n em.remove(toggle)\n await em.flush()\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n }\n}\n\nconst updateToggleCommand: CommandHandler<ToggleUpdateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.update',\n async prepare(rawInput, ctx) {\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id: parsed.id })\n if (!toggle) throw new CrudHttpError(404, { error: 'Toggle not found' })\n toggle.identifier = parsed.identifier ?? toggle.identifier\n toggle.name = parsed.name ?? toggle.name\n toggle.description = parsed.description ?? toggle.description\n toggle.category = parsed.category ?? toggle.category\n toggle.type = parsed.type ?? toggle.type\n toggle.defaultValue = parsed.defaultValue ?? toggle.defaultValue\n await em.flush()\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ToggleSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadToggleSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as unknown as Record<string, unknown>,\n afterSnapshot as unknown as Record<string, unknown>,\n [\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'failMode',\n 'type',\n 'defaultValue',\n ]\n )\n : {}\n\n return {\n actionLabel: translate('feature_toggles.audit.toggles.update', 'Update toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n }\n await em.flush()\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n}\n\nconst deleteToggleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { toggleId: string }> =\n{\n id: 'feature_toggles.global.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, id)\n const overrides = await loadOverrideSnapshots(em, id)\n return snapshot ? { before: snapshot, overrides } : {}\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id })\n if (!toggle) throw new CrudHttpError(404, { error: 'Feature toggle not found' })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggle.id })\n if (overrides.length > 0) {\n em.remove(overrides)\n }\n\n em.remove(toggle)\n await em.flush()\n\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as ToggleSnapshot | undefined\n const overrides = (snapshots as any).overrides as OverrideSnapshot[] | undefined\n\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('feature_toggles.audit.toggles.delete', 'Delete toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n overrides,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n const overrides = payload?.overrides || []\n\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n\n for (const ov of overrides) {\n const override = em.create(FeatureToggleOverride, {\n id: ov.id,\n toggle: toggle,\n tenantId: ov.tenantId,\n value: ov.value ?? null,\n })\n em.persist(override)\n }\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n }\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n await em.flush()\n },\n}\n\nregisterCommand(createToggleCommand)\nregisterCommand(updateToggleCommand)\nregisterCommand(deleteToggleCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { ToggleCreateInput, toggleCreateSchema, ToggleUpdateInput, toggleUpdateSchema } from '../data/validators'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { buildChanges, requireId } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { FeatureTogglesService } from '../lib/feature-flag-check'\n\nfunction assertGlobalToggleSuperAdmin(ctx: { auth?: { [key: string]: unknown } | null; systemActor?: boolean }): void {\n // Trusted server-side callers (CLI seed-defaults/toggle-*, tenant setup) run\n // without an authenticated actor and opt in via `systemActor`. HTTP request\n // paths never set it and always carry a real `auth` actor, so an authenticated\n // but non-super-admin caller \u2014 the cross-tenant escalation vector (#2266) \u2014\n // stays denied.\n if (ctx.systemActor === true) return\n if (ctx.auth?.isSuperAdmin !== true) {\n throw new CrudHttpError(403, { error: 'Global feature toggles can only be managed by a super administrator.' })\n }\n}\n\ntype ToggleSnapshot = {\n id: string\n identifier: string\n name: string\n description: string | null\n category: string | null\n type: 'boolean' | 'string' | 'number' | 'json'\n defaultValue: any\n}\n\ntype OverrideSnapshot = {\n id: string\n toggleId: string\n tenantId: string\n value?: any\n}\n\ntype ToggleUndoPayload = {\n after?: ToggleSnapshot | null\n before?: ToggleSnapshot | null\n overrides?: OverrideSnapshot[]\n}\n\nasync function loadToggleSnapshot(em: EntityManager, id: string): Promise<ToggleSnapshot | null> {\n const toggle = await em.findOne(FeatureToggle, { id })\n if (!toggle) return null\n return {\n id: toggle.id,\n identifier: toggle.identifier,\n name: toggle.name,\n description: toggle.description ?? null,\n category: toggle.category ?? null,\n type: toggle.type ?? 'boolean',\n defaultValue: toggle.defaultValue ?? null,\n }\n}\n\nasync function loadOverrideSnapshots(em: EntityManager, toggleId: string): Promise<OverrideSnapshot[]> {\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n return overrides.map(o => ({\n id: o.id,\n toggleId: o.toggle.id,\n tenantId: o.tenantId,\n value: o.value,\n }))\n}\n\nconst createToggleCommand: CommandHandler<ToggleCreateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.create',\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleCreateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = em.create(FeatureToggle, {\n identifier: parsed.identifier,\n name: parsed.name,\n description: parsed.description,\n category: parsed.category,\n type: parsed.type,\n defaultValue: parsed.defaultValue,\n })\n em.persist(toggle)\n await em.flush()\n\n return { toggleId: toggle.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadToggleSnapshot(em, result.toggleId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadToggleSnapshot(em, result.toggleId)\n return {\n actionLabel: translate('feature_toggles.audit.toggles.create', 'Create toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: result.toggleId,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const toggleId = logEntry?.resourceId ?? null\n if (!toggleId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId })\n if (overrides.length > 0) {\n em.remove(overrides)\n }\n const toggle = await em.findOne(FeatureToggle, { id: toggleId })\n if (toggle) {\n em.remove(toggle)\n await em.flush()\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n }\n}\n\nconst updateToggleCommand: CommandHandler<ToggleUpdateInput, { toggleId: string }> = {\n id: 'feature_toggles.global.update',\n async prepare(rawInput, ctx) {\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const parsed = toggleUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id: parsed.id })\n if (!toggle) throw new CrudHttpError(404, { error: 'Toggle not found' })\n toggle.identifier = parsed.identifier ?? toggle.identifier\n toggle.name = parsed.name ?? toggle.name\n toggle.description = parsed.description ?? toggle.description\n toggle.category = parsed.category ?? toggle.category\n toggle.type = parsed.type ?? toggle.type\n toggle.defaultValue = parsed.defaultValue ?? toggle.defaultValue\n await em.flush()\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ToggleSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadToggleSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as unknown as Record<string, unknown>,\n afterSnapshot as unknown as Record<string, unknown>,\n [\n 'identifier',\n 'name',\n 'description',\n 'category',\n 'failMode',\n 'type',\n 'defaultValue',\n ]\n )\n : {}\n\n return {\n actionLabel: translate('feature_toggles.audit.toggles.update', 'Update toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n }\n await em.flush()\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n }\n}\n\nconst deleteToggleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { toggleId: string }> =\n{\n id: 'feature_toggles.global.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadToggleSnapshot(em, id)\n const overrides = await loadOverrideSnapshots(em, id)\n return snapshot ? { before: snapshot, overrides } : {}\n },\n async execute(input, ctx) {\n assertGlobalToggleSuperAdmin(ctx)\n const id = requireId(input, 'Feature toggle id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const toggle = await em.findOne(FeatureToggle, { id })\n if (!toggle) throw new CrudHttpError(404, { error: 'Feature toggle not found' })\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n\n const overrides = await em.find(FeatureToggleOverride, { toggle: toggle.id })\n if (overrides.length > 0) {\n em.remove(overrides)\n }\n\n em.remove(toggle)\n await em.flush()\n\n return { toggleId: toggle.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as ToggleSnapshot | undefined\n const overrides = (snapshots as any).overrides as OverrideSnapshot[] | undefined\n\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('feature_toggles.audit.toggles.delete', 'Delete toggle'),\n resourceKind: 'feature_toggles.global',\n resourceId: before.id,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n overrides,\n } satisfies ToggleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ToggleUndoPayload>(logEntry)\n const before = payload?.before\n const overrides = payload?.overrides || []\n\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let toggle = await em.findOne(FeatureToggle, { id: before.id })\n if (!toggle) {\n toggle = em.create(FeatureToggle, {\n id: before.id,\n identifier: before.identifier,\n name: before.name,\n description: before.description,\n category: before.category,\n type: before.type,\n defaultValue: before.defaultValue,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(toggle)\n\n for (const ov of overrides) {\n const override = em.create(FeatureToggleOverride, {\n id: ov.id,\n toggle: toggle,\n tenantId: ov.tenantId,\n value: ov.value ?? null,\n })\n em.persist(override)\n }\n } else {\n toggle.identifier = before.identifier\n toggle.name = before.name\n toggle.description = before.description\n toggle.category = before.category\n }\n const featureTogglesService = ctx.container.resolve('featureTogglesService') as FeatureTogglesService\n await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier)\n await em.flush()\n },\n}\n\nregisterCommand(createToggleCommand)\nregisterCommand(updateToggleCommand)\nregisterCommand(deleteToggleCommand)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,13 @@
1
1
  const setup = {
2
2
  defaultRoleFeatures: {
3
- admin: ["feature_toggles.*"]
3
+ // Global feature toggles are a platform-wide (non-tenant-scoped) table, so
4
+ // creating/updating/deleting them is restricted to super administrators.
5
+ superadmin: ["feature_toggles.global.manage"],
6
+ // Tenant admins may view global toggles and manage their own per-tenant
7
+ // overrides, but MUST NOT mutate the shared global toggle definitions.
8
+ // Granted explicitly (not via `feature_toggles.*`) so the wildcard does not
9
+ // implicitly cover `feature_toggles.global.manage`.
10
+ admin: ["feature_toggles.view", "feature_toggles.manage"]
4
11
  }
5
12
  };
6
13
  var setup_default = setup;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/feature_toggles/setup.ts"],
4
- "sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n admin: ['feature_toggles.*'],\n },\n}\n\nexport default setup\n"],
5
- "mappings": "AAEO,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA,IACnB,OAAO,CAAC,mBAAmB;AAAA,EAC7B;AACF;AAEA,IAAO,gBAAQ;",
4
+ "sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n // Global feature toggles are a platform-wide (non-tenant-scoped) table, so\n // creating/updating/deleting them is restricted to super administrators.\n superadmin: ['feature_toggles.global.manage'],\n // Tenant admins may view global toggles and manage their own per-tenant\n // overrides, but MUST NOT mutate the shared global toggle definitions.\n // Granted explicitly (not via `feature_toggles.*`) so the wildcard does not\n // implicitly cover `feature_toggles.global.manage`.\n admin: ['feature_toggles.view', 'feature_toggles.manage'],\n },\n}\n\nexport default setup\n"],
5
+ "mappings": "AAEO,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA;AAAA;AAAA,IAGnB,YAAY,CAAC,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA,IAK5C,OAAO,CAAC,wBAAwB,wBAAwB;AAAA,EAC1D;AACF;AAEA,IAAO,gBAAQ;",
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.4-develop.4270.1.a614eb18e6",
3
+ "version": "0.6.4-develop.4299.1.af24e08431",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -243,16 +243,16 @@
243
243
  "zod": "^4.4.3"
244
244
  },
245
245
  "peerDependencies": {
246
- "@open-mercato/ai-assistant": "0.6.4-develop.4270.1.a614eb18e6",
247
- "@open-mercato/shared": "0.6.4-develop.4270.1.a614eb18e6",
248
- "@open-mercato/ui": "0.6.4-develop.4270.1.a614eb18e6",
246
+ "@open-mercato/ai-assistant": "0.6.4-develop.4299.1.af24e08431",
247
+ "@open-mercato/shared": "0.6.4-develop.4299.1.af24e08431",
248
+ "@open-mercato/ui": "0.6.4-develop.4299.1.af24e08431",
249
249
  "react": "^19.0.0",
250
250
  "react-dom": "^19.0.0"
251
251
  },
252
252
  "devDependencies": {
253
- "@open-mercato/ai-assistant": "0.6.4-develop.4270.1.a614eb18e6",
254
- "@open-mercato/shared": "0.6.4-develop.4270.1.a614eb18e6",
255
- "@open-mercato/ui": "0.6.4-develop.4270.1.a614eb18e6",
253
+ "@open-mercato/ai-assistant": "0.6.4-develop.4299.1.af24e08431",
254
+ "@open-mercato/shared": "0.6.4-develop.4299.1.af24e08431",
255
+ "@open-mercato/ui": "0.6.4-develop.4299.1.af24e08431",
256
256
  "@testing-library/dom": "^10.4.1",
257
257
  "@testing-library/jest-dom": "^6.9.1",
258
258
  "@testing-library/react": "^16.3.1",
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import * as React from 'react'
4
- import { Users, Phone, Check, Mail, Calendar, AlertTriangle, X } from 'lucide-react'
4
+ import { Users, Phone, Check, Mail, Calendar, AlertTriangle, X, StickyNote } from 'lucide-react'
5
5
  import { cn } from '@open-mercato/shared/lib/utils'
6
6
  import { useT } from '@open-mercato/shared/lib/i18n/context'
7
7
  import { validatePhoneNumber } from '@open-mercato/shared/lib/phone'
@@ -33,6 +33,7 @@ const TYPE_TABS: Array<{ type: ActivityType; icon: React.ComponentType<{ classNa
33
33
  { type: 'call', icon: Phone, labelKey: 'customers.schedule.types.call', fallback: 'Call' },
34
34
  { type: 'task', icon: Check, labelKey: 'customers.schedule.types.task', fallback: 'Task' },
35
35
  { type: 'email', icon: Mail, labelKey: 'customers.schedule.types.email', fallback: 'Email' },
36
+ { type: 'note', icon: StickyNote, labelKey: 'customers.schedule.types.note', fallback: 'Note' },
36
37
  ]
37
38
 
38
39
  type DialogChrome = { titleKey: string; titleFallback: string; subtitleKey: string; subtitleFallback: string; saveKey: string; saveFallback: string; saveIcon: React.ComponentType<{ className?: string }> }
@@ -58,6 +59,11 @@ const TYPE_CHROME: Record<ActivityType, DialogChrome> = {
58
59
  subtitleKey: 'customers.schedule.email.subtitle', subtitleFallback: 'Compose and send a tracked email',
59
60
  saveKey: 'customers.schedule.email.save', saveFallback: 'Send email', saveIcon: Mail,
60
61
  },
62
+ note: {
63
+ titleKey: 'customers.schedule.note.title', titleFallback: 'Add note',
64
+ subtitleKey: 'customers.schedule.note.subtitle', subtitleFallback: 'Write down a note about this interaction',
65
+ saveKey: 'customers.schedule.note.save', saveFallback: 'Save note', saveIcon: StickyNote,
66
+ },
61
67
  }
62
68
 
63
69
  const CALL_DIRECTIONS: Array<{ key: 'outbound' | 'inbound'; labelKey: string; labelFallback: string; dot: string }> = [
@@ -1,4 +1,4 @@
1
- export type ActivityType = 'meeting' | 'call' | 'task' | 'email'
1
+ export type ActivityType = 'meeting' | 'call' | 'task' | 'email' | 'note'
2
2
 
3
3
  export type ScheduleFieldId =
4
4
  | 'title'
@@ -39,6 +39,9 @@ export const FIELD_VISIBILITY: Record<ActivityType, Set<ScheduleFieldId>> = {
39
39
  'participants', 'linkedEntities', 'description',
40
40
  'reminder', 'visibility',
41
41
  ]),
42
+ note: new Set([
43
+ 'title', 'linkedEntities', 'description', 'visibility',
44
+ ]),
42
45
  }
43
46
 
44
47
  type LabelOverride = { key: string; fallback: string }
@@ -71,6 +74,10 @@ export const FIELD_LABEL_OVERRIDES: Partial<
71
74
  linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },
72
75
  description: { key: 'customers.schedule.message', fallback: 'Message' },
73
76
  },
77
+ note: {
78
+ linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },
79
+ description: { key: 'customers.schedule.note.content', fallback: 'Note' },
80
+ },
74
81
  }
75
82
 
76
83
  export function isVisible(type: ActivityType, fieldId: ScheduleFieldId): boolean {
@@ -60,6 +60,7 @@ const DEFAULT_REMINDER_MINUTES: Record<ActivityType, number> = {
60
60
  call: 5,
61
61
  task: 1440,
62
62
  email: 15,
63
+ note: 15,
63
64
  }
64
65
 
65
66
  function padDatePart(value: number): string {
@@ -2207,6 +2207,10 @@
2207
2207
  "customers.schedule.meeting.title": "Neues Meeting",
2208
2208
  "customers.schedule.message": "Message",
2209
2209
  "customers.schedule.noResults": "No results",
2210
+ "customers.schedule.note.content": "Notiz",
2211
+ "customers.schedule.note.save": "Notiz speichern",
2212
+ "customers.schedule.note.subtitle": "Eine Notiz zu dieser Interaktion schreiben",
2213
+ "customers.schedule.note.title": "Notiz hinzufügen",
2210
2214
  "customers.schedule.participants": "Participants",
2211
2215
  "customers.schedule.recurrence.active": "Repeats",
2212
2216
  "customers.schedule.recurrence.afterCount": "After {{count}} occurrences",
@@ -2253,6 +2257,7 @@
2253
2257
  "customers.schedule.types.call": "Call",
2254
2258
  "customers.schedule.types.email": "Email",
2255
2259
  "customers.schedule.types.meeting": "Meeting",
2260
+ "customers.schedule.types.note": "Notiz",
2256
2261
  "customers.schedule.types.task": "Task",
2257
2262
  "customers.schedule.update": "Aktivität aktualisieren",
2258
2263
  "customers.schedule.visibility": "Visibility",
@@ -2207,6 +2207,10 @@
2207
2207
  "customers.schedule.meeting.title": "New meeting",
2208
2208
  "customers.schedule.message": "Message",
2209
2209
  "customers.schedule.noResults": "No results",
2210
+ "customers.schedule.note.content": "Note",
2211
+ "customers.schedule.note.save": "Save note",
2212
+ "customers.schedule.note.subtitle": "Write down a note about this interaction",
2213
+ "customers.schedule.note.title": "Add note",
2210
2214
  "customers.schedule.participants": "Participants",
2211
2215
  "customers.schedule.recurrence.active": "Repeats",
2212
2216
  "customers.schedule.recurrence.afterCount": "After {{count}} occurrences",
@@ -2253,6 +2257,7 @@
2253
2257
  "customers.schedule.types.call": "Call",
2254
2258
  "customers.schedule.types.email": "Email",
2255
2259
  "customers.schedule.types.meeting": "Meeting",
2260
+ "customers.schedule.types.note": "Note",
2256
2261
  "customers.schedule.types.task": "Task",
2257
2262
  "customers.schedule.update": "Update activity",
2258
2263
  "customers.schedule.visibility": "Visibility",
@@ -2207,6 +2207,10 @@
2207
2207
  "customers.schedule.meeting.title": "Nueva reunión",
2208
2208
  "customers.schedule.message": "Message",
2209
2209
  "customers.schedule.noResults": "No results",
2210
+ "customers.schedule.note.content": "Nota",
2211
+ "customers.schedule.note.save": "Guardar nota",
2212
+ "customers.schedule.note.subtitle": "Escribe una nota sobre esta interacción",
2213
+ "customers.schedule.note.title": "Añadir nota",
2210
2214
  "customers.schedule.participants": "Participants",
2211
2215
  "customers.schedule.recurrence.active": "Repeats",
2212
2216
  "customers.schedule.recurrence.afterCount": "After {{count}} occurrences",
@@ -2253,6 +2257,7 @@
2253
2257
  "customers.schedule.types.call": "Call",
2254
2258
  "customers.schedule.types.email": "Email",
2255
2259
  "customers.schedule.types.meeting": "Meeting",
2260
+ "customers.schedule.types.note": "Nota",
2256
2261
  "customers.schedule.types.task": "Task",
2257
2262
  "customers.schedule.update": "Actualizar actividad",
2258
2263
  "customers.schedule.visibility": "Visibility",
@@ -2207,6 +2207,10 @@
2207
2207
  "customers.schedule.meeting.title": "Nowe spotkanie",
2208
2208
  "customers.schedule.message": "Wiadomość",
2209
2209
  "customers.schedule.noResults": "No results",
2210
+ "customers.schedule.note.content": "Notatka",
2211
+ "customers.schedule.note.save": "Zapisz notatkę",
2212
+ "customers.schedule.note.subtitle": "Zapisz notatkę dotyczącą tej interakcji",
2213
+ "customers.schedule.note.title": "Dodaj notatkę",
2210
2214
  "customers.schedule.participants": "Uczestnicy",
2211
2215
  "customers.schedule.recurrence.active": "Powtarza się",
2212
2216
  "customers.schedule.recurrence.afterCount": "Po {{count}} wystąpieniach",
@@ -2253,6 +2257,7 @@
2253
2257
  "customers.schedule.types.call": "Połączenie",
2254
2258
  "customers.schedule.types.email": "E-mail",
2255
2259
  "customers.schedule.types.meeting": "Spotkanie",
2260
+ "customers.schedule.types.note": "Notatka",
2256
2261
  "customers.schedule.types.task": "Zadanie",
2257
2262
  "customers.schedule.update": "Aktualizuj aktywność",
2258
2263
  "customers.schedule.visibility": "Widoczność",
@@ -1,6 +1,7 @@
1
1
  export const features = [
2
2
  { id: 'feature_toggles.view', title: 'View feature toggles', module: 'feature_toggles' },
3
- { id: 'feature_toggles.manage', title: 'Manage feature toggles', module: 'feature_toggles' },
3
+ { id: 'feature_toggles.manage', title: 'Manage per-tenant feature toggle overrides', module: 'feature_toggles' },
4
+ { id: 'feature_toggles.global.manage', title: 'Manage system-wide global feature toggles', module: 'feature_toggles' },
4
5
  ]
5
6
 
6
7
  export default features
@@ -30,9 +30,11 @@ type FeatureToggleListQuery = z.infer<typeof listQuerySchema>
30
30
 
31
31
  const routeMetadata = {
32
32
  GET: { requireAuth: true, requireFeatures: ['feature_toggles.view'] },
33
- POST: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },
34
- PUT: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },
35
- DELETE: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },
33
+ // Global feature toggles are platform-wide (no tenant_id); writing them is
34
+ // restricted to super administrators via the dedicated global feature.
35
+ POST: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },
36
+ PUT: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },
37
+ DELETE: { requireAuth: true, requireFeatures: ['feature_toggles.global.manage'] },
36
38
  }
37
39
 
38
40
  const listFields = [
@@ -95,6 +95,12 @@ function buildCommandContext(container: Awaited<ReturnType<typeof createRequestC
95
95
  selectedOrganizationId: null,
96
96
  organizationIds: null,
97
97
  request: undefined as any,
98
+ // CLI invocations (seed-defaults, toggle-create/update/delete) are trusted
99
+ // server-side calls with no end-user actor. Global feature toggles are a
100
+ // platform-wide table whose writes are restricted to super admins (#2266);
101
+ // this flag lets the command-level guard recognize the system caller so
102
+ // `yarn initialize` can seed defaults without an authenticated super admin.
103
+ systemActor: true,
98
104
  }
99
105
  }
100
106
 
@@ -9,6 +9,18 @@ import { buildChanges, requireId } from '@open-mercato/shared/lib/commands/helpe
9
9
  import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
10
10
  import { FeatureTogglesService } from '../lib/feature-flag-check'
11
11
 
12
+ function assertGlobalToggleSuperAdmin(ctx: { auth?: { [key: string]: unknown } | null; systemActor?: boolean }): void {
13
+ // Trusted server-side callers (CLI seed-defaults/toggle-*, tenant setup) run
14
+ // without an authenticated actor and opt in via `systemActor`. HTTP request
15
+ // paths never set it and always carry a real `auth` actor, so an authenticated
16
+ // but non-super-admin caller — the cross-tenant escalation vector (#2266) —
17
+ // stays denied.
18
+ if (ctx.systemActor === true) return
19
+ if (ctx.auth?.isSuperAdmin !== true) {
20
+ throw new CrudHttpError(403, { error: 'Global feature toggles can only be managed by a super administrator.' })
21
+ }
22
+ }
23
+
12
24
  type ToggleSnapshot = {
13
25
  id: string
14
26
  identifier: string
@@ -59,6 +71,7 @@ async function loadOverrideSnapshots(em: EntityManager, toggleId: string): Promi
59
71
  const createToggleCommand: CommandHandler<ToggleCreateInput, { toggleId: string }> = {
60
72
  id: 'feature_toggles.global.create',
61
73
  async execute(rawInput, ctx) {
74
+ assertGlobalToggleSuperAdmin(ctx)
62
75
  const parsed = toggleCreateSchema.parse(rawInput)
63
76
  const em = (ctx.container.resolve('em') as EntityManager).fork()
64
77
  const toggle = em.create(FeatureToggle, {
@@ -121,6 +134,7 @@ const updateToggleCommand: CommandHandler<ToggleUpdateInput, { toggleId: string
121
134
  return snapshot ? { before: snapshot } : {}
122
135
  },
123
136
  async execute(rawInput, ctx) {
137
+ assertGlobalToggleSuperAdmin(ctx)
124
138
  const parsed = toggleUpdateSchema.parse(rawInput)
125
139
  const em = (ctx.container.resolve('em') as EntityManager).fork()
126
140
  const toggle = await em.findOne(FeatureToggle, { id: parsed.id })
@@ -216,6 +230,7 @@ const deleteToggleCommand: CommandHandler<{ body?: Record<string, unknown>; quer
216
230
  return snapshot ? { before: snapshot, overrides } : {}
217
231
  },
218
232
  async execute(input, ctx) {
233
+ assertGlobalToggleSuperAdmin(ctx)
219
234
  const id = requireId(input, 'Feature toggle id required')
220
235
  const em = (ctx.container.resolve('em') as EntityManager).fork()
221
236
  const toggle = await em.findOne(FeatureToggle, { id })
@@ -2,7 +2,14 @@ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
2
 
3
3
  export const setup: ModuleSetupConfig = {
4
4
  defaultRoleFeatures: {
5
- admin: ['feature_toggles.*'],
5
+ // Global feature toggles are a platform-wide (non-tenant-scoped) table, so
6
+ // creating/updating/deleting them is restricted to super administrators.
7
+ superadmin: ['feature_toggles.global.manage'],
8
+ // Tenant admins may view global toggles and manage their own per-tenant
9
+ // overrides, but MUST NOT mutate the shared global toggle definitions.
10
+ // Granted explicitly (not via `feature_toggles.*`) so the wildcard does not
11
+ // implicitly cover `feature_toggles.global.manage`.
12
+ admin: ['feature_toggles.view', 'feature_toggles.manage'],
6
13
  },
7
14
  }
8
15