@cosmicdrift/kumiko-document-designer 0.368.0 → 0.370.0

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 (47) hide show
  1. package/dist/constants.d.ts +1 -0
  2. package/dist/constants.js +2 -0
  3. package/dist/i18n.js +24 -16
  4. package/dist/index.d.ts +2 -2
  5. package/dist/web/editor-shell.d.ts +1 -1
  6. package/dist/web/i18n.js +2 -2
  7. package/dist/web/index.d.ts +6 -6
  8. package/dist/web/layout-editor-body.d.ts +2 -2
  9. package/dist/web/layout-rails.d.ts +3 -3
  10. package/dist/web/layout-types.d.ts +2 -2
  11. package/dist/web/layout-users.d.ts +2 -2
  12. package/dist/web/mail-draft.d.ts +2 -2
  13. package/dist/web/mail-draft.js +6 -4
  14. package/dist/web/mail-edit-center.d.ts +3 -3
  15. package/dist/web/mail-editor-body.d.ts +1 -1
  16. package/dist/web/mail-editor-body.js +3 -3
  17. package/dist/web/mail-footer.d.ts +1 -1
  18. package/dist/web/mail-footer.js +4 -3
  19. package/dist/web/mail-left-rail.d.ts +1 -1
  20. package/dist/web/mail-left-rail.js +2 -1
  21. package/dist/web/mail-origin-bar.d.ts +1 -1
  22. package/dist/web/mail-pdf-link-modal.d.ts +1 -1
  23. package/dist/web/mail-preview-side.d.ts +2 -2
  24. package/dist/web/mail-preview-side.js +2 -1
  25. package/dist/web/mail-preview.d.ts +4 -4
  26. package/dist/web/mail-right-rail.d.ts +6 -5
  27. package/dist/web/mail-right-rail.js +29 -9
  28. package/dist/web/mail-types.d.ts +2 -2
  29. package/dist/web/origin-bar.d.ts +1 -1
  30. package/dist/web/overrides-drawer.d.ts +1 -1
  31. package/dist/web/overview-row.d.ts +1 -1
  32. package/dist/web/page-canvas.d.ts +2 -2
  33. package/dist/web/pdf-footer.d.ts +2 -2
  34. package/dist/web/pdf-footer.js +2 -1
  35. package/dist/web/pdf-template-editor.js +1 -1
  36. package/dist/web/rail.d.ts +3 -1
  37. package/dist/web/rail.js +2 -2
  38. package/dist/web/settings-panel.d.ts +5 -5
  39. package/dist/web/status-label.d.ts +1 -1
  40. package/dist/web/template-rows.d.ts +1 -1
  41. package/dist/web/types.d.ts +1 -1
  42. package/dist/web/use-mail-persistence.d.ts +1 -1
  43. package/dist/web/use-overview-rows.d.ts +1 -1
  44. package/dist/web/used-by-label.d.ts +2 -1
  45. package/dist/web/used-by-label.js +4 -0
  46. package/package.json +8 -8
  47. package/src/changes.json +6 -0
@@ -25,3 +25,4 @@ export declare const DocumentDesignerCells: {
25
25
  readonly overriddenBy: "document-designer-overridden-by-cell";
26
26
  };
27
27
  export declare const DEFAULT_TEMPLATE_LOCALES: readonly string[];
28
+ export declare const TENANT_MAIL_SENDING_SCREEN_ID = "mail-foundation-tenant";
package/dist/constants.js CHANGED
@@ -35,3 +35,5 @@ export const DocumentDesignerCells = {
35
35
  overriddenBy: "document-designer-overridden-by-cell",
36
36
  };
37
37
  export const DEFAULT_TEMPLATE_LOCALES = ["de", "en"];
38
+ // Generated settings screen where a tenant admin picks its mail provider.
39
+ export const TENANT_MAIL_SENDING_SCREEN_ID = "mail-foundation-tenant";
package/dist/i18n.js CHANGED
@@ -143,7 +143,7 @@ export const DOCUMENT_DESIGNER_I18N = {
143
143
  },
144
144
  "documentDesigner.editor.mjmlNotice.tenant": {
145
145
  en: "You see the system layout. Saving creates your own layout for this tenant.",
146
- de: "Sie sehen das System-Layout. Speichern legt ein eigenes Layout für Ihren Mandanten an.",
146
+ de: "Du siehst das System-Layout. Speichern legt ein eigenes Layout für deinen Mandanten an.",
147
147
  },
148
148
  "documentDesigner.editor.origin.none": {
149
149
  en: "There is no layout yet.",
@@ -151,7 +151,7 @@ export const DOCUMENT_DESIGNER_I18N = {
151
151
  },
152
152
  "documentDesigner.editor.origin.system": {
153
153
  en: "You are looking at the system layout. Saving creates a customisation for your tenant.",
154
- de: "Sie sehen das System-Layout. Speichern legt eine Anpassung für Ihren Mandanten an.",
154
+ de: "Du siehst das System-Layout. Speichern legt eine Anpassung für deinen Mandanten an.",
155
155
  },
156
156
  "documentDesigner.editor.origin.tenant.mails": {
157
157
  en: "Overrides system layout v{version}. Applies to {count} mail templates of this tenant.",
@@ -183,7 +183,7 @@ export const DOCUMENT_DESIGNER_I18N = {
183
183
  },
184
184
  "documentDesigner.editor.diff.identical": {
185
185
  en: "Your layout matches the system layout.",
186
- de: "Ihr Layout entspricht dem System-Layout.",
186
+ de: "Dein Layout entspricht dem System-Layout.",
187
187
  },
188
188
  "documentDesigner.editor.reset.open": {
189
189
  en: "Reset",
@@ -195,7 +195,7 @@ export const DOCUMENT_DESIGNER_I18N = {
195
195
  },
196
196
  "documentDesigner.editor.reset.description": {
197
197
  en: "Your own layout is removed and the system layout applies again.",
198
- de: "Ihr eigenes Layout wird entfernt, danach gilt wieder das System-Layout.",
198
+ de: "Dein eigenes Layout wird entfernt, danach gilt wieder das System-Layout.",
199
199
  },
200
200
  "documentDesigner.editor.reset.confirm": { en: "Reset", de: "Zurücksetzen" },
201
201
  "documentDesigner.editor.region.header": { en: "Header", de: "Kopf" },
@@ -235,7 +235,7 @@ export const DOCUMENT_DESIGNER_I18N = {
235
235
  },
236
236
  "documentDesigner.editor.dirtySwitch.description": {
237
237
  en: "Switching the layout kind discards your unsaved changes.",
238
- de: "Beim Wechsel der Layout-Art gehen Ihre ungespeicherten Änderungen verloren.",
238
+ de: "Beim Wechsel der Layout-Art gehen deine ungespeicherten Änderungen verloren.",
239
239
  },
240
240
  "documentDesigner.editor.dirtySwitch.confirm": {
241
241
  en: "Discard and switch",
@@ -283,7 +283,7 @@ export const DOCUMENT_DESIGNER_I18N = {
283
283
  },
284
284
  "documentDesigner.mail.origin.tenantOnly": {
285
285
  en: "This template exists only for your tenant.",
286
- de: "Diese Vorlage gibt es nur für Ihren Mandanten.",
286
+ de: "Diese Vorlage gibt es nur für deinen Mandanten.",
287
287
  },
288
288
  "documentDesigner.mail.origin.activeSince": {
289
289
  en: "Active since {when}.",
@@ -303,7 +303,7 @@ export const DOCUMENT_DESIGNER_I18N = {
303
303
  },
304
304
  "documentDesigner.mail.origin.system": {
305
305
  en: "You are viewing the system template v{version}. Saving creates a customisation for your tenant.",
306
- de: "Sie sehen die System-Vorlage v{version}. Speichern legt eine Anpassung für Ihren Mandanten an.",
306
+ de: "Du siehst die System-Vorlage v{version}. Speichern legt eine Anpassung für deinen Mandanten an.",
307
307
  },
308
308
  "documentDesigner.mail.origin.platform": {
309
309
  en: "System template v{version}. Saving changes it for every tenant without a customisation.",
@@ -339,7 +339,7 @@ export const DOCUMENT_DESIGNER_I18N = {
339
339
  },
340
340
  "documentDesigner.mail.origin.copyNote": {
341
341
  en: "Copying a system version does not bring images or attachments along; upload them again afterwards.",
342
- de: "Beim Kopieren einer System-Fassung kommen Bilder und Anhänge nicht mit; laden Sie sie danach neu hoch.",
342
+ de: "Beim Kopieren einer System-Fassung kommen Bilder und Anhänge nicht mit; lade sie danach neu hoch.",
343
343
  },
344
344
  "documentDesigner.mail.origin.createBlank": {
345
345
  en: "Start empty",
@@ -347,7 +347,7 @@ export const DOCUMENT_DESIGNER_I18N = {
347
347
  },
348
348
  "documentDesigner.mail.origin.creating": {
349
349
  en: "New version for {language}. It is stored when you save.",
350
- de: "Neue Fassung für {language}. Sie wird erst mit Speichern angelegt.",
350
+ de: "Neue Fassung für {language}. Die Fassung wird erst mit Speichern angelegt.",
351
351
  },
352
352
  "documentDesigner.mail.reset.title": {
353
353
  en: "Reset to the system template?",
@@ -355,7 +355,7 @@ export const DOCUMENT_DESIGNER_I18N = {
355
355
  },
356
356
  "documentDesigner.mail.reset.description": {
357
357
  en: "Your customisation of this template is removed in all languages. The system template applies again.",
358
- de: "Ihre Anpassung dieser Vorlage wird in allen Sprachen entfernt. Danach gilt wieder die System-Vorlage.",
358
+ de: "Deine Anpassung dieser Vorlage wird in allen Sprachen entfernt. Danach gilt wieder die System-Vorlage.",
359
359
  },
360
360
  "documentDesigner.mail.reset.confirm": {
361
361
  en: "Reset",
@@ -509,6 +509,14 @@ export const DOCUMENT_DESIGNER_I18N = {
509
509
  en: "The address comes from the platform's mail delivery.",
510
510
  de: "Die Adresse kommt vom Mail-Versand der Plattform.",
511
511
  },
512
+ "documentDesigner.mail.props.senderHintTenant": {
513
+ en: "The address comes from your organization's email sending settings.",
514
+ de: "Die Adresse kommt aus dem E-Mail-Versand deines Mandanten.",
515
+ },
516
+ "documentDesigner.mail.props.senderSettingsLink": {
517
+ en: "Open email sending",
518
+ de: "E-Mail-Versand öffnen",
519
+ },
512
520
  "documentDesigner.mail.props.replyTo": {
513
521
  en: "Reply to",
514
522
  de: "Antwort an",
@@ -679,7 +687,7 @@ export const DOCUMENT_DESIGNER_I18N = {
679
687
  },
680
688
  "documentDesigner.mail.discard.description": {
681
689
  en: "Your unsaved changes are lost.",
682
- de: "Ihre ungespeicherten Änderungen gehen verloren.",
690
+ de: "Deine ungespeicherten Änderungen gehen verloren.",
683
691
  },
684
692
  "documentDesigner.mail.discard.confirm": {
685
693
  en: "Discard",
@@ -687,7 +695,7 @@ export const DOCUMENT_DESIGNER_I18N = {
687
695
  },
688
696
  "documentDesigner.origin.draftBesideActive": {
689
697
  en: "Version {active} is sent, your draft (version {draft}) is not published yet.",
690
- de: "Verschickt wird Version {active}, Ihr Entwurf (Version {draft}) ist noch nicht veröffentlicht.",
698
+ de: "Verschickt wird Version {active}, dein Entwurf (Version {draft}) ist noch nicht veröffentlicht.",
691
699
  },
692
700
  "documentDesigner.mail.leave.title": {
693
701
  en: "Unsaved changes",
@@ -695,7 +703,7 @@ export const DOCUMENT_DESIGNER_I18N = {
695
703
  },
696
704
  "documentDesigner.mail.leave.description": {
697
705
  en: "Save your changes as a draft, discard them or stay on this page.",
698
- de: "Speichern Sie Ihre Änderungen als Entwurf, verwerfen Sie sie oder bleiben Sie auf dieser Seite.",
706
+ de: "Speichere deine Änderungen als Entwurf, verwirf sie oder bleib auf dieser Seite.",
699
707
  },
700
708
  "documentDesigner.mail.leave.save": {
701
709
  en: "Save as draft",
@@ -791,7 +799,7 @@ export const DOCUMENT_DESIGNER_I18N = {
791
799
  },
792
800
  "documentDesigner.mail.testMail.ownAddress": {
793
801
  en: "Your address",
794
- de: "Ihre Adresse",
802
+ de: "Deine Adresse",
795
803
  },
796
804
  "documentDesigner.mail.testMail.changeAddress": {
797
805
  en: "Change address",
@@ -815,11 +823,11 @@ export const DOCUMENT_DESIGNER_I18N = {
815
823
  },
816
824
  "documentDesigner.mail.testMail.hint": {
817
825
  en: "Sent through the tenant's normal delivery path to your own address.",
818
- de: "Versand über den normalen Zustellweg des Mandanten an Ihre eigene Adresse.",
826
+ de: "Versand über den normalen Zustellweg des Mandanten an deine eigene Adresse.",
819
827
  },
820
828
  "documentDesigner.mail.testMail.sent": {
821
829
  en: "Test mail sent to your address: {subject}",
822
- de: "Test-Mail an Ihre Adresse gesendet: {subject}",
830
+ de: "Test-Mail an deine Adresse gesendet: {subject}",
823
831
  },
824
832
  "documentDesigner.mail.testMail.rateLimited": {
825
833
  en: "Too many test mails, please try again later.",
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { DEFAULT_TEMPLATE_LOCALES, DOCUMENT_DESIGNER_FEATURE, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, PLATFORM_TEMPLATES_NAV_ID, PLATFORM_TEMPLATES_SCREEN_ID, TEMPLATE_EDITOR_SCREENS, TEMPLATES_NAV_ID, TEMPLATES_SCREEN_ID, type TemplateEditorKind, } from "./constants.js";
2
- export { createDocumentDesignerFeature } from "./feature.js";
1
+ export { DEFAULT_TEMPLATE_LOCALES, DOCUMENT_DESIGNER_FEATURE, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, PLATFORM_TEMPLATES_NAV_ID, PLATFORM_TEMPLATES_SCREEN_ID, TEMPLATE_EDITOR_SCREENS, TEMPLATES_NAV_ID, TEMPLATES_SCREEN_ID, type TemplateEditorKind, } from "./constants.ts";
2
+ export { createDocumentDesignerFeature } from "./feature.ts";
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import { type EditorOrigin } from "./editor-header.js";
2
+ import { type EditorOrigin } from "./editor-header.tsx";
3
3
  export type InfoBarTone = "active" | "muted" | "warn";
4
4
  export type FooterTone = "bad" | "warn" | "ok";
5
5
  export declare function EditorFrame({ testId, children, }: {
package/dist/web/i18n.js CHANGED
@@ -76,7 +76,7 @@ export const documentDesignerTranslations = {
76
76
  "documentDesigner.canvas.caption": 'Seite 1 · {format} · Die Vorschau zeigt Seitengrenzen nur ungefähr, verbindlich ist "PDF erzeugen".',
77
77
  "documentDesigner.canvas.fromLayout": "aus Layout „{layout}“",
78
78
  "documentDesigner.error.resourceTooLarge": "Das Bild ist zu groß. Bitte eine kleinere Datei wählen.",
79
- "documentDesigner.error.resourceUnsupportedImage": "Dieses Bildformat wird nicht unterstützt. Nutzen Sie PNG, JPEG, GIF oder WebP.",
79
+ "documentDesigner.error.resourceUnsupportedImage": "Dieses Bildformat wird nicht unterstützt. Nutze PNG, JPEG, GIF oder WebP.",
80
80
  "documentDesigner.locale": "Sprache",
81
81
  "documentDesigner.localeDialog.cancel": "Abbrechen",
82
82
  "documentDesigner.localeDialog.description": "Die Änderungen in dieser Sprache sind noch nicht gespeichert.",
@@ -111,7 +111,7 @@ export const documentDesignerTranslations = {
111
111
  "documentDesigner.preview.renderFailed": "Das PDF konnte nicht angezeigt werden.",
112
112
  "documentDesigner.preview.stale": "Die Vorschau ist veraltet. Bitte das PDF neu erzeugen.",
113
113
  "documentDesigner.resources.empty": "Noch keine Bilder.",
114
- "documentDesigner.resources.saveFirst": "Speichern Sie die Vorlage zuerst, danach können Bilder hochgeladen werden.",
114
+ "documentDesigner.resources.saveFirst": "Speichere die Vorlage zuerst, danach können Bilder hochgeladen werden.",
115
115
  "documentDesigner.resources.title": "Ressourcen",
116
116
  "documentDesigner.resources.upload": "+ Hochladen",
117
117
  "documentDesigner.settings.fileName": "Dateiname",
@@ -1,6 +1,6 @@
1
- export { type DocumentDesignerClientOptions, documentDesignerClient, PdfTemplateEditorScreen, } from "./client-plugin.js";
2
- export { composeLayoutHtml, type LayoutRegionValues } from "./compose-layout.js";
3
- export { documentDesignerTranslations } from "./i18n.js";
4
- export { PdfTemplateEditor, type PdfTemplateEditorProps } from "./pdf-template-editor.js";
5
- export { type PreviewRecordPicker, type PreviewRecordPickerProps, PreviewRecordPickerProvider, } from "./preview-record-picker.js";
6
- export { usageLabel, usedByLabel } from "./used-by-label.js";
1
+ export { type DocumentDesignerClientOptions, documentDesignerClient, PdfTemplateEditorScreen, } from "./client-plugin.tsx";
2
+ export { composeLayoutHtml, type LayoutRegionValues } from "./compose-layout.ts";
3
+ export { documentDesignerTranslations } from "./i18n.ts";
4
+ export { PdfTemplateEditor, type PdfTemplateEditorProps } from "./pdf-template-editor.tsx";
5
+ export { type PreviewRecordPicker, type PreviewRecordPickerProps, PreviewRecordPickerProvider, } from "./preview-record-picker.tsx";
6
+ export { usageLabel, usedByLabel } from "./used-by-label.ts";
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
- import type { LayoutKind, LayoutSourceView, TenantOverrideCounts } from "./layout-types.js";
3
- import type { OverviewRowView } from "./overview-row.js";
2
+ import type { LayoutKind, LayoutSourceView, TenantOverrideCounts } from "./layout-types.ts";
3
+ import type { OverviewRowView } from "./overview-row.ts";
4
4
  export declare function LayoutEditorBody({ kind, slug, isSystemAdmin, source, overviewRows, usedByCount, overrides, onKindChange, onReload, }: {
5
5
  readonly kind: LayoutKind;
6
6
  readonly slug: string;
@@ -1,7 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
- import { type LayoutAppearance } from "./layout-meta.js";
3
- import type { BrandingView, LayoutKind, LayoutValidation } from "./layout-types.js";
4
- import type { OverviewRowView } from "./overview-row.js";
2
+ import { type LayoutAppearance } from "./layout-meta.ts";
3
+ import type { BrandingView, LayoutKind, LayoutValidation } from "./layout-types.ts";
4
+ import type { OverviewRowView } from "./overview-row.ts";
5
5
  export declare function BrandingRail({ data, unavailable, }: {
6
6
  readonly data: BrandingView | null;
7
7
  readonly unavailable: boolean;
@@ -1,5 +1,5 @@
1
- import type { LayoutMeta } from "./layout-meta.js";
2
- import type { ServedVersionView } from "./types.js";
1
+ import type { LayoutMeta } from "./layout-meta.ts";
2
+ import type { ServedVersionView } from "./types.ts";
3
3
  export type LayoutKind = "mail-layout" | "pdf-layout";
4
4
  export type ValidationIssue = {
5
5
  readonly path: string;
@@ -1,4 +1,4 @@
1
- import type { LayoutKind } from "./layout-types.js";
2
- import type { OverviewRowView } from "./overview-row.js";
1
+ import type { LayoutKind } from "./layout-types.ts";
2
+ import type { OverviewRowView } from "./overview-row.ts";
3
3
  export declare function layoutUserKind(kind: LayoutKind): "mail" | "document-pdf";
4
4
  export declare function layoutUsers(rows: readonly OverviewRowView[], kind: LayoutKind, layoutSlug: string): readonly OverviewRowView[];
@@ -1,4 +1,4 @@
1
- import type { LinkedResourceView, MailDraft, MailVariable, PlaceholderType, TemplateRowView } from "./mail-types.js";
1
+ import type { LinkedResourceView, MailDraft, MailVariable, PlaceholderType, TemplateRowView } from "./mail-types.ts";
2
2
  export declare const EMPTY_DRAFT: MailDraft;
3
3
  export declare const PLACEHOLDER_SYMBOL: Readonly<Record<PlaceholderType, string>>;
4
4
  export declare const LAYOUT_OWNED_PLACEHOLDERS: ReadonlySet<string>;
@@ -14,6 +14,6 @@ export declare function parseLinkedResources(value: unknown): readonly LinkedRes
14
14
  export declare function uniqueResourceKey(source: string, taken: readonly string[]): string;
15
15
  export declare function lastSlugSegment(slug: string): string;
16
16
  export declare function formatKilobytes(bytes: number): string;
17
- export declare function variableLabelMap(variables: Readonly<Record<string, MailVariable>>): Record<string, string>;
17
+ export declare function variableLabelMap(t: Translate, variables: Readonly<Record<string, MailVariable>>): Record<string, string>;
18
18
  export declare function displayValue(t: (key: string, params?: Readonly<Record<string, unknown>>) => string, value: unknown): string;
19
19
  export {};
@@ -1,3 +1,4 @@
1
+ import { variableLabelText } from "./used-by-label.js";
1
2
  export const EMPTY_DRAFT = {
2
3
  subject: "",
3
4
  previewText: "",
@@ -142,14 +143,15 @@ export function formatKilobytes(bytes) {
142
143
  return `${Math.max(1, Math.round(bytes / 1024))} KB`;
143
144
  }
144
145
  // Labels for the chip editors; list item fields are addressed as "list.item".
145
- export function variableLabelMap(variables) {
146
+ export function variableLabelMap(t, variables) {
146
147
  const labels = {};
147
148
  for (const [name, variable] of Object.entries(variables)) {
148
149
  if (variable.label !== undefined)
149
- labels[name] = variable.label;
150
+ labels[name] = variableLabelText(t, name, variable.label);
150
151
  for (const [itemName, item] of Object.entries(variable.item ?? {})) {
151
- if (item.label !== undefined)
152
- labels[`${name}.${itemName}`] = item.label;
152
+ if (item.label !== undefined) {
153
+ labels[`${name}.${itemName}`] = variableLabelText(t, itemName, item.label);
154
+ }
153
155
  }
154
156
  }
155
157
  return labels;
@@ -1,8 +1,8 @@
1
1
  import { type DocumentResourcePreview } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
2
2
  import type { ReactNode } from "react";
3
- import type { MailMetaField } from "./mail-footer.js";
4
- import type { MailDraft } from "./mail-types.js";
5
- import type { InsertTargets } from "./use-insert-targets.js";
3
+ import type { MailMetaField } from "./mail-footer.ts";
4
+ import type { MailDraft } from "./mail-types.ts";
5
+ import type { InsertTargets } from "./use-insert-targets.ts";
6
6
  export type EditorVocabulary = {
7
7
  readonly names: readonly string[];
8
8
  readonly labels: Readonly<Record<string, string>>;
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import type { MailValidation, TemplateRowView } from "./mail-types.js";
2
+ import type { MailValidation, TemplateRowView } from "./mail-types.ts";
3
3
  export type MailEditorBodyProps = {
4
4
  readonly slug: string;
5
5
  readonly locale: string;
@@ -10,7 +10,7 @@ import { MailConfirmDialogs } from "./mail-dialogs.js";
10
10
  import { buildMeta, isDraftDirty, loadedMailOf, parseJsonRecord, parseLinkedResources, systemLabelMap, variableLabelMap, } from "./mail-draft.js";
11
11
  import { MailEditCenter } from "./mail-edit-center.js";
12
12
  import { footerMessage, metaFieldMessages } from "./mail-footer.js";
13
- import { PlaceholderSection, ResourceSection, SystemPlaceholderSection } from "./mail-left-rail.js";
13
+ import { PlaceholderSection, ResourceSection, SystemPlaceholderSection, } from "./mail-left-rail.js";
14
14
  import { MissingLocaleCanvas } from "./mail-missing-locale.js";
15
15
  import { MailOriginBar } from "./mail-origin-bar.js";
16
16
  import { PdfLinkModal } from "./mail-pdf-link-modal.js";
@@ -158,7 +158,7 @@ export function MailEditorBody(props) {
158
158
  names,
159
159
  labels: {
160
160
  ...systemLabelMap(t, validation?.systemPlaceholders ?? []),
161
- ...variableLabelMap(validation?.variables ?? {}),
161
+ ...variableLabelMap(t, validation?.variables ?? {}),
162
162
  },
163
163
  system: validation?.systemPlaceholders ?? [],
164
164
  };
@@ -173,7 +173,7 @@ export function MailEditorBody(props) {
173
173
  return (_jsxs(EditorFrame, { testId: "document-designer-mail-editor", children: [_jsx(EditorHeader, { title: title, stored: row === null ? null : { status: row.status, version: row.version }, actions: { busy, canSave, canPublish, onSave: saveDraft, onPublish: publish }, children: _jsx(EditorModeSwitch, { testIdPrefix: "mail", mode: mode, onMode: setMode }) }), mode === "edit" && (_jsx(MailOriginBar, { row: row, systemVersion: props.systemVersion, locale: locale, isSystemAdmin: isSystemAdmin, creating: creating, controls: controls, onReload: () => {
174
174
  setResetError(null);
175
175
  void onReload();
176
- }, onResetError: setResetError })), testMail.state.kind === "sent" && (_jsx(Banner, { variant: "primary", testId: "mail-test-sent", children: t("documentDesigner.mail.testMail.sent", { subject: testMail.state.subject }) })), testMail.state.kind === "error" && (_jsx(Banner, { variant: "error", testId: "mail-test-error", children: testMail.state.text })), resetError !== null && (_jsx(Banner, { variant: "error", testId: "mail-reset-error", children: resetError })), resourceError !== null && (_jsx(Banner, { variant: "error", testId: "mail-resource-error", children: resourceError })), mode === "edit" ? (_jsx(EditorBody, { left: _jsxs(_Fragment, { children: [_jsx(PlaceholderSection, { validation: railValidation, onInsert: insert.insertPlaceholder }), _jsx(SystemPlaceholderSection, { validation: railValidation, onInsert: insert.insertPlaceholder }), _jsx(ResourceSection, { resources: resources, pdfTitles: pdfTitles, canUpload: uploadTemplateId !== null, onUpload: () => setUploadOpen(true), onInsert: insert.insertResource, onRemove: (key) => setDialog({ kind: "remove", key }) })] }), center: locked ? (_jsx(MissingLocaleCanvas, { slug: slug, locale: locale, fallbackLocale: props.fallbackLocale, copyScope: isSystemAdmin ? "system" : "tenant", onCreateBlank: () => setCreating(true), onCopied: () => void onReload() })) : (_jsx(MailEditCenter, { draft: draft, vocabulary: vocabulary, resourcePreviews: resourcePreviews, insert: insert, readOnly: false, fieldErrors: fieldErrors, onChange: update }, editorEpoch)), right: _jsxs(_Fragment, { children: [_jsx(PropertiesSection, { draft: draft, layouts: layouts.rows, onChange: update }), _jsx(ChromeSection, { draft: draft, vocabulary: vocabulary, insert: insert, onChange: update }), _jsx(AttachmentSection, { attachments: attachments, pdfTitles: pdfTitles, canEdit: uploadTemplateId !== null, onAddPdf: () => setPdfOpen(true), onRemove: (key) => setDialog({ kind: "remove", key }) }), _jsx(UsageSection, { slug: slug }), _jsx(VersionsSection, { rowId: row?.id ?? null, currentVersion: row?.version ?? 0, currentStatus: row?.status ?? "draft" })] }) })) : (_jsx(MailPreviewMode, { payload: {
176
+ }, onResetError: setResetError })), testMail.state.kind === "sent" && (_jsx(Banner, { variant: "primary", testId: "mail-test-sent", children: t("documentDesigner.mail.testMail.sent", { subject: testMail.state.subject }) })), testMail.state.kind === "error" && (_jsx(Banner, { variant: "error", testId: "mail-test-error", children: testMail.state.text })), resetError !== null && (_jsx(Banner, { variant: "error", testId: "mail-reset-error", children: resetError })), resourceError !== null && (_jsx(Banner, { variant: "error", testId: "mail-resource-error", children: resourceError })), mode === "edit" ? (_jsx(EditorBody, { left: _jsxs(_Fragment, { children: [_jsx(PlaceholderSection, { validation: railValidation, onInsert: insert.insertPlaceholder }), _jsx(SystemPlaceholderSection, { validation: railValidation, onInsert: insert.insertPlaceholder }), _jsx(ResourceSection, { resources: resources, pdfTitles: pdfTitles, canUpload: uploadTemplateId !== null, onUpload: () => setUploadOpen(true), onInsert: insert.insertResource, onRemove: (key) => setDialog({ kind: "remove", key }) })] }), center: locked ? (_jsx(MissingLocaleCanvas, { slug: slug, locale: locale, fallbackLocale: props.fallbackLocale, copyScope: isSystemAdmin ? "system" : "tenant", onCreateBlank: () => setCreating(true), onCopied: () => void onReload() })) : (_jsx(MailEditCenter, { draft: draft, vocabulary: vocabulary, resourcePreviews: resourcePreviews, insert: insert, readOnly: false, fieldErrors: fieldErrors, onChange: update }, editorEpoch)), right: _jsxs(_Fragment, { children: [_jsx(PropertiesSection, { slug: slug, draft: draft, layouts: layouts.rows, onChange: update }), _jsx(ChromeSection, { draft: draft, vocabulary: vocabulary, insert: insert, onChange: update }), _jsx(AttachmentSection, { attachments: attachments, pdfTitles: pdfTitles, canEdit: uploadTemplateId !== null, onAddPdf: () => setPdfOpen(true), onRemove: (key) => setDialog({ kind: "remove", key }) }), _jsx(UsageSection, { slug: slug }), _jsx(VersionsSection, { rowId: row?.id ?? null, currentVersion: row?.version ?? 0, currentStatus: row?.status ?? "draft" })] }) })) : (_jsx(MailPreviewMode, { payload: {
177
177
  slug,
178
178
  kind: "mail",
179
179
  locale,
@@ -1,4 +1,4 @@
1
- import type { MailValidation } from "./mail-types.js";
1
+ import type { MailValidation } from "./mail-types.ts";
2
2
  type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
3
3
  export declare function footerMessage(t: Translate, validation: MailValidation, previewMode: boolean): {
4
4
  readonly tone: "bad" | "warn" | "ok";
@@ -1,10 +1,11 @@
1
+ import { variableLabelText } from "./used-by-label.js";
1
2
  function issueText(t, issue) {
2
3
  const translated = t(issue.i18nKey, issue.params);
3
4
  return translated === issue.i18nKey ? issue.code : translated;
4
5
  }
5
- function missingLabels(validation) {
6
+ function missingLabels(t, validation) {
6
7
  return validation.missingRequired
7
- .map((name) => validation.variables[name]?.label ?? name)
8
+ .map((name) => variableLabelText(t, name, validation.variables[name]?.label))
8
9
  .join(", ");
9
10
  }
10
11
  // The footer sentence: missing required placeholders first, then other blocking issues,
@@ -15,7 +16,7 @@ export function footerMessage(t, validation, previewMode) {
15
16
  tone: "bad",
16
17
  text: t(previewMode
17
18
  ? "documentDesigner.mail.footer.missingPreview"
18
- : "documentDesigner.mail.footer.missing", { names: missingLabels(validation) }),
19
+ : "documentDesigner.mail.footer.missing", { names: missingLabels(t, validation) }),
19
20
  };
20
21
  }
21
22
  const blocking = [...validation.issues, ...validation.publishIssues][0];
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import type { LinkedResourceView, MailValidation } from "./mail-types.js";
2
+ import type { LinkedResourceView, MailValidation } from "./mail-types.ts";
3
3
  export type PlaceholderSource = Pick<MailValidation, "variables" | "used" | "missingRequired" | "systemPlaceholders">;
4
4
  export declare function PlaceholderSection({ validation, onInsert, idPrefix, }: {
5
5
  readonly validation: PlaceholderSource | null;
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
4
4
  import { LAYOUT_OWNED_PLACEHOLDERS, PLACEHOLDER_SYMBOL, SYSTEM_PLACEHOLDER_TYPE, systemPlaceholderLabel, } from "./mail-draft.js";
5
5
  import { RailHint, RailRow, RailSection } from "./rail.js";
6
+ import { variableLabelText } from "./used-by-label.js";
6
7
  function placeholderStatus(t, validation, name) {
7
8
  if (validation.missingRequired.includes(name)) {
8
9
  return { text: t("documentDesigner.mail.placeholder.missing"), tone: "bad" };
@@ -15,7 +16,7 @@ export function PlaceholderSection({ validation, onInsert, idPrefix = "mail", })
15
16
  return (_jsxs(RailSection, { title: t("documentDesigner.mail.placeholder.title"), testId: `${idPrefix}-editor-placeholders`, children: [validation !== null &&
16
17
  entries.map(([name, variable]) => {
17
18
  const status = placeholderStatus(t, validation, name);
18
- return (_jsx(RailRow, { testId: `${idPrefix}-placeholder-${name}`, symbol: PLACEHOLDER_SYMBOL[variable.type], label: variable.label ?? name, required: variable.required === true, bad: validation.missingRequired.includes(name), trailing: status.text, trailingTone: status.tone, onPress: () => onInsert(name) }, name));
19
+ return (_jsx(RailRow, { testId: `${idPrefix}-placeholder-${name}`, symbol: PLACEHOLDER_SYMBOL[variable.type], label: variableLabelText(t, name, variable.label), nameHint: name, required: variable.required === true, bad: validation.missingRequired.includes(name), trailing: status.text, trailingTone: status.tone, onPress: () => onInsert(name) }, name));
19
20
  }), _jsx(RailHint, { children: t("documentDesigner.mail.placeholder.hint") })] }));
20
21
  }
21
22
  export function SystemPlaceholderSection({ validation, onInsert, idPrefix = "mail", isLayoutOwned = (name) => LAYOUT_OWNED_PLACEHOLDERS.has(name), }) {
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import type { TemplateRowView } from "./mail-types.js";
2
+ import type { TemplateRowView } from "./mail-types.ts";
3
3
  type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
4
4
  export declare function languageName(t: Translate, locale: string): string;
5
5
  export declare function MailOriginBar({ row, systemVersion, locale, isSystemAdmin, creating, controls, onReload, onResetError, }: {
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import type { OverviewRowView } from "./overview-row.js";
2
+ import type { OverviewRowView } from "./overview-row.ts";
3
3
  export declare function PdfLinkModal({ open, onOpenChange, templates, ...form }: {
4
4
  readonly open: boolean;
5
5
  readonly onOpenChange: (open: boolean) => void;
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
- import type { MailPreview, MailValidation } from "./mail-types.js";
3
- import type { OwnAddress } from "./use-own-address.js";
2
+ import type { MailPreview, MailValidation } from "./mail-types.ts";
3
+ import type { OwnAddress } from "./use-own-address.ts";
4
4
  export type TestMailControls = {
5
5
  readonly pending: boolean;
6
6
  readonly onSend: () => void;
@@ -5,6 +5,7 @@ import { useState } from "react";
5
5
  import { displayValue } from "./mail-draft.js";
6
6
  import { usePreviewRecordPicker } from "./preview-record-picker.js";
7
7
  import { RailField, RailHint, RailRow, RailSection } from "./rail.js";
8
+ import { variableLabelText } from "./used-by-label.js";
8
9
  const noop = () => undefined;
9
10
  // Check params list names as arrays; the message templates expect one string.
10
11
  function checkText(t, check) {
@@ -21,7 +22,7 @@ function SampleValues({ preview, validation, picked, }) {
21
22
  const names = Object.keys(validation?.variables ?? values);
22
23
  return (_jsx(_Fragment, { children: names.map((name) => {
23
24
  const missing = validation?.missingRequired.includes(name) === true;
24
- return (_jsx(RailRow, { testId: `mail-sample-${name}`, label: validation?.variables[name]?.label ?? name, bad: missing, trailing: missing
25
+ return (_jsx(RailRow, { testId: `mail-sample-${name}`, label: variableLabelText(t, name, validation?.variables[name]?.label), nameHint: name, bad: missing, trailing: missing
25
26
  ? t("documentDesigner.mail.preview.missingInContent")
26
27
  : displayValue(t, values[name]) }, name));
27
28
  }) }));
@@ -1,8 +1,8 @@
1
1
  import { type ReactNode } from "react";
2
- import type { EditorOrigin } from "./editor-header.js";
3
- import { type TestMailControls } from "./mail-preview-side.js";
4
- import type { MailValidation } from "./mail-types.js";
5
- import type { OwnAddress } from "./use-own-address.js";
2
+ import type { EditorOrigin } from "./editor-header.tsx";
3
+ import { type TestMailControls } from "./mail-preview-side.tsx";
4
+ import type { MailValidation } from "./mail-types.ts";
5
+ import type { OwnAddress } from "./use-own-address.ts";
6
6
  export type PreviewPayload = {
7
7
  readonly slug: string;
8
8
  readonly kind: "mail";
@@ -1,8 +1,8 @@
1
1
  import type { ReactNode } from "react";
2
- import type { EditorVocabulary } from "./mail-edit-center.js";
3
- import type { LinkedResourceView, MailDraft } from "./mail-types.js";
4
- import type { OverviewRowView } from "./overview-row.js";
5
- import type { InsertTargets } from "./use-insert-targets.js";
2
+ import type { EditorVocabulary } from "./mail-edit-center.tsx";
3
+ import type { LinkedResourceView, MailDraft } from "./mail-types.ts";
4
+ import type { OverviewRowView } from "./overview-row.ts";
5
+ import type { InsertTargets } from "./use-insert-targets.ts";
6
6
  export declare function layoutOptionLabel(t: (key: string, params?: Readonly<Record<string, unknown>>) => string, layout: OverviewRowView): string;
7
7
  export declare function AttachmentSection({ attachments, pdfTitles, canEdit, onAddPdf, onRemove, }: {
8
8
  readonly attachments: readonly LinkedResourceView[];
@@ -11,7 +11,8 @@ export declare function AttachmentSection({ attachments, pdfTitles, canEdit, onA
11
11
  readonly onAddPdf: () => void;
12
12
  readonly onRemove: (key: string) => void;
13
13
  }): ReactNode;
14
- export declare function PropertiesSection({ draft, layouts, onChange, }: {
14
+ export declare function PropertiesSection({ slug, draft, layouts, onChange, }: {
15
+ readonly slug: string;
15
16
  readonly draft: MailDraft;
16
17
  readonly layouts: readonly OverviewRowView[];
17
18
  readonly onChange: (patch: Partial<MailDraft>) => void;
@@ -1,11 +1,34 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // @runtime client
3
3
  import { MAIL_TONES, PlaceholderLineEditor, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
- import { usePrimitives, useQuery, useTranslation } from "@cosmicdrift/kumiko-renderer";
4
+ import { useAppFeatures, useNav, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
5
  import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
6
- import { DEFAULT_LAYOUT_SLUG } from "../constants.js";
6
+ import { DEFAULT_LAYOUT_SLUG, TENANT_MAIL_SENDING_SCREEN_ID } from "../constants.js";
7
7
  import { RailField, RailHint, RailRow, RailSection } from "./rail.js";
8
8
  import { usageParts } from "./used-by-label.js";
9
+ function useTemplateUsage(slug) {
10
+ const { data } = useQuery(TemplateResolverQueries.usage, {
11
+ slug,
12
+ kind: "mail",
13
+ });
14
+ return data?.usedBy ?? [];
15
+ }
16
+ function useHasScreen(screenId) {
17
+ const features = useAppFeatures();
18
+ return features.some((feature) => feature.screens.some((screen) => screen.id === screenId));
19
+ }
20
+ // A notification routed through the tenant transport sends from the tenant's own provider, so
21
+ // the hint points there instead of to the platform.
22
+ function SenderHint({ slug }) {
23
+ const t = useTranslation();
24
+ const nav = useNav();
25
+ const { Link } = usePrimitives();
26
+ const tenantRouted = useTemplateUsage(slug).some((usage) => usage.tenantTransport === true);
27
+ const hasSettingsScreen = useHasScreen(TENANT_MAIL_SENDING_SCREEN_ID);
28
+ if (!tenantRouted)
29
+ return _jsx(RailHint, { children: t("documentDesigner.mail.props.senderHint") });
30
+ return (_jsxs(RailHint, { children: [t("documentDesigner.mail.props.senderHintTenant"), hasSettingsScreen && (_jsxs(_Fragment, { children: [" ", _jsx(Link, { href: nav.hrefFor({ screenId: TENANT_MAIL_SENDING_SCREEN_ID }), onPress: () => nav.navigate({ screenId: TENANT_MAIL_SENDING_SCREEN_ID }), testId: "mail-sender-settings-link", children: t("documentDesigner.mail.props.senderSettingsLink") })] }))] }));
31
+ }
9
32
  export function layoutOptionLabel(t, layout) {
10
33
  // A layout has no registry title; the slug would read like an id, so name it by kind.
11
34
  const title = layout.title === layout.slug
@@ -31,11 +54,8 @@ function layoutOptions(t, layouts) {
31
54
  }
32
55
  function UsageRows({ slug }) {
33
56
  const t = useTranslation();
34
- const { data } = useQuery(TemplateResolverQueries.usage, {
35
- slug,
36
- kind: "mail",
37
- });
38
- return (_jsx(_Fragment, { children: (data?.usedBy ?? []).map((usage) => {
57
+ const usedBy = useTemplateUsage(slug);
58
+ return (_jsx(_Fragment, { children: usedBy.map((usage) => {
39
59
  const parts = usageParts(t, usage);
40
60
  return (_jsx(RailRow, { label: t("documentDesigner.mail.usage.line", parts), testId: "mail-usage-row" }, `${usage.feature}:${usage.notification}`));
41
61
  }) }));
@@ -53,12 +73,12 @@ export function AttachmentSection({ attachments, pdfTitles, canEdit, onAddPdf, o
53
73
  ? attachment.fileName
54
74
  : t("documentDesigner.mail.attachments.file"), action: _jsx(Button, { variant: "danger-ghost", size: "sm", onClick: () => onRemove(attachment.key), testId: `mail-attachment-remove-${attachment.key}`, children: t("documentDesigner.mail.resource.remove") }) }, attachment.key))), _jsx(RailHint, { children: t("documentDesigner.mail.attachments.hint") })] }));
55
75
  }
56
- export function PropertiesSection({ draft, layouts, onChange, }) {
76
+ export function PropertiesSection({ slug, draft, layouts, onChange, }) {
57
77
  const t = useTranslation();
58
78
  const { Field, Input } = usePrimitives();
59
79
  const layoutValue = draft.layoutSlug === "" ? DEFAULT_LAYOUT_SLUG : draft.layoutSlug;
60
80
  const categoryValue = draft.category === "" ? "transactional" : draft.category;
61
- return (_jsxs(RailSection, { title: t("documentDesigner.mail.props.title"), testId: "mail-editor-properties", children: [_jsx(RailField, { children: _jsx(Field, { id: "mail-layout", label: t("documentDesigner.mail.props.layout"), children: _jsx(Input, { kind: "select", id: "mail-layout", name: "mail-layout", value: layoutValue, display: "dropdown", onChange: (next) => onChange({ layoutSlug: next }), options: layoutOptions(t, layouts) }) }) }), _jsx(RailField, { children: _jsx(Field, { id: "mail-sender", label: t("documentDesigner.mail.props.sender"), children: _jsx(Input, { kind: "text", id: "mail-sender", name: "mail-sender", testId: "mail-sender", value: draft.senderName, onChange: (next) => onChange({ senderName: next }), placeholder: t("documentDesigner.mail.props.senderPlaceholder") }) }) }), _jsx(RailHint, { children: t("documentDesigner.mail.props.senderHint") }), _jsx(RailField, { children: _jsx(Field, { id: "mail-reply-to", label: t("documentDesigner.mail.props.replyTo"), children: _jsx(Input, { kind: "email", id: "mail-reply-to", name: "mail-reply-to", testId: "mail-reply-to", value: draft.replyTo, onChange: (next) => onChange({ replyTo: next }) }) }) }), _jsx(RailField, { children: _jsx(Field, { id: "mail-category", label: t("documentDesigner.mail.props.category"), children: _jsx(Input, { kind: "select", id: "mail-category", name: "mail-category", value: categoryValue, display: "dropdown", onChange: (next) => onChange({ category: next === "marketing" ? "marketing" : "transactional" }), options: [
81
+ return (_jsxs(RailSection, { title: t("documentDesigner.mail.props.title"), testId: "mail-editor-properties", children: [_jsx(RailField, { children: _jsx(Field, { id: "mail-layout", label: t("documentDesigner.mail.props.layout"), children: _jsx(Input, { kind: "select", id: "mail-layout", name: "mail-layout", value: layoutValue, display: "dropdown", onChange: (next) => onChange({ layoutSlug: next }), options: layoutOptions(t, layouts) }) }) }), _jsx(RailField, { children: _jsx(Field, { id: "mail-sender", label: t("documentDesigner.mail.props.sender"), children: _jsx(Input, { kind: "text", id: "mail-sender", name: "mail-sender", testId: "mail-sender", value: draft.senderName, onChange: (next) => onChange({ senderName: next }), placeholder: t("documentDesigner.mail.props.senderPlaceholder") }) }) }), _jsx(SenderHint, { slug: slug }), _jsx(RailField, { children: _jsx(Field, { id: "mail-reply-to", label: t("documentDesigner.mail.props.replyTo"), children: _jsx(Input, { kind: "email", id: "mail-reply-to", name: "mail-reply-to", testId: "mail-reply-to", value: draft.replyTo, onChange: (next) => onChange({ replyTo: next }) }) }) }), _jsx(RailField, { children: _jsx(Field, { id: "mail-category", label: t("documentDesigner.mail.props.category"), children: _jsx(Input, { kind: "select", id: "mail-category", name: "mail-category", value: categoryValue, display: "dropdown", onChange: (next) => onChange({ category: next === "marketing" ? "marketing" : "transactional" }), options: [
62
82
  { value: "transactional", label: t("documentDesigner.mail.props.transactional") },
63
83
  { value: "marketing", label: t("documentDesigner.mail.props.marketing") },
64
84
  ] }) }) }), _jsx(RailHint, { children: t("documentDesigner.mail.props.categoryHint") })] }));
@@ -1,5 +1,5 @@
1
- import type { ValidationIssue } from "./layout-types.js";
2
- import type { ServedVersionView } from "./types.js";
1
+ import type { ValidationIssue } from "./layout-types.ts";
2
+ import type { ServedVersionView } from "./types.ts";
3
3
  export type PlaceholderType = "text" | "money" | "date" | "percent" | "link" | "list";
4
4
  export type MailVariable = {
5
5
  readonly type: PlaceholderType;
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import type { LayoutKind, LayoutSourceView, TemplateDiff, TenantOverrideCounts } from "./layout-types.js";
2
+ import type { LayoutKind, LayoutSourceView, TemplateDiff, TenantOverrideCounts } from "./layout-types.ts";
3
3
  export declare function DiffView({ data, mode, }: {
4
4
  readonly data: TemplateDiff;
5
5
  readonly mode?: "diff" | "full";
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import { type TenantOverrides } from "./types.js";
2
+ import { type TenantOverrides } from "./types.ts";
3
3
  export type OverridesDrawerProps = {
4
4
  readonly open: boolean;
5
5
  readonly onOpenChange: (open: boolean) => void;
@@ -1,4 +1,4 @@
1
- import type { TemplateEditorKind } from "../constants.js";
1
+ import type { TemplateEditorKind } from "../constants.ts";
2
2
  export type OverviewUsageView = {
3
3
  readonly feature: string;
4
4
  readonly notification: string;
@@ -1,7 +1,7 @@
1
1
  import { type DocumentEditorHandle, type DocumentResourcePreview } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
2
2
  import { type ReactNode } from "react";
3
- import { type PdfMetaDraft } from "./pdf-meta.js";
4
- import type { LayoutRegionsHtml } from "./types.js";
3
+ import { type PdfMetaDraft } from "./pdf-meta.ts";
4
+ import type { LayoutRegionsHtml } from "./types.ts";
5
5
  export type PageCanvasProps = {
6
6
  readonly meta: PdfMetaDraft;
7
7
  readonly content: string;
@@ -1,5 +1,5 @@
1
- import type { FooterTone } from "./editor-shell.js";
2
- import type { ValidationResult } from "./types.js";
1
+ import type { FooterTone } from "./editor-shell.tsx";
2
+ import type { ValidationResult } from "./types.ts";
3
3
  type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
4
4
  export type GeneratedProbe = {
5
5
  readonly pages: number;
@@ -1,3 +1,4 @@
1
+ import { variableLabelText } from "./used-by-label.js";
1
2
  function formatSize(bytes) {
2
3
  return bytes < 1024 * 1024
3
4
  ? `${Math.max(1, Math.round(bytes / 1024))} KB`
@@ -8,7 +9,7 @@ function formatSize(bytes) {
8
9
  export function pdfFooterMessage(t, validation, probe) {
9
10
  if (validation.missingRequired.length > 0) {
10
11
  const names = validation.missingRequired
11
- .map((name) => validation.variables[name]?.label ?? name)
12
+ .map((name) => variableLabelText(t, name, validation.variables[name]?.label))
12
13
  .join(", ");
13
14
  return { tone: "bad", text: t("documentDesigner.validation.missingRequired", { name: names }) };
14
15
  }
@@ -115,7 +115,7 @@ export function PdfTemplateEditor({ slug, locale: requestedLocale, onLocaleChang
115
115
  const variables = validation?.variables ?? NO_VARIABLES;
116
116
  const systemVariables = useMemo(() => visibleSystemPlaceholders(validation?.systemPlaceholders ?? []), [validation?.systemPlaceholders]);
117
117
  const systemLabels = useMemo(() => systemLabelMap(t, systemVariables), [t, systemVariables]);
118
- const variableLabels = useMemo(() => ({ ...systemLabels, ...variableLabelMap(variables) }), [systemLabels, variables]);
118
+ const variableLabels = useMemo(() => ({ ...systemLabels, ...variableLabelMap(t, variables) }), [systemLabels, variables, t]);
119
119
  const variableNames = useMemo(() => Object.keys(variables), [variables]);
120
120
  const linkedResources = detail?.linkedResources ?? null;
121
121
  // The editor shows resource images from the stored copies; no extra request is made here.
@@ -13,9 +13,11 @@ declare const TRAILING_TONE: {
13
13
  readonly bad: "text-status-bad";
14
14
  readonly muted: "text-muted-foreground";
15
15
  };
16
- export declare function RailRow({ symbol, label, required, trailing, trailingTone, action, bad, testId, onPress, }: {
16
+ export declare function RailRow({ symbol, label, nameHint, required, trailing, trailingTone, action, bad, testId, onPress, }: {
17
17
  readonly symbol?: string;
18
18
  readonly label: string;
19
+ /** Technical name behind a translated label, shown as tooltip. */
20
+ readonly nameHint?: string;
19
21
  readonly required?: boolean;
20
22
  readonly trailing?: ReactNode;
21
23
  readonly trailingTone?: keyof typeof TRAILING_TONE;
package/dist/web/rail.js CHANGED
@@ -12,8 +12,8 @@ const TRAILING_TONE = {
12
12
  bad: "text-status-bad",
13
13
  muted: "text-muted-foreground",
14
14
  };
15
- export function RailRow({ symbol, label, required, trailing, trailingTone = "muted", action, bad, testId, onPress, }) {
16
- const content = (_jsxs(_Fragment, { children: [symbol !== undefined && (_jsx("span", { className: "inline-flex h-[18px] w-[22px] shrink-0 items-center justify-center rounded-sm bg-muted font-mono text-xs text-muted-foreground", children: symbol })), _jsxs("span", { className: cn("min-w-0 grow break-words text-sm", trailing !== undefined && "max-w-[65%] shrink-0", bad === true && "text-status-bad"), children: [label, required === true && _jsx("span", { className: "font-semibold text-status-bad", children: " *" })] }), trailing !== undefined && (_jsx("span", { className: cn("min-w-0 truncate text-right text-xs", bad === true ? "text-status-bad" : TRAILING_TONE[trailingTone]), title: typeof trailing === "string" ? trailing : undefined, children: trailing }))] }));
15
+ export function RailRow({ symbol, label, nameHint, required, trailing, trailingTone = "muted", action, bad, testId, onPress, }) {
16
+ const content = (_jsxs(_Fragment, { children: [symbol !== undefined && (_jsx("span", { className: "inline-flex h-[18px] w-[22px] shrink-0 items-center justify-center rounded-sm bg-muted font-mono text-xs text-muted-foreground", children: symbol })), _jsxs("span", { className: cn("min-w-0 grow break-words text-sm", trailing !== undefined && "max-w-[65%] shrink-0", bad === true && "text-status-bad"), title: nameHint !== undefined && nameHint !== label ? nameHint : undefined, children: [label, required === true && _jsx("span", { className: "font-semibold text-status-bad", children: " *" })] }), trailing !== undefined && (_jsx("span", { className: cn("min-w-0 truncate text-right text-xs", bad === true ? "text-status-bad" : TRAILING_TONE[trailingTone]), title: typeof trailing === "string" ? trailing : undefined, children: trailing }))] }));
17
17
  const rowClass = "flex min-h-10 min-w-0 grow items-center gap-2 px-3.5 text-left";
18
18
  return (_jsxs("div", { className: cn("flex items-center border-b", bad === true && "bg-status-bad-surface"), "data-testid": testId, "data-bad": bad === true ? "true" : undefined, children: [onPress === undefined ? (_jsx("div", { className: rowClass, children: content })) : (_jsx("button", { type: "button", onClick: onPress, className: cn(rowClass, "hover:bg-muted"), ...(testId !== undefined && { "data-testid": `${testId}-insert` }), children: content })), action !== undefined && _jsx("div", { className: "shrink-0 pr-2", children: action })] }));
19
19
  }
@@ -1,9 +1,9 @@
1
1
  import type { ReactNode } from "react";
2
- import type { EditorVocabulary } from "./mail-edit-center.js";
3
- import type { OverviewRowView } from "./overview-row.js";
4
- import { type PdfMetaDraft } from "./pdf-meta.js";
5
- import { type TemplateStatus } from "./types.js";
6
- import type { InsertTargets } from "./use-insert-targets.js";
2
+ import type { EditorVocabulary } from "./mail-edit-center.tsx";
3
+ import type { OverviewRowView } from "./overview-row.ts";
4
+ import { type PdfMetaDraft } from "./pdf-meta.ts";
5
+ import { type TemplateStatus } from "./types.ts";
6
+ import type { InsertTargets } from "./use-insert-targets.ts";
7
7
  export type VersionInfo = {
8
8
  readonly id: string;
9
9
  readonly version: number;
@@ -1,5 +1,5 @@
1
1
  import type { StatusBadgeTone } from "@cosmicdrift/kumiko-renderer-web";
2
- import type { OverviewRowView } from "./overview-row.js";
2
+ import type { OverviewRowView } from "./overview-row.ts";
3
3
  type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
4
4
  export declare function statusTone(status: OverviewRowView["status"]): StatusBadgeTone;
5
5
  export declare function statusLabel(t: Translate, status: OverviewRowView["status"], version: number): string;
@@ -1,4 +1,4 @@
1
- import type { TemplateListRow, TemplateScope } from "./types.js";
1
+ import type { TemplateListRow, TemplateScope } from "./types.ts";
2
2
  /**
3
3
  * The row the editor works on. A tenant sees its own customisation, or the
4
4
  * system row as the starting point for one; a SystemAdmin edits the system row.
@@ -1,5 +1,5 @@
1
1
  import type { FieldIssue } from "@cosmicdrift/kumiko-headless";
2
- import type { TemplateDiff } from "./layout-types.js";
2
+ import type { TemplateDiff } from "./layout-types.ts";
3
3
  export type TemplateScope = "system" | "tenant";
4
4
  export type TemplateStatus = "draft" | "active" | "archived";
5
5
  export declare const DOCUMENT_PDF_KIND: "document-pdf";
@@ -1,4 +1,4 @@
1
- import type { MailDraft, TemplateRowView } from "./mail-types.js";
1
+ import type { MailDraft, TemplateRowView } from "./mail-types.ts";
2
2
  export type SaveOutcome = "saved" | "saved-not-published" | "failed";
3
3
  export type MailPersistence = {
4
4
  readonly save: (draft: MailDraft, publish: boolean) => Promise<SaveOutcome>;
@@ -1,4 +1,4 @@
1
- import { type OverviewRowView } from "./overview-row.js";
1
+ import { type OverviewRowView } from "./overview-row.ts";
2
2
  export type OverviewRows = {
3
3
  readonly rows: readonly OverviewRowView[];
4
4
  readonly loaded: boolean;
@@ -1,5 +1,6 @@
1
- import type { OverviewRowView, OverviewUsageView } from "./overview-row.js";
1
+ import type { OverviewRowView, OverviewUsageView } from "./overview-row.ts";
2
2
  type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
3
+ export declare function variableLabelText(t: Translate, name: string, label: string | undefined): string;
3
4
  export declare function templateTitle(t: Translate, row: Pick<OverviewRowView, "title" | "slug" | "kind">): string;
4
5
  export declare function usageParts(t: Translate, usage: OverviewUsageView): {
5
6
  readonly feature: string;
@@ -5,6 +5,10 @@ function translateOrRaw(t, key, raw) {
5
5
  const translated = t(key);
6
6
  return translated === key ? raw : translated;
7
7
  }
8
+ // A declared label is an i18n key or plain text; without one the variable name stands in.
9
+ export function variableLabelText(t, name, label) {
10
+ return label === undefined ? name : translateOrRaw(t, label, label);
11
+ }
8
12
  const NOTIFICATION_SLUG = /^([^:]+):notify:([^:]+)$/;
9
13
  // A seeded system mail has no stored title, so its notification label from the
10
14
  // owning feature names it; the slug stays the last resort.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-document-designer",
3
- "version": "0.368.0",
3
+ "version": "0.370.0",
4
4
  "description": "Document designer for Kumiko: template overview, layout editor (mail layout, PDF letterhead) and the PDF template editor.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -19,13 +19,13 @@
19
19
  }
20
20
  },
21
21
  "dependencies": {
22
- "@cosmicdrift/kumiko-bundled-features": "0.368.0",
23
- "@cosmicdrift/kumiko-framework": "0.368.0",
24
- "@cosmicdrift/kumiko-headless": "0.368.0",
25
- "@cosmicdrift/kumiko-renderer": "0.368.0",
26
- "@cosmicdrift/kumiko-renderer-puppeteer-client": "0.368.0",
27
- "@cosmicdrift/kumiko-renderer-web": "0.368.0",
28
- "@cosmicdrift/kumiko-types": "0.368.0",
22
+ "@cosmicdrift/kumiko-bundled-features": "0.370.0",
23
+ "@cosmicdrift/kumiko-framework": "0.370.0",
24
+ "@cosmicdrift/kumiko-headless": "0.370.0",
25
+ "@cosmicdrift/kumiko-renderer": "0.370.0",
26
+ "@cosmicdrift/kumiko-renderer-puppeteer-client": "0.370.0",
27
+ "@cosmicdrift/kumiko-renderer-web": "0.370.0",
28
+ "@cosmicdrift/kumiko-types": "0.370.0",
29
29
  "pdfjs-dist": "6.3.289",
30
30
  "react": "^19.2.6"
31
31
  },
package/src/changes.json CHANGED
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.370.0",
4
+ "type": "fix",
5
+ "title": "Designer addresses the user informally in German, points tenant-routed senders to Email sending and renders chips inside buttons",
6
+ "detail": "All German document designer strings use du like the rest of the framework; the formal locale overrides keep Sie. For a notification with tenantTransport the sender hint in the mail editor says the address comes from the tenant's Email sending settings and links to that screen when the app mounts it; TemplateUsageEntry gains tenantTransport. A placeholder chip inside a button block is now drawn in inverted colors so the button text stays readable in the editor."
7
+ },
2
8
  {
3
9
  "version": "0.365.0",
4
10
  "type": "improvement",