@cosmicdrift/kumiko-document-designer 0.386.0 → 0.388.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.
package/LICENSE CHANGED
@@ -1,57 +1,21 @@
1
- Business Source License 1.1
2
-
3
- Parameters
4
-
5
- Licensor: Marc Frost
6
-
7
- Licensed Work: @cosmicdrift/kumiko-document-designer
8
- The Licensed Work is © 2026 Marc Frost.
9
-
10
- Additional Use Grant:
11
- You may use the Licensed Work in production for any purpose, including
12
- commercially, EXCEPT for the Restricted Use.
13
-
14
- "Restricted Use" is defined as using the Licensed Work to provide a platform
15
- or service to third parties that allows them to host, deploy, or run their
16
- own applications built with the Licensed Work. This includes, but is not
17
- limited to: managed hosting services, software-as-a-service (SaaS) platforms,
18
- platform-as-a-service (PaaS), developer platforms, or any multi-tenant
19
- managed offering of the Licensed Work.
20
-
21
- This restriction does not apply to the Licensor, any entity controlled by,
22
- controlling, or under common control with the Licensor ("Affiliates"), or
23
- contractors acting on their behalf. The Licensor remains free to use the
24
- Licensed Work for any purpose, including for the operation of kumiko.rocks.
25
-
26
- Change Date: 2030-05-05
27
- Change License: Apache License, Version 2.0
28
-
29
-
30
- Terms
31
-
32
- The Licensor hereby grants you the right to copy, modify, create derivative works,
33
- redistribute, and make non-production use of the Licensed Work. The Licensor may
34
- make an Additional Use Grant, above, permitting limited production use.
35
-
36
- Effective on the Change Date, or the fourth anniversary of the first publicly
37
- available distribution of the Licensed Work under this License, whichever comes
38
- first, this License will convert to the Change License.
39
-
40
- This Business Source License governs use of the Licensed Work in all cases, except
41
- as to any use that is explicitly granted in the Additional Use Grant above or
42
- under the Change License after the Change Date.
43
-
44
- If your use of the Licensed Work does not comply with the requirements of this
45
- License, you must cease use of the Licensed Work immediately.
46
-
47
- All copies of the Licensed Work, and all derivative works thereof, must include
48
- this License.
49
-
50
- This License does not grant you any right, title, or interest in any trademark,
51
- logo, or branding of the Licensor, except as required to comply with this License.
52
-
53
- TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
54
- “AS IS” BASIS. LICENSOR DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
55
- WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
56
- TITLE, AND NON-INFRINGEMENT. IN NO EVENT WILL LICENSOR BE LIABLE FOR ANY DAMAGES
57
- ARISING OUT OF OR RELATED TO THIS LICENSE OR THE USE OF THE LICENSED WORK.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marc Frost
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,3 +1,4 @@
1
+ import { TemplateResolverHandlers } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
1
2
  import { i18nKey, } from "@cosmicdrift/kumiko-framework/engine";
2
3
  import { DocumentDesignerCells, TEMPLATE_EDITOR_SCREENS, } from "./web/constants.js";
3
4
  function cell(component) {
@@ -90,11 +91,10 @@ const EDITABLE_KINDS = [
90
91
  "mail-layout",
91
92
  "pdf-layout",
92
93
  ];
93
- const RESET_TO_SYSTEM_HANDLER = "template-resolver:write:reset-to-system";
94
94
  const RESET_LOCKED_OVERRIDE_ACTION = {
95
95
  id: "reset-locked-override",
96
96
  label: i18nKey("documentDesigner.action.resetLockedOverride"),
97
- handler: RESET_TO_SYSTEM_HANDLER,
97
+ handler: TemplateResolverHandlers.resetToSystem,
98
98
  payload: { map: { id: "lockedOverrideId" } },
99
99
  confirm: i18nKey("documentDesigner.action.resetLockedOverride.confirm"),
100
100
  confirmLabel: i18nKey("documentDesigner.action.resetLockedOverride.confirmLabel"),
package/dist/web/cells.js CHANGED
@@ -11,7 +11,7 @@ function withRow(row, render) {
11
11
  }
12
12
  export function TitleCell({ row }) {
13
13
  const t = useTranslation();
14
- return withRow(row, (r) => (_jsxs("div", { children: [_jsx("div", { className: "max-w-48 whitespace-normal font-medium", children: templateTitle(t, r) }), _jsx("span", { className: "block max-w-40 truncate font-mono text-xs text-muted-foreground", title: r.slug, children: r.slug })] })));
14
+ return withRow(row, (r) => (_jsxs("div", { children: [_jsx("div", { className: "max-w-48 whitespace-normal font-medium [overflow-wrap:anywhere]", children: templateTitle(t, r) }), _jsx("span", { className: "block max-w-28 truncate font-mono text-xs text-muted-foreground", title: r.slug, children: r.slug })] })));
15
15
  }
16
16
  const KIND_KEY = {
17
17
  mail: "documentDesigner.kind.mail",
@@ -13,8 +13,14 @@ export const documentDesignerTranslations = {
13
13
  "documentDesigner.localeDialog.title": "Unsaved changes",
14
14
  "documentDesigner.notFound": 'Template "{slug}" was not found.',
15
15
  "documentDesigner.originBar.export": "Export as bundle",
16
- "documentDesigner.originBar.noTotal": "System template for all tenants. {count} tenants have their own version and do not receive changes made here.",
17
- "documentDesigner.originBar.withTotal": "System template for all tenants. {count} of {total} tenants have their own version and do not receive changes made here.",
16
+ "documentDesigner.originBar.noTotal": {
17
+ one: "System template for all tenants. {count} tenant has its own version and does not receive changes made here.",
18
+ other: "System template for all tenants. {count} tenants have their own version and do not receive changes made here.",
19
+ },
20
+ "documentDesigner.originBar.withTotal": {
21
+ one: "System template for all tenants. {count} of {total} tenants has its own version and does not receive changes made here.",
22
+ other: "System template for all tenants. {count} of {total} tenants have their own version and do not receive changes made here.",
23
+ },
18
24
  "documentDesigner.originBar.showOverrides": "Show tenant customisations",
19
25
  "documentDesigner.overrides.action.diff": "Show difference",
20
26
  "documentDesigner.overrides.action.open": "Open",
@@ -27,7 +33,10 @@ export const documentDesignerTranslations = {
27
33
  "documentDesigner.overrides.empty": "No tenant has customised this template. Changes here apply to everyone.",
28
34
  "documentDesigner.overrides.status.active": "Customised",
29
35
  "documentDesigner.overrides.status.draft": "Draft",
30
- "documentDesigner.overrides.summary": "{count} tenants have their own version.",
36
+ "documentDesigner.overrides.summary": {
37
+ one: "{count} tenant has its own version.",
38
+ other: "{count} tenants have their own version.",
39
+ },
31
40
  "documentDesigner.overrides.title": "Tenant customisations",
32
41
  "documentDesigner.overrides.usedBy": "Used by",
33
42
  "documentDesigner.overrides.usedByEmpty": "No registered notification uses this template directly.",
@@ -85,8 +94,14 @@ export const documentDesignerTranslations = {
85
94
  "documentDesigner.localeDialog.title": "Ungespeicherte Änderungen",
86
95
  "documentDesigner.notFound": 'Die Vorlage "{slug}" wurde nicht gefunden.',
87
96
  "documentDesigner.originBar.export": "Als Bundle exportieren",
88
- "documentDesigner.originBar.noTotal": "System-Vorlage für alle Mandanten. {count} Mandanten haben eine eigene Fassung und bekommen Änderungen hier nicht.",
89
- "documentDesigner.originBar.withTotal": "System-Vorlage für alle Mandanten. {count} von {total} Mandanten haben eine eigene Fassung und bekommen Änderungen hier nicht.",
97
+ "documentDesigner.originBar.noTotal": {
98
+ one: "System-Vorlage für alle Mandanten. {count} Mandant hat eine eigene Fassung und bekommt Änderungen hier nicht.",
99
+ other: "System-Vorlage für alle Mandanten. {count} Mandanten haben eine eigene Fassung und bekommen Änderungen hier nicht.",
100
+ },
101
+ "documentDesigner.originBar.withTotal": {
102
+ one: "System-Vorlage für alle Mandanten. {count} von {total} Mandanten hat eine eigene Fassung und bekommt Änderungen hier nicht.",
103
+ other: "System-Vorlage für alle Mandanten. {count} von {total} Mandanten haben eine eigene Fassung und bekommen Änderungen hier nicht.",
104
+ },
90
105
  "documentDesigner.originBar.showOverrides": "Anpassungen der Mandanten anzeigen",
91
106
  "documentDesigner.overrides.action.diff": "Unterschied anzeigen",
92
107
  "documentDesigner.overrides.action.open": "Öffnen",
@@ -99,7 +114,10 @@ export const documentDesignerTranslations = {
99
114
  "documentDesigner.overrides.empty": "Kein Mandant hat diese Vorlage angepasst. Änderungen hier gelten für alle.",
100
115
  "documentDesigner.overrides.status.active": "Angepasst",
101
116
  "documentDesigner.overrides.status.draft": "Entwurf",
102
- "documentDesigner.overrides.summary": "{count} Mandanten haben eine eigene Fassung.",
117
+ "documentDesigner.overrides.summary": {
118
+ one: "{count} Mandant hat eine eigene Fassung.",
119
+ other: "{count} Mandanten haben eine eigene Fassung.",
120
+ },
103
121
  "documentDesigner.overrides.title": "Anpassungen der Mandanten",
104
122
  "documentDesigner.overrides.usedBy": "Wird verwendet von",
105
123
  "documentDesigner.overrides.usedByEmpty": "Keine registrierte Benachrichtigung nutzt diese Vorlage direkt.",
@@ -157,8 +175,14 @@ export const documentDesignerTranslations = {
157
175
  "documentDesigner.localeDialog.title": "Cambios sin guardar",
158
176
  "documentDesigner.notFound": "No se encontró la plantilla «{slug}».",
159
177
  "documentDesigner.originBar.export": "Exportar como paquete",
160
- "documentDesigner.originBar.noTotal": "Plantilla del sistema para todas las organizaciones. {count} organizaciones tienen su propia versión y no reciben los cambios que hagas aquí.",
161
- "documentDesigner.originBar.withTotal": "Plantilla del sistema para todas las organizaciones. {count} de {total} organizaciones tienen su propia versión y no reciben los cambios que hagas aquí.",
178
+ "documentDesigner.originBar.noTotal": {
179
+ one: "Plantilla del sistema para todas las organizaciones. {count} organización tiene su propia versión y no recibe los cambios que hagas aquí.",
180
+ other: "Plantilla del sistema para todas las organizaciones. {count} organizaciones tienen su propia versión y no reciben los cambios que hagas aquí.",
181
+ },
182
+ "documentDesigner.originBar.withTotal": {
183
+ one: "Plantilla del sistema para todas las organizaciones. {count} de {total} organizaciones tiene su propia versión y no recibe los cambios que hagas aquí.",
184
+ other: "Plantilla del sistema para todas las organizaciones. {count} de {total} organizaciones tienen su propia versión y no reciben los cambios que hagas aquí.",
185
+ },
162
186
  "documentDesigner.originBar.showOverrides": "Ver personalizaciones de las organizaciones",
163
187
  "documentDesigner.overrides.action.diff": "Ver diferencias",
164
188
  "documentDesigner.overrides.action.open": "Abrir",
@@ -171,7 +195,10 @@ export const documentDesignerTranslations = {
171
195
  "documentDesigner.overrides.empty": "Ninguna organización ha personalizado esta plantilla. Los cambios que hagas aquí se aplican a todas.",
172
196
  "documentDesigner.overrides.status.active": "Personalizada",
173
197
  "documentDesigner.overrides.status.draft": "Borrador",
174
- "documentDesigner.overrides.summary": "{count} organizaciones tienen su propia versión.",
198
+ "documentDesigner.overrides.summary": {
199
+ one: "{count} organización tiene su propia versión.",
200
+ other: "{count} organizaciones tienen su propia versión.",
201
+ },
175
202
  "documentDesigner.overrides.title": "Personalizaciones de las organizaciones",
176
203
  "documentDesigner.overrides.usedBy": "Se usa en",
177
204
  "documentDesigner.overrides.usedByEmpty": "Ninguna notificación registrada usa esta plantilla directamente.",
@@ -1,8 +1,8 @@
1
- // Mirrors the mail-layout meta of template-resolver (template-meta.ts); the server parses it again on every write and render.
2
- export const LAYOUT_WIDTHS = [560, 600, 640];
3
- export const LAYOUT_FONTS = ["sans", "serif"];
4
- export const LAYOUT_ACCENT_LINES = ["branding", "none"];
5
- export const LAYOUT_BUTTON_STYLES = ["filled-rounded", "filled-square", "outline"];
1
+ import { HEX_COLOR_PATTERN, MAIL_LAYOUT_ACCENT_LINES, MAIL_LAYOUT_BUTTON_STYLES, MAIL_LAYOUT_FONTS, MAIL_LAYOUT_WIDTHS, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
2
+ export const LAYOUT_WIDTHS = MAIL_LAYOUT_WIDTHS;
3
+ export const LAYOUT_FONTS = MAIL_LAYOUT_FONTS;
4
+ export const LAYOUT_ACCENT_LINES = MAIL_LAYOUT_ACCENT_LINES;
5
+ export const LAYOUT_BUTTON_STYLES = MAIL_LAYOUT_BUTTON_STYLES;
6
6
  export const DEFAULT_LAYOUT_APPEARANCE = {
7
7
  width: 600,
8
8
  font: "sans",
@@ -10,9 +10,8 @@ export const DEFAULT_LAYOUT_APPEARANCE = {
10
10
  accentLine: "none",
11
11
  buttonStyle: "filled-rounded",
12
12
  };
13
- const HEX_COLOR = /^#[0-9a-fA-F]{6}$/;
14
13
  export function isHexColor(value) {
15
- return HEX_COLOR.test(value);
14
+ return HEX_COLOR_PATTERN.test(value);
16
15
  }
17
16
  export function appearanceOf(meta) {
18
17
  return { ...DEFAULT_LAYOUT_APPEARANCE, ...meta };
@@ -1,4 +1,4 @@
1
- import type { LinkedResourceView, MailDraft, MailVariable, PlaceholderType, TemplateRowView } from "./mail-types.ts";
1
+ import type { LinkedResourceView, MailDraft, MailVariable, PickedValues, 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>;
@@ -8,6 +8,7 @@ export declare function systemPlaceholderLabel(t: Translate, name: string): stri
8
8
  export declare function labelPlaceholders(line: string, variableLabels: Readonly<Record<string, string>>): string;
9
9
  export declare function systemLabelMap(t: Translate, names: readonly string[]): Readonly<Record<string, string>>;
10
10
  export declare function parseJsonRecord(value: unknown): Readonly<Record<string, unknown>>;
11
+ export declare function pickDeclaredVariables(picked: PickedValues | null, declared: Readonly<Record<string, MailVariable>> | undefined): PickedValues | null;
11
12
  export declare function loadedMailOf(row: TemplateRowView | null): MailDraft;
12
13
  export declare function buildMeta(draft: MailDraft): Record<string, unknown>;
13
14
  export declare function isDraftDirty(current: MailDraft, saved: MailDraft): boolean;
@@ -62,6 +62,12 @@ export function parseJsonRecord(value) {
62
62
  return {};
63
63
  }
64
64
  }
65
+ // The server rejects any variable the template does not declare, so a stale pick must not travel along.
66
+ export function pickDeclaredVariables(picked, declared) {
67
+ if (picked === null || declared === undefined)
68
+ return null;
69
+ return Object.fromEntries(Object.entries(picked).filter(([name]) => Object.hasOwn(declared, name)));
70
+ }
65
71
  function categoryOf(value) {
66
72
  return value === "transactional" || value === "marketing" ? value : "";
67
73
  }
@@ -6,7 +6,7 @@ import { EditorHeader, EditorLocaleSwitch, EditorModeSwitch, originOf, } from ".
6
6
  import { EditorBody, EditorFooter, EditorFrame } from "./editor-shell.js";
7
7
  import { fileUrl } from "./file-name.js";
8
8
  import { MailConfirmDialogs } from "./mail-dialogs.js";
9
- import { buildMeta, isDraftDirty, loadedMailOf, parseJsonRecord, parseLinkedResources, systemLabelMap, variableLabelMap, } from "./mail-draft.js";
9
+ import { buildMeta, isDraftDirty, loadedMailOf, parseJsonRecord, parseLinkedResources, pickDeclaredVariables, systemLabelMap, variableLabelMap, } from "./mail-draft.js";
10
10
  import { MailEditCenter } from "./mail-edit-center.js";
11
11
  import { footerMessage, metaFieldMessages } from "./mail-footer.js";
12
12
  import { PlaceholderSection, ResourceSection, SystemPlaceholderSection, } from "./mail-left-rail.js";
@@ -83,6 +83,7 @@ export function MailEditorBody(props) {
83
83
  locale,
84
84
  scope: isSystemAdmin ? "system" : "tenant",
85
85
  }).data;
86
+ const pickedVariables = pickDeclaredVariables(picked, validation?.variables);
86
87
  const update = (patch) => {
87
88
  const next = { ...draft, ...patch };
88
89
  setDraft(next);
@@ -153,7 +154,7 @@ export function MailEditorBody(props) {
153
154
  variableSchema,
154
155
  includeAttachments,
155
156
  prefixSubject,
156
- ...(picked !== null && { variables: picked }),
157
+ ...(pickedVariables !== null && { variables: pickedVariables }),
157
158
  });
158
159
  };
159
160
  // Nothing is written yet in a missing language, so no placeholder may read as missing.
@@ -185,7 +186,7 @@ export function MailEditorBody(props) {
185
186
  content: settled.body,
186
187
  meta: buildMeta(settled),
187
188
  variableSchema,
188
- ...(picked !== null && { variables: picked }),
189
+ ...(pickedVariables !== null && { variables: pickedVariables }),
189
190
  }, picked: picked, onPick: setPicked, validation: validation ?? null, slug: slug, locale: locale, fromName: settled.senderName, own: own, origin: origin, controls: controls, testMail: {
190
191
  availability: testMail.availability,
191
192
  pending: testMail.pending,
@@ -1,8 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ConfigQueries } from "@cosmicdrift/kumiko-bundled-features/config/web";
3
- import { useNav, usePrimitives, useQuery, useTranslation } from "@cosmicdrift/kumiko-renderer";
2
+ import { useNav, usePrimitives, useTenantCurrency, useTranslation, } from "@cosmicdrift/kumiko-renderer";
4
3
  import { DOCUMENT_FALLBACK_CURRENCY } from "@cosmicdrift/kumiko-renderer/headless";
5
- import { TENANT_CURRENCY_CONFIG_KEY } from "@cosmicdrift/kumiko-types/fields";
6
4
  import { useState } from "react";
7
5
  import { displayValue } from "./mail-draft.js";
8
6
  import { usePreviewRecordPicker } from "./preview-record-picker.js";
@@ -22,22 +20,16 @@ function checkText(t, check, validation) {
22
20
  const translated = t(check.i18nKey, params);
23
21
  return translated === check.i18nKey ? check.code : translated;
24
22
  }
25
- // Same source and fallback as the renderer's readDocumentFormat, so a bare amount shows
26
- // the currency the rendered mail prints.
27
- function useTenantCurrency() {
28
- const { data, error } = useQuery(ConfigQueries.values, {
29
- keys: [TENANT_CURRENCY_CONFIG_KEY],
30
- });
31
- if (error)
32
- return undefined;
33
- if (data === null)
34
- return undefined;
35
- const value = data[TENANT_CURRENCY_CONFIG_KEY]?.value;
36
- return typeof value === "string" && value !== "" ? value : DOCUMENT_FALLBACK_CURRENCY;
23
+ // Same fallback as the renderer's readDocumentFormat, so a bare amount shows the currency the
24
+ // rendered mail prints, also when the config query fails.
25
+ function useSampleTenantCurrency(validation) {
26
+ const hasMoneyVariable = Object.values(validation?.variables ?? {}).some((variable) => variable.type === "money");
27
+ const resolution = useTenantCurrency(hasMoneyVariable, DOCUMENT_FALLBACK_CURRENCY);
28
+ return resolution.status === "ready" ? resolution.currency : undefined;
37
29
  }
38
30
  function SampleValues({ preview, validation, picked, locale, }) {
39
31
  const t = useTranslation();
40
- const tenantCurrency = useTenantCurrency();
32
+ const tenantCurrency = useSampleTenantCurrency(validation);
41
33
  const values = { ...(preview?.sampleValues ?? {}), ...(picked ?? {}) };
42
34
  const names = Object.keys(validation?.variables ?? values);
43
35
  return (_jsx(_Fragment, { children: names.map((name) => {
@@ -5,7 +5,7 @@ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
5
5
  import { DEFAULT_LAYOUT_SLUG, TENANT_MAIL_SENDING_SCREEN_ID } from "./constants.js";
6
6
  import { labelPlaceholders } from "./mail-draft.js";
7
7
  import { RailField, RailHint, RailRow, RailSection } from "./rail.js";
8
- import { usageParts } from "./used-by-label.js";
8
+ import { templateTitleText, usageParts } from "./used-by-label.js";
9
9
  function useTemplateUsage(slug) {
10
10
  const { data } = useQuery(TemplateResolverQueries.usage, {
11
11
  slug,
@@ -33,7 +33,7 @@ export function layoutOptionLabel(t, layout) {
33
33
  // A layout has no registry title; the slug would read like an id, so name it by kind.
34
34
  const title = layout.title === layout.slug
35
35
  ? t(`documentDesigner.editor.title.${layout.kind === "mail-layout" ? "mail" : "pdf"}`)
36
- : layout.title;
36
+ : templateTitleText(t, layout.title);
37
37
  return t(layout.origin === "tenant"
38
38
  ? "documentDesigner.mail.props.layoutTenant"
39
39
  : "documentDesigner.mail.props.layoutSystem", { title, version: layout.version });
@@ -92,7 +92,7 @@ export type MailDraft = {
92
92
  readonly category: MailCategory;
93
93
  readonly senderName: string;
94
94
  readonly replyTo: string;
95
- /** Meta lines without rail fields yet; carried so a tenant save keeps them. */
95
+ /** Placeholder lines; an empty tone stores the accent default. */
96
96
  readonly headerChip: string;
97
97
  readonly tone: string;
98
98
  readonly footerNote: string;
@@ -23,7 +23,7 @@ import { DEFAULT_LAYOUT_SLUG, DOCUMENT_PDF_KIND, PDF_LAYOUT_KIND, } from "./type
23
23
  import { useFilesCapability } from "./use-files-capability.js";
24
24
  import { useInsertTargets } from "./use-insert-targets.js";
25
25
  import { useOverviewRows } from "./use-overview-rows.js";
26
- import { templateTitle } from "./used-by-label.js";
26
+ import { templateTitle, templateTitleText } from "./used-by-label.js";
27
27
  const SETTLE_MS = 500;
28
28
  const NO_VARIABLES = {};
29
29
  // Only the label of an images variable is needed, for the Images block and its picker.
@@ -282,7 +282,8 @@ export function PdfTemplateEditor({ slug, locale: requestedLocale, onLocaleChang
282
282
  }
283
283
  const canPublish = validation?.canPublish === true;
284
284
  const layoutSlugShown = layout?.slug ?? DEFAULT_LAYOUT_SLUG;
285
- const layoutName = layouts.rows.find((row) => row.slug === layoutSlugShown)?.title ?? layoutSlugShown;
285
+ const layoutTitle = layouts.rows.find((row) => row.slug === layoutSlugShown)?.title;
286
+ const layoutName = layoutTitle === undefined ? layoutSlugShown : templateTitleText(t, layoutTitle);
286
287
  const layoutResourceSrc = (key) => key === "logo" && brandingLogoFile !== "" ? fileUrl(brandingLogoFile) : undefined;
287
288
  const placeholderSource = validation === undefined || validation === null
288
289
  ? null
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-document-designer",
3
- "version": "0.386.0",
3
+ "version": "0.388.0",
4
4
  "description": "Document designer for Kumiko: template overview, layout editor (mail layout, PDF letterhead) and the PDF template editor.",
5
- "license": "BUSL-1.1",
5
+ "license": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
7
7
  "type": "module",
8
8
  "kumiko": {
@@ -20,11 +20,11 @@
20
20
  "./web/styles.css": "./src/web/styles.css"
21
21
  },
22
22
  "dependencies": {
23
- "@cosmicdrift/kumiko-bundled-features": "0.386.0",
24
- "@cosmicdrift/kumiko-framework": "0.386.0",
25
- "@cosmicdrift/kumiko-renderer": "0.386.0",
26
- "@cosmicdrift/kumiko-renderer-puppeteer-client": "0.386.0",
27
- "@cosmicdrift/kumiko-types": "0.386.0",
23
+ "@cosmicdrift/kumiko-bundled-features": "0.388.0",
24
+ "@cosmicdrift/kumiko-framework": "0.388.0",
25
+ "@cosmicdrift/kumiko-renderer": "0.388.0",
26
+ "@cosmicdrift/kumiko-renderer-puppeteer-client": "0.388.0",
27
+ "@cosmicdrift/kumiko-types": "0.388.0",
28
28
  "react": "^19.2.6"
29
29
  },
30
30
  "devDependencies": {
package/src/changes.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "version": "0.388.0",
4
+ "type": "fix",
5
+ "title": "Picked preview values are filtered to declared variables",
6
+ "detail": "A picked record value for a variable the template does not declare no longer travels into the preview or test mail, which the server rejected as an unknown variable."
7
+ },
8
+ {
9
+ "version": "0.388.0",
10
+ "type": "fix",
11
+ "title": "Singular tenant counts in the override texts",
12
+ "detail": "The override summary and the system origin bar use plural forms, so one customising tenant reads \"1 tenant has its own version\" in en, de and es."
13
+ },
14
+ {
15
+ "version": "0.388.0",
16
+ "type": "fix",
17
+ "title": "Seeded layout titles show translated in the PDF editor and mail rail",
18
+ "detail": "A layout seeded with a title i18n key now shows its translated name in the PDF editor header and the mail editor properties rail instead of the raw key."
19
+ },
2
20
  {
3
21
  "version": "0.384.0",
4
22
  "type": "improvement",