@antelopejs/dms-mailing 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +11 -11
  2. package/dist/data/read-only-routes.d.ts +14 -1
  3. package/dist/data/sends-table.d.ts +14 -1
  4. package/dist/data/suppressions-table.d.ts +14 -1
  5. package/dist/data/templates-table.d.ts +15 -2
  6. package/dist/db/models/send-events.model.d.ts +3 -3
  7. package/dist/db/models/sends.model.d.ts +3 -3
  8. package/dist/db/models/settings.model.d.ts +3 -3
  9. package/dist/db/models/suppressions.model.d.ts +3 -3
  10. package/dist/db/models/template-versions.model.d.ts +3 -3
  11. package/dist/db/models/template-versions.model.js +2 -3
  12. package/dist/db/models/template-versions.model.js.map +1 -1
  13. package/dist/db/models/templates.model.d.ts +4 -3
  14. package/dist/db/models/templates.model.js +14 -6
  15. package/dist/db/models/templates.model.js.map +1 -1
  16. package/dist/db/models/webhook-status.model.d.ts +3 -3
  17. package/dist/db/tables/templates.table.d.ts +4 -1
  18. package/dist/hooks/tenant-export.js +5 -1
  19. package/dist/hooks/tenant-export.js.map +1 -1
  20. package/dist/pages/editor.js +1 -1
  21. package/dist/pages/editor.js.map +1 -1
  22. package/dist/routes/sends.js +2 -1
  23. package/dist/routes/sends.js.map +1 -1
  24. package/dist/routes/templates.d.ts +2 -2
  25. package/dist/routes/templates.js +2 -2
  26. package/dist/routes/templates.js.map +1 -1
  27. package/dist/services/settings.js +2 -1
  28. package/dist/services/settings.js.map +1 -1
  29. package/dist/services/templates.js +10 -8
  30. package/dist/services/templates.js.map +1 -1
  31. package/dist/validation/declarations.schema.d.ts +1 -1
  32. package/dist/validation/stored.schema.d.ts +12 -0
  33. package/dist/validation/stored.schema.js +198 -0
  34. package/dist/validation/stored.schema.js.map +1 -0
  35. package/frontend-vue/app/build/components/FormFieldRow.vue +1 -1
  36. package/frontend-vue/app/build/components/TemplateDrawerPerformance.vue +3 -3
  37. package/frontend-vue/app/build/components/TemplateDrawerVariables.vue +4 -4
  38. package/frontend-vue/app/components/CategoriesInput.vue +2 -2
  39. package/frontend-vue/app/components/CategoryInput.vue +1 -1
  40. package/frontend-vue/app/components/FunnelCard.vue +4 -4
  41. package/frontend-vue/app/components/LocaleSelect.vue +1 -1
  42. package/frontend-vue/app/components/NewTemplateModal.vue +1 -1
  43. package/frontend-vue/app/components/OverviewGate.vue +6 -6
  44. package/frontend-vue/app/components/RealSendModal.vue +6 -6
  45. package/frontend-vue/app/components/RenderingModal.vue +2 -2
  46. package/frontend-vue/app/components/RetentionInput.vue +1 -1
  47. package/frontend-vue/app/components/SendDrawer.vue +9 -9
  48. package/frontend-vue/app/components/StarterGallery.vue +2 -2
  49. package/frontend-vue/app/components/SystemVariables.vue +1 -1
  50. package/frontend-vue/app/components/TemplateCard.vue +2 -2
  51. package/frontend-vue/app/components/TemplateDrawer.vue +4 -4
  52. package/frontend-vue/app/components/TemplateGallery.vue +2 -2
  53. package/frontend-vue/app/components/TestSendModal.vue +4 -4
  54. package/frontend-vue/app/components/VersionPreview.vue +3 -3
  55. package/frontend-vue/app/components/editor/EditorBlockFields.vue +1 -1
  56. package/frontend-vue/app/components/editor/EditorCompare.vue +1 -1
  57. package/frontend-vue/app/components/editor/EditorEnvelope.vue +2 -2
  58. package/frontend-vue/app/components/editor/EditorLifecycle.vue +1 -1
  59. package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +1 -1
  60. package/frontend-vue/app/components/editor/EditorPublishReview.vue +4 -4
  61. package/frontend-vue/app/components/editor/EditorTemplatePane.vue +2 -2
  62. package/frontend-vue/app/components/editor/EditorTestDataPane.vue +1 -1
  63. package/frontend-vue/app/components/editor/EditorVariablesPane.vue +1 -1
  64. package/frontend-vue/app/components/editor/EditorViewSwitches.vue +2 -2
  65. package/frontend-vue/app/components/editor/EditorVisibility.vue +1 -1
  66. package/frontend-vue/app/composables/useEditorContext.ts +1 -1
  67. package/frontend-vue/build/backend-boundary.ts +7 -2
  68. package/frontend-vue/dms.frontend.ts +1 -4
  69. package/frontend-vue/eslint.config.mjs +9 -1
  70. package/frontend-vue/package.json +2 -2
  71. package/frontend-vue/pnpm-lock.yaml +24 -7
  72. package/frontend-vue/tests/backend-boundary.test.ts +4 -4
  73. package/frontend-vue/tests/categories-input.test.ts +1 -1
  74. package/frontend-vue/tests/column-displays.test.ts +13 -12
  75. package/frontend-vue/tests/dms-component-names.test.ts +12 -0
  76. package/frontend-vue/tests/footer-block.test.ts +1 -1
  77. package/frontend-vue/tests/funnel-card.test.ts +3 -3
  78. package/frontend-vue/tests/helpers/component-tags.ts +58 -0
  79. package/frontend-vue/tests/helpers/nuxt-globals.ts +1 -1
  80. package/frontend-vue/tests/overview-gate.test.ts +1 -1
  81. package/frontend-vue/tests/retention-input.test.ts +2 -2
  82. package/frontend-vue/tests/send-drawer.test.ts +2 -2
  83. package/frontend-vue/tests/send-modals.test.ts +1 -1
  84. package/frontend-vue/tests/template-drawer.test.ts +6 -6
  85. package/frontend-vue/tests/template-gallery-display.test.ts +6 -5
  86. package/frontend-vue/tests/template-gallery.test.ts +3 -3
  87. package/frontend-vue/tests/template-overlays.test.ts +4 -12
  88. package/package.json +16 -16
  89. package/src/db/models/template-versions.model.ts +8 -6
  90. package/src/db/models/templates.model.ts +25 -9
  91. package/src/hooks/tenant-export.ts +5 -2
  92. package/src/pages/editor.ts +1 -1
  93. package/src/routes/sends.ts +2 -3
  94. package/src/routes/templates.ts +3 -2
  95. package/src/services/settings.ts +9 -3
  96. package/src/services/templates.ts +35 -16
  97. package/src/validation/stored.schema.ts +257 -0
  98. /package/frontend-vue/app/components/{Editor.vue → TemplateEditor.vue} +0 -0
  99. /package/frontend-vue/app/components/blocks/{Button.vue → BlockButton.vue} +0 -0
  100. /package/frontend-vue/app/components/blocks/{Code.vue → BlockCode.vue} +0 -0
  101. /package/frontend-vue/app/components/blocks/{Divider.vue → BlockDivider.vue} +0 -0
  102. /package/frontend-vue/app/components/blocks/{Footer.vue → BlockFooter.vue} +0 -0
  103. /package/frontend-vue/app/components/blocks/{Heading.vue → BlockHeading.vue} +0 -0
  104. /package/frontend-vue/app/components/blocks/{Hero.vue → BlockHero.vue} +0 -0
  105. /package/frontend-vue/app/components/blocks/{List.vue → BlockList.vue} +0 -0
  106. /package/frontend-vue/app/components/blocks/{Paragraph.vue → BlockParagraph.vue} +0 -0
  107. /package/frontend-vue/app/components/blocks/{Total.vue → BlockTotal.vue} +0 -0
@@ -83,7 +83,7 @@ function createTemplate(): void {
83
83
  </script>
84
84
 
85
85
  <template>
86
- <DmsCard
86
+ <DmsCardPanel
87
87
  v-if="state === 'loading' && !facts"
88
88
  :padded="false"
89
89
  aria-busy="true"
@@ -92,16 +92,16 @@ function createTemplate(): void {
92
92
  <USkeleton class="h-6 w-56" />
93
93
  <USkeleton v-for="row in SKELETON_ROWS" :key="row" class="h-10 w-full" />
94
94
  </div>
95
- </DmsCard>
95
+ </DmsCardPanel>
96
96
 
97
- <DmsCard v-else-if="state === 'error' && !facts" :padded="false">
97
+ <DmsCardPanel v-else-if="state === 'error' && !facts" :padded="false">
98
98
  <DmsEmptyState
99
99
  variant="error"
100
100
  :title="key('error_title')"
101
101
  :description="key('error_description')"
102
102
  :actions="errorActions"
103
103
  />
104
- </DmsCard>
104
+ </DmsCardPanel>
105
105
 
106
106
  <div v-else class="flex flex-col gap-4">
107
107
  <div v-if="canResume" class="flex justify-end">
@@ -115,7 +115,7 @@ function createTemplate(): void {
115
115
  />
116
116
  </div>
117
117
 
118
- <DmsCard v-if="showsSetup" :padded="false">
118
+ <DmsCardPanel v-if="showsSetup" :padded="false">
119
119
  <div
120
120
  class="grid gap-8 p-6 sm:p-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]"
121
121
  >
@@ -196,7 +196,7 @@ function createTemplate(): void {
196
196
  </li>
197
197
  </ol>
198
198
  </div>
199
- </DmsCard>
199
+ </DmsCardPanel>
200
200
 
201
201
  <slot v-if="showsDashboard" />
202
202
  </div>
@@ -345,7 +345,7 @@ async function submit(): Promise<void> {
345
345
  novalidate
346
346
  @submit.prevent="submit"
347
347
  >
348
- <DmsBanner
348
+ <DmsNoticeBanner
349
349
  tone="error"
350
350
  icon="i-ph-paper-plane-right"
351
351
  :title="
@@ -362,7 +362,7 @@ async function submit(): Promise<void> {
362
362
 
363
363
  <FormFieldRow :label="t('dms_mailing.test_send.locale')">
364
364
  <template #default="{ id }">
365
- <DmsSelect
365
+ <DmsInputSelect
366
366
  :id="id"
367
367
  v-model="locale"
368
368
  :items="localeItems"
@@ -420,7 +420,7 @@ async function submit(): Promise<void> {
420
420
  </p>
421
421
  </section>
422
422
 
423
- <DmsBanner
423
+ <DmsNoticeBanner
424
424
  v-if="detail && !hasSender"
425
425
  tone="warning"
426
426
  icon="i-ph-at"
@@ -437,7 +437,7 @@ async function submit(): Promise<void> {
437
437
  @click="openSettings"
438
438
  />
439
439
  </template>
440
- </DmsBanner>
440
+ </DmsNoticeBanner>
441
441
 
442
442
  <div class="border-default rounded-lg border px-3">
443
443
  <DmsKeyValueList
@@ -478,7 +478,7 @@ async function submit(): Promise<void> {
478
478
  </DmsKeyValueList>
479
479
  </div>
480
480
 
481
- <DmsCheckbox
481
+ <DmsInputCheckbox
482
482
  v-model="isAcknowledged"
483
483
  :disabled="!recipients.length"
484
484
  :label="
@@ -517,7 +517,7 @@ async function submit(): Promise<void> {
517
517
  </form>
518
518
 
519
519
  <div v-else class="flex flex-col gap-4">
520
- <DmsBanner
520
+ <DmsNoticeBanner
521
521
  tone="warning"
522
522
  icon="i-ph-paper-plane-right"
523
523
  :title="
@@ -116,7 +116,7 @@ onMounted(load)
116
116
  {{ key('description') }}
117
117
  <span class="font-mono text-[12px]">{{ sendIdValue }}</span>
118
118
  </p>
119
- <DmsSegmented
119
+ <DmsSegmentedControl
120
120
  v-model="view"
121
121
  size="xs"
122
122
  :items="viewItems"
@@ -138,7 +138,7 @@ onMounted(load)
138
138
  />
139
139
 
140
140
  <template v-else>
141
- <DmsBanner
141
+ <DmsNoticeBanner
142
142
  v-if="rendering.approximate"
143
143
  tone="warning"
144
144
  size="sm"
@@ -167,7 +167,7 @@ form?.addValidator(guardSubmit)
167
167
  <template>
168
168
  <div class="flex flex-col gap-2">
169
169
  <div class="flex flex-wrap items-center gap-2">
170
- <DmsSegmented
170
+ <DmsSegmentedControl
171
171
  v-model="choice"
172
172
  :items="items"
173
173
  :disabled="disabled"
@@ -424,7 +424,7 @@ onMounted(() => {
424
424
  <template v-else-if="send">
425
425
  <header class="border-default flex flex-col gap-1.5 border-b pb-4">
426
426
  <div class="flex items-center gap-1">
427
- <DmsEyebrow
427
+ <DmsEyebrowLabel
428
428
  class="min-w-0 flex-1"
429
429
  truncate
430
430
  :label="key('eyebrow', { id: shortSendId(send._id) })"
@@ -481,7 +481,7 @@ onMounted(() => {
481
481
  columns-from="md"
482
482
  :items="stats"
483
483
  />
484
- <DmsBanner
484
+ <DmsNoticeBanner
485
485
  v-if="problem"
486
486
  :tone="isResolved ? 'info' : problemTone(problem.kind)"
487
487
  :size="isResolved ? 'sm' : undefined"
@@ -492,7 +492,7 @@ onMounted(() => {
492
492
  "
493
493
  data-problem-banner
494
494
  />
495
- <DmsBanner
495
+ <DmsNoticeBanner
496
496
  v-if="suppression"
497
497
  :tone="suppression.tone"
498
498
  size="sm"
@@ -519,8 +519,8 @@ onMounted(() => {
519
519
  @click="openSuppressionList"
520
520
  />
521
521
  </template>
522
- </DmsBanner>
523
- <DmsBanner
522
+ </DmsNoticeBanner>
523
+ <DmsNoticeBanner
524
524
  v-for="link in linkedSends"
525
525
  :key="link.id"
526
526
  tone="info"
@@ -539,7 +539,7 @@ onMounted(() => {
539
539
  @click="void showSend(link.id)"
540
540
  />
541
541
  </template>
542
- </DmsBanner>
542
+ </DmsNoticeBanner>
543
543
  </section>
544
544
 
545
545
  <!--
@@ -548,7 +548,7 @@ onMounted(() => {
548
548
  -->
549
549
  <div class="@2xl:grid-cols-2 grid gap-6" data-send-details-grid>
550
550
  <section class="flex min-w-0 flex-col gap-2">
551
- <DmsEyebrow :label="key('timeline')" />
551
+ <DmsEyebrowLabel :label="key('timeline')" />
552
552
  <ol class="flex flex-col">
553
553
  <li
554
554
  v-for="item in timeline"
@@ -581,7 +581,7 @@ onMounted(() => {
581
581
  </section>
582
582
 
583
583
  <section class="flex min-w-0 flex-col gap-2">
584
- <DmsEyebrow :label="key('origin')" />
584
+ <DmsEyebrowLabel :label="key('origin')" />
585
585
  <div
586
586
  class="border-default divide-default divide-y overflow-hidden rounded-lg border"
587
587
  >
@@ -623,7 +623,7 @@ onMounted(() => {
623
623
 
624
624
  <section class="flex flex-col gap-2">
625
625
  <div class="flex items-center justify-between gap-2">
626
- <DmsEyebrow :label="key('payload')" />
626
+ <DmsEyebrowLabel :label="key('payload')" />
627
627
  <UButton
628
628
  v-if="payload"
629
629
  size="xs"
@@ -90,7 +90,7 @@ async function createFrom(starter: StarterSummary): Promise<void> {
90
90
  class="rounded-(--dms-radius-card) h-[150px] w-full"
91
91
  />
92
92
  </template>
93
- <DmsCard
93
+ <DmsCardPanel
94
94
  v-for="starter in starters"
95
95
  v-else
96
96
  :key="starter.id"
@@ -131,7 +131,7 @@ async function createFrom(starter: StarterSummary): Promise<void> {
131
131
  {{ starter.slug }}
132
132
  </span>
133
133
  </div>
134
- </DmsCard>
134
+ </DmsCardPanel>
135
135
  </div>
136
136
  <UButton
137
137
  icon="i-ph-plus"
@@ -22,7 +22,7 @@ const { t } = useI18n()
22
22
  v-if="variables.length || checks.length"
23
23
  class="flex min-w-0 flex-col gap-1"
24
24
  >
25
- <DmsEyebrow :label="t(`${KEY}.title`)" />
25
+ <DmsEyebrowLabel :label="t(`${KEY}.title`)" />
26
26
  <p class="text-muted text-[12px] [overflow-wrap:anywhere]">
27
27
  {{ t(`${KEY}.description`) }}
28
28
  </p>
@@ -159,7 +159,7 @@ function chipLabel(code: string, present: boolean): string {
159
159
  </script>
160
160
 
161
161
  <template>
162
- <DmsCard
162
+ <DmsCardPanel
163
163
  as="article"
164
164
  :padded="false"
165
165
  interactive
@@ -302,5 +302,5 @@ function chipLabel(code: string, present: boolean): string {
302
302
  <span v-else class="truncate">{{ footerText }}</span>
303
303
  </div>
304
304
  </div>
305
- </DmsCard>
305
+ </DmsCardPanel>
306
306
  </template>
@@ -451,7 +451,7 @@ function onResolved(preview: PreviewResponse): void {
451
451
  <template v-else-if="template">
452
452
  <div class="flex flex-col gap-1.5 pb-3">
453
453
  <div class="flex items-center gap-1">
454
- <DmsEyebrow :label="t('dms_mailing.templates.drawer.eyebrow')" />
454
+ <DmsEyebrowLabel :label="t('dms_mailing.templates.drawer.eyebrow')" />
455
455
  <div class="ml-auto flex items-center gap-0.5">
456
456
  <template v-if="navigation">
457
457
  <UButton
@@ -569,7 +569,7 @@ function onResolved(preview: PreviewResponse): void {
569
569
 
570
570
  <div v-show="tab === 'preview'" class="flex flex-col gap-3 py-4">
571
571
  <div class="flex items-center justify-between gap-2">
572
- <DmsSegmented
572
+ <DmsSegmentedControl
573
573
  v-if="localeItems.length > 1"
574
574
  v-model="locale"
575
575
  :items="localeItems"
@@ -599,7 +599,7 @@ function onResolved(preview: PreviewResponse): void {
599
599
  />
600
600
  </div>
601
601
  </div>
602
- <DmsBanner
602
+ <DmsNoticeBanner
603
603
  v-if="isLocaleMissing"
604
604
  tone="warning"
605
605
  size="sm"
@@ -629,7 +629,7 @@ function onResolved(preview: PreviewResponse): void {
629
629
  @click="openEditor(locale)"
630
630
  />
631
631
  </template>
632
- </DmsBanner>
632
+ </DmsNoticeBanner>
633
633
  </div>
634
634
 
635
635
  <div v-show="tab === 'variables'" class="flex flex-col gap-4 py-4">
@@ -250,7 +250,7 @@ useEventListener(
250
250
  &#8203;
251
251
  </div>
252
252
  <div class="grid grid-cols-[repeat(auto-fill,minmax(272px,1fr))] gap-4">
253
- <DmsCard
253
+ <DmsCardPanel
254
254
  v-for="index in SKELETON_CARDS"
255
255
  :key="index"
256
256
  :padded="false"
@@ -273,7 +273,7 @@ useEventListener(
273
273
  &#8203;
274
274
  </div>
275
275
  </div>
276
- </DmsCard>
276
+ </DmsCardPanel>
277
277
  </div>
278
278
  </section>
279
279
 
@@ -242,7 +242,7 @@ async function submit(): Promise<void> {
242
242
  :label="t('dms_mailing.real_send.version')"
243
243
  >
244
244
  <template #default="{ id }">
245
- <DmsSelect
245
+ <DmsInputSelect
246
246
  :id="id"
247
247
  v-model="versionChoice"
248
248
  :items="versionItems"
@@ -257,7 +257,7 @@ async function submit(): Promise<void> {
257
257
 
258
258
  <FormFieldRow :label="t('dms_mailing.test_send.locale')">
259
259
  <template #default="{ id }">
260
- <DmsSelect
260
+ <DmsInputSelect
261
261
  :id="id"
262
262
  v-model="locale"
263
263
  :items="localeItems"
@@ -271,7 +271,7 @@ async function submit(): Promise<void> {
271
271
 
272
272
  <FormFieldRow :label="t('dms_mailing.test_send.data')">
273
273
  <template #default="{ id }">
274
- <DmsSelect
274
+ <DmsInputSelect
275
275
  :id="id"
276
276
  v-model="dataChoice"
277
277
  :items="dataItems"
@@ -284,7 +284,7 @@ async function submit(): Promise<void> {
284
284
  </FormFieldRow>
285
285
  </FormRows>
286
286
 
287
- <DmsBanner
287
+ <DmsNoticeBanner
288
288
  tone="info"
289
289
  icon="i-ph-info"
290
290
  :title="t('dms_mailing.test_send.info_title')"
@@ -62,7 +62,7 @@ function onResolved(preview: PreviewResponse): void {
62
62
  <template>
63
63
  <div class="flex flex-col gap-4">
64
64
  <div class="flex items-center justify-between gap-2">
65
- <DmsSegmented
65
+ <DmsSegmentedControl
66
66
  v-if="localeItems.length > 1"
67
67
  v-model="locale"
68
68
  :items="localeItems"
@@ -95,7 +95,7 @@ function onResolved(preview: PreviewResponse): void {
95
95
 
96
96
  <div class="grid gap-4 sm:grid-cols-2">
97
97
  <div class="flex flex-col gap-1.5">
98
- <DmsEyebrow :label="t(`${KEY}.brought`)" />
98
+ <DmsEyebrowLabel :label="t(`${KEY}.brought`)" />
99
99
  <ul v-if="lines.length" class="flex flex-col gap-1 text-[12.5px]">
100
100
  <li
101
101
  v-for="line in lines"
@@ -114,7 +114,7 @@ function onResolved(preview: PreviewResponse): void {
114
114
  </p>
115
115
  </div>
116
116
  <div class="flex flex-col gap-1.5">
117
- <DmsEyebrow :label="t(`${KEY}.declared`)" />
117
+ <DmsEyebrowLabel :label="t(`${KEY}.declared`)" />
118
118
  <p v-if="!declared.length" class="text-muted text-[12.5px]">
119
119
  {{ t(`${KEY}.no_variables`) }}
120
120
  </p>
@@ -91,7 +91,7 @@ function patchChecked(field: BlockField, value: unknown): void {
91
91
  :is-multiline="field.kind === 'tokens'"
92
92
  @update:model-value="patch(field.key, $event)"
93
93
  />
94
- <DmsSegmented
94
+ <DmsSegmentedControl
95
95
  v-else-if="field.kind === 'align'"
96
96
  :model-value="text(field.key) || ALIGNMENTS[0]"
97
97
  :items="alignItems"
@@ -178,7 +178,7 @@ watch(locale, load, { immediate: true })
178
178
  >
179
179
  <div class="flex min-w-0 flex-col gap-0.5">
180
180
  <div class="flex items-center gap-2">
181
- <DmsEyebrow :label="side.label" />
181
+ <DmsEyebrowLabel :label="side.label" />
182
182
  <span
183
183
  v-if="side.preview"
184
184
  class="text-highlighted min-w-0 truncate text-xs font-medium"
@@ -29,7 +29,7 @@ function openEnvelope(): void {
29
29
  class="hover:bg-elevated/50 col-span-2 grid min-h-[38px] w-full grid-cols-subgrid items-center gap-x-2.5 px-3 text-left"
30
30
  @click.stop="openEnvelope()"
31
31
  >
32
- <DmsEyebrow
32
+ <DmsEyebrowLabel
33
33
  :label="t(`${KEY}.subject`)"
34
34
  class="min-w-[78px] whitespace-nowrap"
35
35
  />
@@ -57,7 +57,7 @@ function openEnvelope(): void {
57
57
  class="border-default hover:bg-elevated/50 col-span-2 grid min-h-[38px] w-full grid-cols-subgrid items-center gap-x-2.5 border-t px-3 text-left"
58
58
  @click.stop="openEnvelope()"
59
59
  >
60
- <DmsEyebrow
60
+ <DmsEyebrowLabel
61
61
  :label="t(`${KEY}.preheader`)"
62
62
  class="min-w-[78px] whitespace-nowrap"
63
63
  />
@@ -45,7 +45,7 @@ const buttons = computed(() => lifecycleActions(status.value))
45
45
 
46
46
  <template>
47
47
  <div class="flex flex-col gap-3">
48
- <DmsEyebrow :label="t(`${KEY}.title`)" />
48
+ <DmsEyebrowLabel :label="t(`${KEY}.title`)" />
49
49
  <DmsStatGroup :items="stats" layout="joined" :columns="3" />
50
50
  <div class="flex flex-wrap items-center gap-1.5">
51
51
  <UButton
@@ -37,7 +37,7 @@ useSortableList(root, () => props.entries, {
37
37
 
38
38
  <template>
39
39
  <section>
40
- <DmsEyebrow :label="title" class="mb-2 block" />
40
+ <DmsEyebrowLabel :label="title" class="mb-2 block" />
41
41
  <div ref="root" class="grid grid-cols-2 gap-1.5">
42
42
  <template v-for="element in entries" :key="element.type">
43
43
  <button
@@ -43,7 +43,7 @@ const { t } = useI18n()
43
43
  {{ t(`${KEY}.no_changes`, { version: currentVersion }) }}
44
44
  </p>
45
45
 
46
- <DmsBanner
46
+ <DmsNoticeBanner
47
47
  v-if="blockers.length"
48
48
  tone="error"
49
49
  icon="i-ph-prohibit"
@@ -54,9 +54,9 @@ const { t } = useI18n()
54
54
  <li v-for="blocker in blockers" :key="blocker">{{ blocker }}</li>
55
55
  </ul>
56
56
  </template>
57
- </DmsBanner>
57
+ </DmsNoticeBanner>
58
58
 
59
- <DmsBanner
59
+ <DmsNoticeBanner
60
60
  v-if="warnings.length"
61
61
  tone="warning"
62
62
  icon="i-ph-warning"
@@ -67,7 +67,7 @@ const { t } = useI18n()
67
67
  <li v-for="warning in warnings" :key="warning">{{ warning }}</li>
68
68
  </ul>
69
69
  </template>
70
- </DmsBanner>
70
+ </DmsNoticeBanner>
71
71
 
72
72
  <div class="border-default -mx-1 flex items-center gap-2 border-t pt-4">
73
73
  <UButton
@@ -95,7 +95,7 @@ const preheader = computed({
95
95
 
96
96
  <template v-if="editor.current">
97
97
  <USeparator />
98
- <DmsEyebrow
98
+ <DmsEyebrowLabel
99
99
  :label="t(`${KEY}.envelope`, { locale: editor.locale.toUpperCase() })"
100
100
  />
101
101
  <UFormField :label="t('dms_mailing.editor.subject')" size="sm">
@@ -112,7 +112,7 @@ const preheader = computed({
112
112
 
113
113
  <USeparator />
114
114
  <div class="flex flex-col gap-2">
115
- <DmsEyebrow :label="t('dms_mailing.history.title')" />
115
+ <DmsEyebrowLabel :label="t('dms_mailing.history.title')" />
116
116
  <MailingVersionHistory
117
117
  v-if="session.state.version > 0"
118
118
  :template-id="templateId"
@@ -91,7 +91,7 @@ async function save(): Promise<void> {
91
91
  </div>
92
92
 
93
93
  <USeparator />
94
- <DmsEyebrow :label="t(`${KEY}.checks`)" />
94
+ <DmsEyebrowLabel :label="t(`${KEY}.checks`)" />
95
95
  <DmsCheckList :items="items" size="sm" />
96
96
  <MailingEditorMissingPolicy />
97
97
  </div>
@@ -114,7 +114,7 @@ function insert(path: string): void {
114
114
 
115
115
  <template>
116
116
  <div class="flex flex-col gap-2">
117
- <DmsEyebrow :label="t(`${KEY}.insert_hint`)" />
117
+ <DmsEyebrowLabel :label="t(`${KEY}.insert_hint`)" />
118
118
  <p class="text-muted text-xs">{{ t(`${KEY}.draft_hint`) }}</p>
119
119
 
120
120
  <p v-if="merged.length === 0" class="text-muted text-xs">
@@ -58,7 +58,7 @@ const mode = computed({
58
58
  "
59
59
  >
60
60
  <span class="inline-flex">
61
- <DmsSegmented
61
+ <DmsSegmentedControl
62
62
  v-model="device"
63
63
  size="xs"
64
64
  :items="deviceItems"
@@ -66,7 +66,7 @@ const mode = computed({
66
66
  />
67
67
  </span>
68
68
  </UTooltip>
69
- <DmsSegmented
69
+ <DmsSegmentedControl
70
70
  v-model="mode"
71
71
  size="xs"
72
72
  :items="modeItems"
@@ -66,7 +66,7 @@ const mode = computed({
66
66
  <template>
67
67
  <div class="flex flex-col gap-2">
68
68
  <p class="text-highlighted text-sm font-medium">{{ t(`${KEY}.label`) }}</p>
69
- <DmsSegmented
69
+ <DmsSegmentedControl
70
70
  v-model="mode"
71
71
  size="xs"
72
72
  :items="items"
@@ -21,7 +21,7 @@ export interface EditorContext {
21
21
  }
22
22
 
23
23
  const EDITOR_KEY: InjectionKey<EditorContext> = Symbol('dms-mailing-editor')
24
- const MISSING_CONTEXT = 'useEditorContext() must be called inside MailingEditor'
24
+ const MISSING_CONTEXT = 'useEditorContext() must be called inside MailingTemplateEditor'
25
25
 
26
26
  export function provideEditorContext(context: EditorContext): void {
27
27
  provide(EDITOR_KEY, context)
@@ -74,6 +74,11 @@ function backendSourceFile(id: string): string | undefined {
74
74
  : undefined
75
75
  }
76
76
 
77
+ /** `path` with `/` separators, the form Vite gives an importer, so a boundary error names both paths alike on every OS. */
78
+ function forwardSlashes(path: string): string {
79
+ return path.split(sep).join('/')
80
+ }
81
+
77
82
  /**
78
83
  * Fails the build and the dev server when a module of the frontend resolves to
79
84
  * a file under the `src` of the backend package: the browser reaches the
@@ -113,9 +118,9 @@ function boundaryCheck(): VitePlugin {
113
118
  const from =
114
119
  importer === undefined
115
120
  ? 'an entry point'
116
- : importer.replace(QUERY_OR_HASH, '')
121
+ : forwardSlashes(importer.replace(QUERY_OR_HASH, ''))
117
122
  return this.error(
118
- `Backend boundary: ${from} imports ${file}, which is under the src of ${BACKEND_PACKAGE}. The frontend reaches the backend only through its HTTP interface.`,
123
+ `Backend boundary: ${from} imports ${forwardSlashes(file)}, which is under the src of ${BACKEND_PACKAGE}. The frontend reaches the backend only through its HTTP interface.`,
119
124
  )
120
125
  },
121
126
  }
@@ -8,17 +8,14 @@ interface VueModule {
8
8
  }
9
9
 
10
10
  const COMPONENT_PREFIX = 'Mailing'
11
- const BLOCKS_DIRECTORY = '/components/blocks/'
12
- const BLOCK_NAME_PREFIX = 'Block'
13
11
 
14
12
  const components = import.meta.glob<VueModule>('./app/components/**/*.vue')
15
13
 
16
14
  function componentName(path: string): string {
17
- const base = path
15
+ return path
18
16
  .split('/')
19
17
  .at(-1)!
20
18
  .replace(/\.vue$/, '')
21
- return path.includes(BLOCKS_DIRECTORY) ? `${BLOCK_NAME_PREFIX}${base}` : base
22
19
  }
23
20
 
24
21
  const frontendModule: DmsFrontendModule = {
@@ -9,7 +9,6 @@ export default [
9
9
  {
10
10
  files: ['**/*.ts', '**/*.vue'],
11
11
  languageOptions: { parserOptions: { parser: ts.parser } },
12
- rules: { 'vue/multi-word-component-names': 'off' },
13
12
  },
14
13
  { files: ['**/*.ts'], languageOptions: { parser: ts.parser } },
15
14
  {
@@ -21,6 +20,15 @@ export default [
21
20
  'antelopejs/no-typeof-function-only-test': 'error',
22
21
  'antelopejs/no-self-import-in-tests': 'error',
23
22
  'anti-slop/no-module-mocking': 'error',
23
+ 'no-restricted-syntax': [
24
+ 'error',
25
+ {
26
+ selector:
27
+ 'CallExpression:matches([callee.name=/^(it|test)$/], [callee.object.name=/^(it|test)$/], [callee.callee.object.name=/^(it|test)$/]) ImportExpression',
28
+ message:
29
+ 'Import the module under test statically at the top of the file: a dynamic import inside a test body counts the module load in the test timeout.',
30
+ },
31
+ ],
24
32
  },
25
33
  },
26
34
  ]
@@ -6,7 +6,7 @@
6
6
  "license": "Apache-2.0",
7
7
  "packageManager": "pnpm@10.2.0",
8
8
  "engines": {
9
- "@antelopejs/dms-frontend": ">=0.10.0 <0.11.0"
9
+ "@antelopejs/dms-frontend": ">=0.11.0 <0.12.0"
10
10
  },
11
11
  "scripts": {
12
12
  "format": "prettier --write .",
@@ -21,7 +21,7 @@
21
21
  "vue": "^3.5.18"
22
22
  },
23
23
  "devDependencies": {
24
- "@antelopejs/tooling-configs": "^0.3.3",
24
+ "@antelopejs/tooling-configs": ">=0.7.1 <0.8.0",
25
25
  "@nuxt/ui": "4.10.0",
26
26
  "@types/sortablejs": "^1.15.9",
27
27
  "@vitejs/plugin-vue": "^6.0.0",