@gooddata/sdk-model 11.55.0-alpha.3 → 11.55.0-alpha.4

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.
@@ -0,0 +1,39 @@
1
+ import { type AccessGranularPermission } from "../accessControl/index.js";
2
+ import { type IWorkspacePermissions } from "./index.js";
3
+ /**
4
+ * Effective EDIT capability for a single metric.
5
+ *
6
+ * @remarks
7
+ * A workspace admin can always edit, matching how dashboards behave — `getDashboardPermissions`
8
+ * short-circuits to EDIT when the workspace grants MANAGE, and MANAGE is what canManageProject
9
+ * is built from. Behind enableMetricPermissions the metric's own EDIT grants it to everyone else.
10
+ *
11
+ * Only admins could edit metrics before the flag, so this can only ever grant edit, never take
12
+ * it away. Drop the flag term when the flag is removed.
13
+ *
14
+ * @param metricPermissions - the metric's own permissions, absent when they were not requested
15
+ * @param workspacePermissions - the user's workspace permissions
16
+ * @param areMetricPermissionsEnabled - the enableMetricPermissions feature flag
17
+ *
18
+ * @alpha
19
+ */
20
+ export declare function canEditMetric(metricPermissions: AccessGranularPermission[] | undefined, workspacePermissions: IWorkspacePermissions, areMetricPermissionsEnabled: boolean): boolean;
21
+ /**
22
+ * Effective SHARE capability for a single metric.
23
+ *
24
+ * @remarks
25
+ * Same shape as {@link canEditMetric}: a workspace admin can always share, otherwise the metric's
26
+ * own SHARE decides. Deliberately not derived from EDIT — the permissions are read independently
27
+ * rather than as an EDIT ⊃ SHARE ⊃ VIEW hierarchy.
28
+ *
29
+ * Unlike editing, sharing a metric did not exist before enableMetricPermissions, so it stays off
30
+ * entirely without the flag.
31
+ *
32
+ * @param metricPermissions - the metric's own permissions, absent when they were not requested
33
+ * @param workspacePermissions - the user's workspace permissions
34
+ * @param areMetricPermissionsEnabled - the enableMetricPermissions feature flag
35
+ *
36
+ * @alpha
37
+ */
38
+ export declare function canShareMetric(metricPermissions: AccessGranularPermission[] | undefined, workspacePermissions: IWorkspacePermissions, areMetricPermissionsEnabled: boolean): boolean;
39
+ //# sourceMappingURL=metric.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metric.d.ts","sourceRoot":"","sources":["../../src/permissions/metric.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAE1E,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CACzB,iBAAiB,EAAE,wBAAwB,EAAE,GAAG,SAAS,EACzD,oBAAoB,EAAE,qBAAqB,EAC3C,2BAA2B,EAAE,OAAO,GACrC,OAAO,CAKT;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC1B,iBAAiB,EAAE,wBAAwB,EAAE,GAAG,SAAS,EACzD,oBAAoB,EAAE,qBAAqB,EAC3C,2BAA2B,EAAE,OAAO,GACrC,OAAO,CAKT"}
@@ -0,0 +1,45 @@
1
+ // (C) 2026 GoodData Corporation
2
+ /**
3
+ * Effective EDIT capability for a single metric.
4
+ *
5
+ * @remarks
6
+ * A workspace admin can always edit, matching how dashboards behave — `getDashboardPermissions`
7
+ * short-circuits to EDIT when the workspace grants MANAGE, and MANAGE is what canManageProject
8
+ * is built from. Behind enableMetricPermissions the metric's own EDIT grants it to everyone else.
9
+ *
10
+ * Only admins could edit metrics before the flag, so this can only ever grant edit, never take
11
+ * it away. Drop the flag term when the flag is removed.
12
+ *
13
+ * @param metricPermissions - the metric's own permissions, absent when they were not requested
14
+ * @param workspacePermissions - the user's workspace permissions
15
+ * @param areMetricPermissionsEnabled - the enableMetricPermissions feature flag
16
+ *
17
+ * @alpha
18
+ */
19
+ export function canEditMetric(metricPermissions, workspacePermissions, areMetricPermissionsEnabled) {
20
+ return (workspacePermissions.canManageProject ||
21
+ (areMetricPermissionsEnabled && (metricPermissions?.includes("EDIT") ?? false)));
22
+ }
23
+ /**
24
+ * Effective SHARE capability for a single metric.
25
+ *
26
+ * @remarks
27
+ * Same shape as {@link canEditMetric}: a workspace admin can always share, otherwise the metric's
28
+ * own SHARE decides. Deliberately not derived from EDIT — the permissions are read independently
29
+ * rather than as an EDIT ⊃ SHARE ⊃ VIEW hierarchy.
30
+ *
31
+ * Unlike editing, sharing a metric did not exist before enableMetricPermissions, so it stays off
32
+ * entirely without the flag.
33
+ *
34
+ * @param metricPermissions - the metric's own permissions, absent when they were not requested
35
+ * @param workspacePermissions - the user's workspace permissions
36
+ * @param areMetricPermissionsEnabled - the enableMetricPermissions feature flag
37
+ *
38
+ * @alpha
39
+ */
40
+ export function canShareMetric(metricPermissions, workspacePermissions, areMetricPermissionsEnabled) {
41
+ if (!areMetricPermissionsEnabled) {
42
+ return false;
43
+ }
44
+ return workspacePermissions.canManageProject || (metricPermissions?.includes("SHARE") ?? false);
45
+ }
@@ -0,0 +1,187 @@
1
+ import { type IRgbColorValue } from "../colors/index.js";
2
+ /**
3
+ * Named brand image offered to report authors.
4
+ *
5
+ * @alpha
6
+ */
7
+ export interface IReportsBrandKitImage {
8
+ /**
9
+ * Identifier the image is referenced by from report content, as the
10
+ * `{{image_<id>}}` variable. Must match /^[a-zA-Z0-9_]+$/.
11
+ */
12
+ id: string;
13
+ /**
14
+ * Absolute URL of the externally hosted image. The organization CSP must allow the
15
+ * host, and for exports the host must also serve CORS headers.
16
+ */
17
+ url: string;
18
+ /**
19
+ * Human readable description shown when the image is offered in the editor.
20
+ */
21
+ description?: string;
22
+ }
23
+ /**
24
+ * Brand colors.
25
+ *
26
+ * @remarks
27
+ * Every color but {@link IReportsBrandKitColors.chart} is written into CSS, so it may be stated in
28
+ * any notation CSS reads. Chart colors become palette entries instead of declarations, so they are
29
+ * held to {@link parseReportsBrandChartColor} and a value it cannot read is dropped.
30
+ *
31
+ * @alpha
32
+ */
33
+ export interface IReportsBrandKitColors {
34
+ /**
35
+ * Primary brand color, offered to authors as a swatch.
36
+ *
37
+ * @remarks
38
+ * Also seeds the derived chart palette when {@link IReportsBrandKitColors.chart} is not
39
+ * provided — but only when stated in a notation {@link parseReportsBrandChartColor} reads,
40
+ * since seeding means computing tints of it rather than declaring it. Stated in any other
41
+ * notation it remains a swatch, and charts keep the workspace palette.
42
+ */
43
+ brand?: string;
44
+ /**
45
+ * Ordered chart series colors the visualization palette is derived from. Each must be a color
46
+ * {@link parseReportsBrandChartColor} reads; the sanitizer drops the rest.
47
+ */
48
+ chart?: string[];
49
+ /**
50
+ * Ink of headings and titles.
51
+ */
52
+ ink?: string;
53
+ /**
54
+ * Ink of body and secondary text.
55
+ */
56
+ inkMuted?: string;
57
+ /**
58
+ * Default page background.
59
+ */
60
+ paper?: string;
61
+ /**
62
+ * Alternate background for covers and highlighted sections.
63
+ */
64
+ paperAlt?: string;
65
+ }
66
+ /**
67
+ * One hosted font file, loaded as a font face for rendered reports.
68
+ *
69
+ * @alpha
70
+ */
71
+ export interface IReportsBrandKitFontFace {
72
+ /**
73
+ * Font family name the face registers; reference it from
74
+ * {@link IReportsBrandKitTypography.fontFamily}.
75
+ */
76
+ family: string;
77
+ /**
78
+ * Absolute URL of the externally hosted font file (TTF/OTF/WOFF/WOFF2). The organization
79
+ * CSP must allow the host as a font source, and for exports the host must also serve
80
+ * CORS headers.
81
+ */
82
+ url: string;
83
+ /**
84
+ * CSS font-weight the face carries (1-1000). Defaults to 400.
85
+ */
86
+ weight?: number;
87
+ /**
88
+ * Defaults to "normal".
89
+ */
90
+ style?: "normal" | "italic";
91
+ }
92
+ /**
93
+ * Brand typography.
94
+ *
95
+ * @alpha
96
+ */
97
+ export interface IReportsBrandKitTypography {
98
+ /**
99
+ * CSS font-family stack applied to rendered reports.
100
+ */
101
+ fontFamily?: string;
102
+ /**
103
+ * Hosted font files backing the families named in
104
+ * {@link IReportsBrandKitTypography.fontFamily}.
105
+ */
106
+ fonts?: IReportsBrandKitFontFace[];
107
+ }
108
+ /**
109
+ * Brand assets, all hosted outside the platform.
110
+ *
111
+ * @alpha
112
+ */
113
+ export interface IReportsBrandKitAssets {
114
+ /**
115
+ * Primary logo URL; resolves the `{{logo}}` report variable, taking precedence over
116
+ * the workspace white-labeling logo.
117
+ */
118
+ logo?: string;
119
+ /**
120
+ * Logo variant for dark backgrounds; resolves the `{{logoInverse}}` report variable.
121
+ */
122
+ logoInverse?: string;
123
+ /**
124
+ * Additional brand images, each resolving its `{{image_<id>}}` report variable.
125
+ */
126
+ images?: IReportsBrandKitImage[];
127
+ }
128
+ /**
129
+ * Workspace brand kit: the customer's colors, assets, and typography reports are branded with.
130
+ *
131
+ * @remarks
132
+ * Stored as the free-form content of the `reportsBrandKit` workspace setting. Everything is
133
+ * optional; each part degrades independently to the product defaults. Assets resolve report
134
+ * variables, chart colors derive the visualization palette, typography applies to rendered
135
+ * reports.
136
+ *
137
+ * @alpha
138
+ */
139
+ export interface IReportsBrandKit {
140
+ /**
141
+ * Content model version, for stored-content evolution.
142
+ */
143
+ version: "1";
144
+ colors?: IReportsBrandKitColors;
145
+ typography?: IReportsBrandKitTypography;
146
+ assets?: IReportsBrandKitAssets;
147
+ }
148
+ /**
149
+ * Prefix of the report variables brand kit images resolve; custom report variables must not
150
+ * use it.
151
+ *
152
+ * @alpha
153
+ */
154
+ export declare const ReportsBrandKitImageVariablePrefix = "image_";
155
+ /**
156
+ * Name of the report variable resolving the brand kit image with the given id.
157
+ *
158
+ * @alpha
159
+ */
160
+ export declare function reportsBrandKitImageVariable(imageId: string): string;
161
+ /**
162
+ * Reads one of {@link IReportsBrandKitColors.chart} as rgb channels, or undefined where the value is
163
+ * not one of the notations those colors are stated in.
164
+ *
165
+ * @remarks
166
+ * Chart colors become palette entries rather than CSS declarations, so unlike the rest of the kit
167
+ * they are held to what this reads: hex (3, 4, 6 or 8 digits) and `rgb()`/`rgba()`. The sanitizer
168
+ * drops what this cannot read, so a stored kit never carries a chart color the palette would
169
+ * silently ignore.
170
+ *
171
+ * @alpha
172
+ */
173
+ export declare function parseReportsBrandChartColor(color: string): IRgbColorValue | undefined;
174
+ /**
175
+ * Reads a stored brand kit from free-form JSON, keeping the parts that are valid and dropping
176
+ * everything else.
177
+ *
178
+ * @remarks
179
+ * The setting content is not schema-validated by the backend, so every consumer must go
180
+ * through this. Returns undefined only when the value is not a version "1" brand kit at all;
181
+ * a kit whose sections are absent or entirely invalid comes back empty, which the type permits
182
+ * and a workspace that has not filled its kit in yet carries.
183
+ *
184
+ * @alpha
185
+ */
186
+ export declare function sanitizeReportsBrandKit(value: unknown): IReportsBrandKit | undefined;
187
+ //# sourceMappingURL=brandKit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brandKit.d.ts","sourceRoot":"","sources":["../../src/reports/brandKit.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IAClC;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,sBAAsB;IACnC;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAEjB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACrC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACvC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,KAAK,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,MAAM,CAAC,EAAE,qBAAqB,EAAE,CAAC;CACpC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;OAEG;IACH,OAAO,EAAE,GAAG,CAAC;IAEb,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAEhC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IAExC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACnC;AAED;;;;;GAKG;AACH,eAAO,MAAM,kCAAkC,WAAW,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpE;AAgCD;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAsBrF;AA6HD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAWpF"}
@@ -0,0 +1,208 @@
1
+ // (C) 2026 GoodData Corporation
2
+ /**
3
+ * Prefix of the report variables brand kit images resolve; custom report variables must not
4
+ * use it.
5
+ *
6
+ * @alpha
7
+ */
8
+ export const ReportsBrandKitImageVariablePrefix = "image_";
9
+ /**
10
+ * Name of the report variable resolving the brand kit image with the given id.
11
+ *
12
+ * @alpha
13
+ */
14
+ export function reportsBrandKitImageVariable(imageId) {
15
+ return `${ReportsBrandKitImageVariablePrefix}${imageId}`;
16
+ }
17
+ const imageIdPattern = /^[a-zA-Z0-9_]+$/;
18
+ const HEX_COLOR = /^#(?<digits>[0-9a-f]{3,8})$/i;
19
+ // Either the comma form or the space form, not a mixture of the two, and an alpha that is an
20
+ // actual number: `rgb(1, 2 3)` and `rgba(1,2,3,.)` are no more colors than a word would be.
21
+ const RGB_NUMBER = String.raw `\d{1,3}`;
22
+ const RGB_ALPHA = String.raw `(?:\d+(?:\.\d+)?|\.\d+)%?`;
23
+ const RGB_COLOR = new RegExp(String.raw `^rgba?\(\s*(?:` +
24
+ String.raw `(?<r>${RGB_NUMBER})\s*,\s*(?<g>${RGB_NUMBER})\s*,\s*(?<b>${RGB_NUMBER})(?:\s*,\s*${RGB_ALPHA})?` +
25
+ String.raw `|` +
26
+ String.raw `(?<rs>${RGB_NUMBER})\s+(?<gs>${RGB_NUMBER})\s+(?<bs>${RGB_NUMBER})(?:\s*/\s*${RGB_ALPHA})?` +
27
+ String.raw `)\s*\)$`, "i");
28
+ // Alpha is dropped from a 4- or 8-digit hex and from rgba(): a palette fill is opaque rgb.
29
+ function fromHexDigits(digits) {
30
+ const short = digits.length === 3 || digits.length === 4;
31
+ if (!short && digits.length !== 6 && digits.length !== 8) {
32
+ return undefined;
33
+ }
34
+ const width = short ? 1 : 2;
35
+ const channel = (index) => {
36
+ const part = digits.slice(index * width, index * width + width);
37
+ return Number.parseInt(short ? part + part : part, 16);
38
+ };
39
+ return { r: channel(0), g: channel(1), b: channel(2) };
40
+ }
41
+ /**
42
+ * Reads one of {@link IReportsBrandKitColors.chart} as rgb channels, or undefined where the value is
43
+ * not one of the notations those colors are stated in.
44
+ *
45
+ * @remarks
46
+ * Chart colors become palette entries rather than CSS declarations, so unlike the rest of the kit
47
+ * they are held to what this reads: hex (3, 4, 6 or 8 digits) and `rgb()`/`rgba()`. The sanitizer
48
+ * drops what this cannot read, so a stored kit never carries a chart color the palette would
49
+ * silently ignore.
50
+ *
51
+ * @alpha
52
+ */
53
+ export function parseReportsBrandChartColor(color) {
54
+ const trimmed = color.trim();
55
+ const hex = HEX_COLOR.exec(trimmed);
56
+ if (hex?.groups) {
57
+ return fromHexDigits(hex.groups["digits"]);
58
+ }
59
+ const rgb = RGB_COLOR.exec(trimmed);
60
+ if (rgb?.groups) {
61
+ // One of the two forms matched, so each channel sits in exactly one of the two group sets.
62
+ const channels = [
63
+ ["r", "rs"],
64
+ ["g", "gs"],
65
+ ["b", "bs"],
66
+ ].map(([comma, space]) => Number(rgb.groups[comma] ?? rgb.groups[space]));
67
+ return channels.every((value) => value <= 255)
68
+ ? { r: channels[0], g: channels[1], b: channels[2] }
69
+ : undefined;
70
+ }
71
+ return undefined;
72
+ }
73
+ // Brand assets are fetched over the network and drawn into reports; a url of any other scheme
74
+ // (javascript:, data:, a relative path) does not describe an externally hosted asset and is dropped
75
+ // rather than reaching an element's src or a @font-face rule.
76
+ function sanitizedAssetUrl(value) {
77
+ const url = sanitizedString(value);
78
+ if (url === undefined) {
79
+ return undefined;
80
+ }
81
+ try {
82
+ const { protocol } = new URL(url);
83
+ return protocol === "https:" || protocol === "http:" ? url : undefined;
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ }
89
+ function sanitizedString(value) {
90
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
91
+ }
92
+ function withDefined(parts) {
93
+ const defined = Object.fromEntries(Object.entries(parts).filter(([, value]) => value !== undefined));
94
+ return Object.keys(defined).length > 0 ? defined : undefined;
95
+ }
96
+ function sanitizedColors(value) {
97
+ if (typeof value !== "object" || value === null) {
98
+ return undefined;
99
+ }
100
+ const colors = value;
101
+ const chart = Array.isArray(colors.chart)
102
+ ? colors.chart
103
+ .map(sanitizedString)
104
+ .filter((color) => color !== undefined && parseReportsBrandChartColor(color) !== undefined)
105
+ : undefined;
106
+ return withDefined({
107
+ brand: sanitizedString(colors.brand),
108
+ chart: chart !== undefined && chart.length > 0 ? chart : undefined,
109
+ ink: sanitizedString(colors.ink),
110
+ inkMuted: sanitizedString(colors.inkMuted),
111
+ paper: sanitizedString(colors.paper),
112
+ paperAlt: sanitizedString(colors.paperAlt),
113
+ });
114
+ }
115
+ function sanitizedFontFace(value) {
116
+ if (typeof value !== "object" || value === null) {
117
+ return undefined;
118
+ }
119
+ const { family, url, weight, style } = value;
120
+ const sanitizedFamily = sanitizedString(family);
121
+ const sanitizedUrl = sanitizedAssetUrl(url);
122
+ if (sanitizedFamily === undefined || sanitizedUrl === undefined) {
123
+ return undefined;
124
+ }
125
+ return withDefined({
126
+ family: sanitizedFamily,
127
+ url: sanitizedUrl,
128
+ weight: typeof weight === "number" && weight >= 1 && weight <= 1000 ? weight : undefined,
129
+ style: style === "normal" || style === "italic" ? style : undefined,
130
+ });
131
+ }
132
+ function sanitizedFonts(value) {
133
+ if (!Array.isArray(value)) {
134
+ return undefined;
135
+ }
136
+ const fonts = value
137
+ .map(sanitizedFontFace)
138
+ .filter((font) => font !== undefined);
139
+ return fonts.length > 0 ? fonts : undefined;
140
+ }
141
+ function sanitizedTypography(value) {
142
+ if (typeof value !== "object" || value === null) {
143
+ return undefined;
144
+ }
145
+ const typography = value;
146
+ return withDefined({
147
+ fontFamily: sanitizedString(typography.fontFamily),
148
+ fonts: sanitizedFonts(typography.fonts),
149
+ });
150
+ }
151
+ function sanitizedImages(value) {
152
+ if (!Array.isArray(value)) {
153
+ return undefined;
154
+ }
155
+ const seen = new Set();
156
+ const images = [];
157
+ for (const entry of value) {
158
+ if (typeof entry !== "object" || entry === null) {
159
+ continue;
160
+ }
161
+ const { id, url, description } = entry;
162
+ if (typeof id !== "string" || !imageIdPattern.test(id) || seen.has(id)) {
163
+ continue;
164
+ }
165
+ const sanitizedUrl = sanitizedAssetUrl(url);
166
+ if (sanitizedUrl === undefined) {
167
+ continue;
168
+ }
169
+ seen.add(id);
170
+ images.push(withDefined({ id, url: sanitizedUrl, description: sanitizedString(description) }));
171
+ }
172
+ return images.length > 0 ? images : undefined;
173
+ }
174
+ function sanitizedAssets(value) {
175
+ if (typeof value !== "object" || value === null) {
176
+ return undefined;
177
+ }
178
+ const assets = value;
179
+ return withDefined({
180
+ logo: sanitizedAssetUrl(assets.logo),
181
+ logoInverse: sanitizedAssetUrl(assets.logoInverse),
182
+ images: sanitizedImages(assets.images),
183
+ });
184
+ }
185
+ /**
186
+ * Reads a stored brand kit from free-form JSON, keeping the parts that are valid and dropping
187
+ * everything else.
188
+ *
189
+ * @remarks
190
+ * The setting content is not schema-validated by the backend, so every consumer must go
191
+ * through this. Returns undefined only when the value is not a version "1" brand kit at all;
192
+ * a kit whose sections are absent or entirely invalid comes back empty, which the type permits
193
+ * and a workspace that has not filled its kit in yet carries.
194
+ *
195
+ * @alpha
196
+ */
197
+ export function sanitizeReportsBrandKit(value) {
198
+ if (typeof value !== "object" || value?.version !== "1") {
199
+ return undefined;
200
+ }
201
+ const kit = value;
202
+ const parts = withDefined({
203
+ colors: sanitizedColors(kit.colors),
204
+ typography: sanitizedTypography(kit.typography),
205
+ assets: sanitizedAssets(kit.assets),
206
+ });
207
+ return { version: "1", ...parts };
208
+ }
@@ -122,7 +122,7 @@ export interface IReportPageBodyValidationIssue {
122
122
  /**
123
123
  * Validates the structural invariants of a page body beyond what its types guarantee for content
124
124
  * arriving from the wire: the page format is known, slot localIdentifiers are unique, layout
125
- * weights are positive, corner radii are finite and not negative, every slot is placed (by the
125
+ * weights are positive, corner radii and paddings are finite and not negative, every slot is placed (by the
126
126
  * layout, or as the image background of a box that renders), every layout slotId resolves
127
127
  * (unresolved ones are warnings — they render as empty areas), and background references
128
128
  * resolve to image slots.
@@ -1 +1 @@
1
- {"version":3,"file":"pageLayout.d.ts","sourceRoot":"","sources":["../../src/reports/pageLayout.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,KAAK,MAAM,EAAY,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAAE,KAAK,oBAAoB,EAAgD,MAAM,aAAa,CAAC;AACtG,OAAO,EAAE,KAAK,gBAAgB,EAAsB,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,UAAU,EAAuC,MAAM,WAAW,CAAC;AACjF,OAAO,EAAE,KAAK,eAAe,EAA2B,MAAM,cAAc,CAAC;AAE7E;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC5B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IAEvC;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAE1B;;OAEG;IACH,KAAK,CAAC,EAAE,eAAe,CAAC;IAExB;;OAEG;IACH,MAAM,EAAE,oBAAoB,CAAC;IAE7B;;OAEG;IACH,KAAK,EAAE,UAAU,EAAE,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAyB,SAAQ,eAAe;IAC7D;;OAEG;IACH,OAAO,EAAE,GAAG,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IACxC,IAAI,EAAE,kBAAkB,CAAC;IAEzB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,KAAK,EAAE,MAAM,CAAC;IAEd,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB,OAAO,EAAE,wBAAwB,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAkB,SAAQ,2BAA2B,EAAE,eAAe,EAAE,eAAe;IACpG,GAAG,EAAE,MAAM,CAAC;IAEZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,2BAA2B,CAE7F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,iBAAiB,CAEzE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,wBAAwB,CAOzF;AAED;;;;GAIG;AACH,MAAM,WAAW,8BAA8B;IAC3C,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,eAAe,GAAG,8BAA8B,EAAE,CA8F9F"}
1
+ {"version":3,"file":"pageLayout.d.ts","sourceRoot":"","sources":["../../src/reports/pageLayout.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,KAAK,MAAM,EAAY,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAAE,KAAK,oBAAoB,EAAgD,MAAM,aAAa,CAAC;AACtG,OAAO,EAAE,KAAK,gBAAgB,EAAsB,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,UAAU,EAAuC,MAAM,WAAW,CAAC;AACjF,OAAO,EAAE,KAAK,eAAe,EAA2B,MAAM,cAAc,CAAC;AAE7E;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC5B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IAEvC;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAE1B;;OAEG;IACH,KAAK,CAAC,EAAE,eAAe,CAAC;IAExB;;OAEG;IACH,MAAM,EAAE,oBAAoB,CAAC;IAE7B;;OAEG;IACH,KAAK,EAAE,UAAU,EAAE,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAyB,SAAQ,eAAe;IAC7D;;OAEG;IACH,OAAO,EAAE,GAAG,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IACxC,IAAI,EAAE,kBAAkB,CAAC;IAEzB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,KAAK,EAAE,MAAM,CAAC;IAEd,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB,OAAO,EAAE,wBAAwB,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAkB,SAAQ,2BAA2B,EAAE,eAAe,EAAE,eAAe;IACpG,GAAG,EAAE,MAAM,CAAC;IAEZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,2BAA2B,CAE7F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,iBAAiB,CAEzE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,wBAAwB,CAOzF;AAED;;;;GAIG;AACH,MAAM,WAAW,8BAA8B;IAC3C,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,eAAe,GAAG,8BAA8B,EAAE,CA+F9F"}
@@ -35,7 +35,7 @@ export function isReportPageLayoutContentV1(obj) {
35
35
  /**
36
36
  * Validates the structural invariants of a page body beyond what its types guarantee for content
37
37
  * arriving from the wire: the page format is known, slot localIdentifiers are unique, layout
38
- * weights are positive, corner radii are finite and not negative, every slot is placed (by the
38
+ * weights are positive, corner radii and paddings are finite and not negative, every slot is placed (by the
39
39
  * layout, or as the image background of a box that renders), every layout slotId resolves
40
40
  * (unresolved ones are warnings — they render as empty areas), and background references
41
41
  * resolve to image slots.
@@ -47,11 +47,11 @@ export function validateReportPageBody(body) {
47
47
  if (body.format !== undefined && !isReportPageFormat(body.format)) {
48
48
  issues.push({ severity: "error", message: `Unknown page format "${body.format}".` });
49
49
  }
50
- const checkBorderRadius = (borderRadius) => {
51
- if (borderRadius !== undefined && !(Number.isFinite(borderRadius) && borderRadius >= 0)) {
50
+ const checkLength = (name, value) => {
51
+ if (value !== undefined && !(Number.isFinite(value) && value >= 0)) {
52
52
  issues.push({
53
53
  severity: "error",
54
- message: `Border radius must be a finite non-negative number, got ${borderRadius}.`,
54
+ message: `${name} must be a finite non-negative number, got ${value}.`,
55
55
  });
56
56
  }
57
57
  };
@@ -68,7 +68,8 @@ export function validateReportPageBody(body) {
68
68
  const slotsById = new Map(body.slots.map((slot) => [slot.localIdentifier, slot]));
69
69
  const backgroundIds = new Set();
70
70
  const checkBoxStyle = (style, isDrawn) => {
71
- checkBorderRadius(style?.borderRadius);
71
+ checkLength("Border radius", style?.borderRadius);
72
+ checkLength("Padding", style?.padding);
72
73
  if (!style?.background || !isReportImageBackground(style.background)) {
73
74
  return;
74
75
  }
@@ -51,6 +51,13 @@ export interface IReportBoxStyle {
51
51
  * Content reaching into a rounded corner is clipped.
52
52
  */
53
53
  borderRadius?: number;
54
+ /**
55
+ * Inset of the box's own content, in percent of the page width like
56
+ * {@link IReportBoxStyle.borderRadius}. Insets only inward — siblings and the layout
57
+ * gaps around the box are untouched. A box that paints a background usually wants one;
58
+ * a full-bleed box (an image-backed cover) wants none, which is why no default exists.
59
+ */
60
+ padding?: number;
54
61
  }
55
62
  /**
56
63
  * Styling of a text slot: the paint of its box, plus the ink and placement of the text it owns.
@@ -1 +1 @@
1
- {"version":3,"file":"styling.d.ts","sourceRoot":"","sources":["../../src/reports/styling.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,IAAI,EAAE,OAAO,CAAC;IAEd;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,IAAI,EAAE,OAAO,CAAC;IAEd;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;AAE/E;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC5B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAE9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACrD;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,eAAe,CAAC,EAAE,sBAAsB,CAAC;IAEzC,aAAa,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAC9B,eAAe,CAAC,EAAE,sBAAsB,CAAC;IAEzC,aAAa,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,sBAAsB,CAEnF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,sBAAsB,CAEnF"}
1
+ {"version":3,"file":"styling.d.ts","sourceRoot":"","sources":["../../src/reports/styling.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,IAAI,EAAE,OAAO,CAAC;IAEd;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,IAAI,EAAE,OAAO,CAAC;IAEd;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;AAE/E;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC5B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAE9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACrD;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,eAAe,CAAC,EAAE,sBAAsB,CAAC;IAEzC,aAAa,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAC9B,eAAe,CAAC,EAAE,sBAAsB,CAAC;IAEzC,aAAa,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,sBAAsB,CAEnF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,sBAAsB,CAEnF"}
@@ -13,7 +13,7 @@ export type ReportDateString = string;
13
13
  *
14
14
  * @alpha
15
15
  */
16
- export type ReportBuiltInVariable = "reportTitle" | "periodStart" | "periodEnd" | "workspaceName" | "generatedAt" | "pageNumber" | "totalPages" | "logo";
16
+ export type ReportBuiltInVariable = "reportTitle" | "periodStart" | "periodEnd" | "workspaceName" | "generatedAt" | "pageNumber" | "totalPages" | "logo" | "logoInverse";
17
17
  /**
18
18
  * All built-in report variable names.
19
19
  *
@@ -1 +1 @@
1
- {"version":3,"file":"variables.d.ts","sourceRoot":"","sources":["../../src/reports/variables.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEtC;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,GAC3B,aAAa,GACb,aAAa,GACb,WAAW,GACX,eAAe,GACf,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,MAAM,CAAC;AAEb;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,qBAAqB,EASzD,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAAyB;IACtC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAID;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAShE;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAIlG"}
1
+ {"version":3,"file":"variables.d.ts","sourceRoot":"","sources":["../../src/reports/variables.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEtC;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,GAC3B,aAAa,GACb,aAAa,GACb,WAAW,GACX,eAAe,GACf,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,MAAM,GACN,aAAa,CAAC;AAEpB;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,qBAAqB,EAUzD,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAAyB;IACtC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAID;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAShE;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAIlG"}
@@ -13,6 +13,7 @@ export const ReportBuiltInVariables = [
13
13
  "pageNumber",
14
14
  "totalPages",
15
15
  "logo",
16
+ "logoInverse",
16
17
  ];
17
18
  const placeholderRegex = /\{\{([a-zA-Z][a-zA-Z0-9_]*)\}\}/g;
18
19
  /**