@cosmicdrift/kumiko-bundled-features 0.329.0 → 0.330.2

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 (72) hide show
  1. package/README.md +17 -0
  2. package/dist/admin-shell/feature.d.ts +5 -0
  3. package/dist/admin-shell/feature.js +15 -61
  4. package/dist/admin-shell/i18n.js +35 -9
  5. package/dist/admin-shell/overview-allowlist.d.ts +2 -2
  6. package/dist/admin-shell/overview-allowlist.js +15 -9
  7. package/dist/admin-shell/overview-panels.d.ts +12 -0
  8. package/dist/admin-shell/overview-panels.js +167 -0
  9. package/dist/auth-email-password/email-templates.d.ts +1 -0
  10. package/dist/auth-email-password/email-templates.js +1 -1
  11. package/dist/auth-mfa/i18n.js +9 -0
  12. package/dist/auth-mfa/screens.js +3 -3
  13. package/dist/billing-foundation/checkout-core.d.ts +1 -0
  14. package/dist/billing-foundation/checkout-core.js +1 -0
  15. package/dist/billing-foundation/constants.d.ts +1 -0
  16. package/dist/billing-foundation/constants.js +1 -0
  17. package/dist/billing-foundation/get-subscription-for-tenant.d.ts +1 -0
  18. package/dist/billing-foundation/get-subscription-for-tenant.js +1 -0
  19. package/dist/billing-foundation/i18n.js +1 -1
  20. package/dist/billing-foundation/types.d.ts +1 -0
  21. package/dist/billing-foundation/types.js +1 -0
  22. package/dist/cap-counter/enforce-cap.d.ts +1 -0
  23. package/dist/cap-counter/enforce-cap.js +1 -0
  24. package/dist/cap-overview/handlers/caps-usage.query.js +3 -3
  25. package/dist/data-retention/run-retention-cleanup.js +6 -1
  26. package/dist/delivery/tables.js +4 -2
  27. package/dist/jobs/feature.js +2 -2
  28. package/dist/jobs/i18n.js +6 -0
  29. package/dist/jobs/job-run-table.js +1 -0
  30. package/dist/metrics/compute.d.ts +13 -0
  31. package/dist/metrics/compute.js +127 -0
  32. package/dist/metrics/constants.d.ts +7 -0
  33. package/dist/metrics/constants.js +14 -0
  34. package/dist/metrics/default-metrics.d.ts +9 -0
  35. package/dist/metrics/default-metrics.js +115 -0
  36. package/dist/metrics/feature.d.ts +4 -0
  37. package/dist/metrics/feature.js +119 -0
  38. package/dist/metrics/i18n.d.ts +5 -0
  39. package/dist/metrics/i18n.js +16 -0
  40. package/dist/metrics/index.d.ts +5 -0
  41. package/dist/metrics/index.js +6 -0
  42. package/dist/metrics/types.d.ts +70 -0
  43. package/dist/metrics/types.js +3 -0
  44. package/dist/metrics/validate.d.ts +3 -0
  45. package/dist/metrics/validate.js +74 -0
  46. package/dist/metrics/window.d.ts +13 -0
  47. package/dist/metrics/window.js +63 -0
  48. package/dist/personal-access-tokens/i18n.js +3 -0
  49. package/dist/personal-access-tokens/screens.js +6 -1
  50. package/dist/sessions/schema/user-session.js +1 -0
  51. package/dist/shared/grace-period.d.ts +1 -0
  52. package/dist/shared/grace-period.js +1 -0
  53. package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +1 -0
  54. package/dist/tenant-lifecycle/run-tenant-destroy.js +1 -0
  55. package/dist/tier-engine/feature.js +1 -1
  56. package/dist/tier-engine/i18n.js +3 -0
  57. package/dist/user-data-rights/email-templates.d.ts +1 -0
  58. package/dist/user-data-rights/email-templates.js +1 -1
  59. package/dist/user-data-rights/feature.js +2 -0
  60. package/dist/user-data-rights/web/i18n.js +2 -0
  61. package/dist/user-profile/feature.js +4 -3
  62. package/dist/user-profile/i18n.js +2 -1
  63. package/package.json +17 -9
  64. package/src/admin-shell/changes.json +13 -0
  65. package/src/billing-foundation/changes.json +6 -0
  66. package/src/data-retention/changes.json +5 -0
  67. package/src/delivery/changes.json +7 -0
  68. package/src/jobs/changes.json +7 -0
  69. package/src/metrics/changes.json +15 -0
  70. package/src/personal-access-tokens/changes.json +5 -0
  71. package/src/sessions/changes.json +7 -0
  72. package/src/user-data-rights/changes.json +6 -0
@@ -0,0 +1,13 @@
1
+ import type { AggregateTimeBucket } from "@cosmicdrift/kumiko-types/aggregate-types";
2
+ import { Temporal } from "temporal-polyfill";
3
+ import type { MetricRange } from "./constants.js";
4
+ import type { MetricDefinition } from "./types.js";
5
+ export type MetricWindow = {
6
+ readonly start: Temporal.Instant;
7
+ readonly end: Temporal.Instant;
8
+ readonly previousStart: Temporal.Instant;
9
+ };
10
+ export declare function isValidTimeZone(timeZone: string): boolean;
11
+ export declare function resolveBucket(metric: MetricDefinition, range: MetricRange): AggregateTimeBucket | undefined;
12
+ export declare function computeWindow(range: MetricRange, bucket: AggregateTimeBucket | undefined, timeZone: string, now: Temporal.Instant): MetricWindow;
13
+ export declare function bucketStartsMs(window: MetricWindow, bucket: AggregateTimeBucket, timeZone: string): readonly number[];
@@ -0,0 +1,63 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ const RANGE_HOURS = {
3
+ "24h": 24,
4
+ "7d": 7 * 24,
5
+ "14d": 14 * 24,
6
+ "30d": 30 * 24,
7
+ };
8
+ // The 24h range counts as one day when bucketing by day.
9
+ const RANGE_DAYS = {
10
+ "24h": 1,
11
+ "7d": 7,
12
+ "14d": 14,
13
+ "30d": 30,
14
+ };
15
+ export function isValidTimeZone(timeZone) {
16
+ try {
17
+ new Intl.DateTimeFormat(undefined, { timeZone });
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ export function resolveBucket(metric, range) {
25
+ if (metric.bucket === undefined)
26
+ return undefined;
27
+ if (metric.bucket === "auto")
28
+ return range === "24h" ? "hour" : "day";
29
+ return metric.bucket;
30
+ }
31
+ export function computeWindow(range, bucket, timeZone, now) {
32
+ const zonedNow = now.toZonedDateTimeISO(timeZone);
33
+ let start;
34
+ if (bucket === "hour") {
35
+ const hourStart = zonedNow.round({ smallestUnit: "hour", roundingMode: "floor" });
36
+ start = hourStart.subtract({ hours: RANGE_HOURS[range] - 1 }).toInstant();
37
+ }
38
+ else if (bucket === "day") {
39
+ start = zonedNow
40
+ .startOfDay()
41
+ .subtract({ days: RANGE_DAYS[range] - 1 })
42
+ .startOfDay()
43
+ .toInstant();
44
+ }
45
+ else {
46
+ start = now.subtract({ hours: RANGE_HOURS[range] });
47
+ }
48
+ const spanMs = now.epochMilliseconds - start.epochMilliseconds;
49
+ const previousStart = Temporal.Instant.fromEpochMilliseconds(start.epochMilliseconds - spanMs);
50
+ return { start, end: now, previousStart };
51
+ }
52
+ // Every bucket start in [window.start, window.end], in the display zone —
53
+ // SQL only returns buckets that have rows, the chart needs the gaps too.
54
+ export function bucketStartsMs(window, bucket, timeZone) {
55
+ const starts = [];
56
+ const step = bucket === "hour" ? { hours: 1 } : { days: 1 };
57
+ let cursor = window.start.toZonedDateTimeISO(timeZone);
58
+ while (cursor.epochMilliseconds <= window.end.epochMilliseconds) {
59
+ starts.push(cursor.epochMilliseconds);
60
+ cursor = bucket === "day" ? cursor.add(step).startOfDay() : cursor.add(step);
61
+ }
62
+ return starts;
63
+ }
@@ -7,6 +7,9 @@ export const PAT_FEATURE_I18N = {
7
7
  "pat.list.col.status": { en: "Status" },
8
8
  "pat.list.col.created": { en: "Created" },
9
9
  "pat.list.col.expires": { en: "Expires" },
10
+ "pat.list.status.active": { en: "Active" },
11
+ "pat.list.status.revoked": { en: "Revoked" },
12
+ "pat.list.status.expired": { en: "Expired" },
10
13
  "pat.list.revoke": { en: "Revoke" },
11
14
  "pat.list.revoke.confirm": { en: "Revoke this token? Anything using it will stop working." },
12
15
  "pat.create.title": { en: "Create a new token" },
@@ -1,5 +1,6 @@
1
1
  import { i18nKey, } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { PAT_MINT_SCREEN_ID, PAT_SCREEN_ID, PatHandlers, PatQueries } from "./constants.js";
3
+ const PAT_STATUS_OPTION_KEY_PREFIX = "pat.list.status.";
3
4
  export const patListScreen = {
4
5
  id: PAT_SCREEN_ID,
5
6
  type: "projectionList",
@@ -9,7 +10,11 @@ export const patListScreen = {
9
10
  { field: "name", label: i18nKey("pat.list.col.name") },
10
11
  { field: "prefix", label: i18nKey("pat.list.col.prefix") },
11
12
  { field: "scopes", label: i18nKey("pat.list.col.scopes") },
12
- { field: "status", label: i18nKey("pat.list.col.status") },
13
+ {
14
+ field: "status",
15
+ label: i18nKey("pat.list.col.status"),
16
+ renderer: { format: "enumOption", keyPrefix: PAT_STATUS_OPTION_KEY_PREFIX },
17
+ },
13
18
  {
14
19
  field: "createdAt",
15
20
  label: i18nKey("pat.list.col.created"),
@@ -71,6 +71,7 @@ export const userSessionEntity = createEntity({
71
71
  find: "none",
72
72
  }),
73
73
  },
74
+ indexes: [{ columns: ["tenantId", "lastSeenAt"] }],
74
75
  });
75
76
  // Plain EntityTableMeta, NOT a branded EntityTable: user-session is an
76
77
  // unmanaged direct-write store (r.storeTable in feature.ts, no event stream —
@@ -1,4 +1,5 @@
1
1
  /** Shared grace-period check for user-data-rights + tenant-lifecycle cancel flows. */
2
+ /// <reference types="temporal-polyfill/global" preserve="true" />
2
3
  /**
3
4
  * True when `gracePeriodEnd` is still in the future.
4
5
  * Uses the polyfill Instant API so callers don't depend on `globalThis.Temporal`.
@@ -1,3 +1,4 @@
1
+ /// <reference types="temporal-polyfill/global" preserve="true" />
1
2
  /** Shared grace-period check for user-data-rights + tenant-lifecycle cancel flows. */
2
3
  import { Temporal as TemporalPolyfill } from "temporal-polyfill";
3
4
  /**
@@ -1,3 +1,4 @@
1
+ /// <reference types="temporal-polyfill/global" preserve="true" />
1
2
  import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
2
3
  import { type EscapeHatchAuditSink, type Registry, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
4
  import type { FileProviderResolver } from "@cosmicdrift/kumiko-framework/files";
@@ -1,3 +1,4 @@
1
+ /// <reference types="temporal-polyfill/global" preserve="true" />
1
2
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
3
  import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
3
4
  import { createSystemUser, } from "@cosmicdrift/kumiko-framework/engine";
@@ -198,7 +198,7 @@ export function createTierEngineFeature(opts = {}) {
198
198
  },
199
199
  submitLabel: "tier-admin.submit",
200
200
  cancelTarget: false,
201
- description: "Operator form that assigns a tier to any tenant as a manual grant without a billing purchase.",
201
+ description: "tier-admin.screen.subtitle",
202
202
  access: { roles: ["SystemAdmin"] },
203
203
  // Apps place it via r.nav() (see above) — no nav area to resolve in
204
204
  // isolation.
@@ -6,6 +6,9 @@
6
6
  import { translationsByLocaleFromKeys, } from "@cosmicdrift/kumiko-renderer";
7
7
  export const TIER_ENGINE_I18N = {
8
8
  "screen:tier-admin.title": { en: "Assign tier manually" },
9
+ "tier-admin.screen.subtitle": {
10
+ en: "Assign a tier to a tenant as a manual grant, without a purchase.",
11
+ },
9
12
  "tier-admin.explainer": {
10
13
  en: "Grant a tenant a tier without a purchase. The grant is marked as “manual” and a later billing sync won't overwrite it.",
11
14
  },
@@ -1,5 +1,6 @@
1
1
  export type GdprMailLocale = string;
2
2
  export declare function normalizeGdprMailLocale(raw: string | null | undefined): GdprMailLocale | undefined;
3
+ export declare const GDPR_MAIL_EN: Readonly<Record<string, string>>;
3
4
  export type RenderedEmail = {
4
5
  readonly subject: string;
5
6
  readonly html: string;
@@ -11,7 +11,7 @@ export function normalizeGdprMailLocale(raw) {
11
11
  const root = raw.toLowerCase().split("-")[0];
12
12
  return root && root.length > 0 ? root : undefined;
13
13
  }
14
- const GDPR_MAIL_EN = {
14
+ export const GDPR_MAIL_EN = {
15
15
  "gdpr.mail.appNameDefault": "Account",
16
16
  "gdpr.mail.greeting": "Hi,",
17
17
  "gdpr.mail.exportReady.subject": "{app} — Your data export is ready",
@@ -157,12 +157,14 @@ export function createUserDataRightsFeature(opts = {}) {
157
157
  "userDataRights.privacyCenter.export.title": { en: "Export your data (Art. 20)" },
158
158
  "userDataRights.privacyCenter.restriction.title": { en: "Restrict processing (Art. 18)" },
159
159
  "userDataRights.privacyCenter.restriction.restrict": { en: "Restrict account" },
160
+ "userDataRights.privacyCenter.restriction.dialogTitle": { en: "Restrict your account?" },
160
161
  "userDataRights.privacyCenter.restriction.dialogDescription": {
161
162
  en: "You will be signed out immediately and cannot sign in again until support lifts the restriction.",
162
163
  },
163
164
  "userDataRights.privacyCenter.deletion.title": { en: "Delete account (Art. 17)" },
164
165
  "userDataRights.privacyCenter.deletion.delete": { en: "Delete account" },
165
166
  "userDataRights.privacyCenter.deletion.cancel": { en: "Cancel deletion" },
167
+ "userDataRights.privacyCenter.deletion.dialogTitle": { en: "Delete your account?" },
166
168
  "userDataRights.privacyCenter.deletion.dialogDescription": {
167
169
  en: "Confirming starts the deletion grace period. You can cancel the deletion until it ends.",
168
170
  },
@@ -44,11 +44,13 @@ const apexTranslations = {
44
44
  "userDataRights.privacyCenter.restriction.title": "Restrict processing (Art. 18)",
45
45
  "userDataRights.privacyCenter.restriction.explainer": "Freeze your account: processing of your data is paused and you are signed out. Lifting the restriction afterwards is only possible via support.",
46
46
  "userDataRights.privacyCenter.restriction.restrict": "Restrict account",
47
+ "userDataRights.privacyCenter.restriction.dialogTitle": "Restrict your account?",
47
48
  "userDataRights.privacyCenter.restriction.dialogDescription": "You will be signed out immediately and cannot sign in again until support lifts the restriction.",
48
49
  "userDataRights.privacyCenter.deletion.title": "Delete account (Art. 17)",
49
50
  "userDataRights.privacyCenter.deletion.explainer": "Request deletion of your account. Until the grace period ends you can cancel the deletion.",
50
51
  "userDataRights.privacyCenter.deletion.delete": "Delete account",
51
52
  "userDataRights.privacyCenter.deletion.cancel": "Cancel deletion",
53
+ "userDataRights.privacyCenter.deletion.dialogTitle": "Delete your account?",
52
54
  "userDataRights.privacyCenter.deletion.dialogDescription": "Confirming starts the deletion grace period. You can cancel the deletion until it ends.",
53
55
  "userDataRights.errors.download.notFound": "The download link is invalid or belongs to a different account.",
54
56
  "userDataRights.errors.download.expired": "Your download has expired. Please request a new export.",
@@ -36,6 +36,9 @@ export function createUserProfileFeature() {
36
36
  r.translations({
37
37
  keys: {
38
38
  "screen:profile.title": { en: "Profile" },
39
+ "profile.screen.profile.subtitle": {
40
+ en: "Change your password or email, or request account deletion.",
41
+ },
39
42
  "profile.email.title": { en: "Email address" },
40
43
  "profile.password.title": { en: "Password" },
41
44
  "profile.danger.title": { en: "Delete account" },
@@ -71,9 +74,7 @@ export function createUserProfileFeature() {
71
74
  // Self-service settings-area screen, placed by the consuming app's own
72
75
  // r.nav() (see samples/apps/use-all-bundled's screens-feature.ts).
73
76
  dormant: true,
74
- description: "Self-service account page: change password, change email (with re-auth and " +
75
- "a verification-mail follow-up), and request or cancel account deletion " +
76
- "(user-data-rights grace period).",
77
+ description: "profile.screen.profile.subtitle",
77
78
  fieldLabels: {
78
79
  gracePeriodEnd: i18nKey("profile.danger.gracePeriodEnd"),
79
80
  },
@@ -8,6 +8,7 @@
8
8
  export const defaultTranslations = {
9
9
  en: {
10
10
  "screen:profile.title": "Profile",
11
+ "profile.screen.profile.subtitle": "Change your password or email, or request account deletion.",
11
12
  "profile.email.title": "Email address",
12
13
  "profile.email.current": "Current email",
13
14
  "profile.email.new": "New email",
@@ -30,6 +31,6 @@ export const defaultTranslations = {
30
31
  "profile.errors.generic": "Something went wrong.",
31
32
  "profile.errors.emailUnchanged": "That is already your email address.",
32
33
  "user.errors.emailAlreadyExists": "This email address is already in use.",
33
- "auth.errors.invalidCredentials": "Email or password incorrect.",
34
+ "auth.errors.invalidCredentials": "Invalid email or password.",
34
35
  },
35
36
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.329.0",
3
+ "version": "0.330.2",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -101,6 +101,10 @@
101
101
  "types": "./dist/cap-overview/web/index.d.ts",
102
102
  "default": "./dist/cap-overview/web/index.js"
103
103
  },
104
+ "./metrics": {
105
+ "types": "./dist/metrics/index.d.ts",
106
+ "default": "./dist/metrics/index.js"
107
+ },
104
108
  "./custom-fields": {
105
109
  "types": "./dist/custom-fields/index.d.ts",
106
110
  "default": "./dist/custom-fields/index.js"
@@ -479,12 +483,12 @@
479
483
  }
480
484
  },
481
485
  "dependencies": {
482
- "@cosmicdrift/kumiko-dispatcher-live": "0.329.0",
483
- "@cosmicdrift/kumiko-framework": "0.329.0",
484
- "@cosmicdrift/kumiko-headless": "0.329.0",
485
- "@cosmicdrift/kumiko-renderer": "0.329.0",
486
- "@cosmicdrift/kumiko-renderer-web": "0.329.0",
487
- "@cosmicdrift/kumiko-types": "0.329.0",
486
+ "@cosmicdrift/kumiko-dispatcher-live": "0.330.2",
487
+ "@cosmicdrift/kumiko-framework": "0.330.2",
488
+ "@cosmicdrift/kumiko-headless": "0.330.2",
489
+ "@cosmicdrift/kumiko-renderer": "0.330.2",
490
+ "@cosmicdrift/kumiko-renderer-web": "0.330.2",
491
+ "@cosmicdrift/kumiko-types": "0.330.2",
488
492
  "@mollie/api-client": "^4.5.0",
489
493
  "@node-rs/argon2": "^2.0.2",
490
494
  "@types/mailparser": "^3.4.6",
@@ -589,6 +593,10 @@
589
593
  "types": "./dist/cap-overview/web/index.d.ts",
590
594
  "default": "./dist/cap-overview/web/index.js"
591
595
  },
596
+ "./metrics": {
597
+ "types": "./dist/metrics/index.d.ts",
598
+ "default": "./dist/metrics/index.js"
599
+ },
592
600
  "./custom-fields": {
593
601
  "types": "./dist/custom-fields/index.d.ts",
594
602
  "default": "./dist/custom-fields/index.js"
@@ -975,8 +983,8 @@
975
983
  ],
976
984
  "devDependencies": {
977
985
  "@testing-library/user-event": "^14.6.1",
978
- "@cosmicdrift/kumiko-locale-de": "0.329.0",
979
- "@cosmicdrift/kumiko-locale-es": "0.329.0",
986
+ "@cosmicdrift/kumiko-locale-de": "0.330.2",
987
+ "@cosmicdrift/kumiko-locale-es": "0.330.2",
980
988
  "jsqr": "^1.4.0"
981
989
  }
982
990
  }
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "version": "0.330.0",
4
+ "type": "breaking",
5
+ "title": "admin-shell requires the metrics and metrics-system features and builds its overview dashboards from a metrics list",
6
+ "detail": "Dashboard panels gain the chart kinds `stacked-bars`, `segment-bars` and `stacked-area`, a subtitle, per-series tones, static query `params`, `ignoreScreenFilter`, an empty label and hint, a `span` (half/full width), a stat `sparklineField` and static `tone`, a `negative` tone, an unlabelled `stat-group` KPI strip and bar/badge list columns. Every panel shows skeleton, empty and error states with retry. A screen can declare a `timeRange` control and a `scope` badge and notice. The admin-shell overview screens use all of this and show the metrics of the new `metrics` and `metrics-system` features. `deliveries-by-channel` now labels the email, in-app and push channels.",
7
+ "migration": "Mount createMetricsFeature({ metrics: DEFAULT_METRICS }) and createSystemMetricsFeature({ metrics: DEFAULT_METRICS }) from the metrics bundled feature before admin-shell. DEFAULT_METRICS also requires the delivery, sessions, jobs and tenant features.\n\nIf you do not mount all of them, pass the same reduced list to all three: createMetricsFeature({ metrics }), createSystemMetricsFeature({ metrics }) and createAdminShellFeature({ metrics }). Overview panels exist only for metric ids in that list."
8
+ },
9
+ {
10
+ "version": "0.330.0",
11
+ "type": "improvement",
12
+ "title": "Bundled screen subtitles (admin-shell, jobs, auth-mfa, tier-engine, user-profile) are i18n keys with de and es translations",
13
+ "migration": "Additive. Apps that override these screen descriptions keep working; apps that asserted the old English description text in tests now see the translated subtitle."
14
+ },
2
15
  {
3
16
  "version": "0.241.0",
4
17
  "type": "fix",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.330.0",
4
+ "type": "fix",
5
+ "title": "The switch-plan error for a subscription with a scheduled cancellation says \"scheduled to end\", matching the plan badge",
6
+ "detail": "The error for switching plans on a subscription with a scheduled cancellation now says \"scheduled to end\", like the plan badge, and the invalid-credentials message reads the same in every source that registers it."
7
+ },
2
8
  {
3
9
  "version": "0.321.0",
4
10
  "type": "breaking",
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "version": "0.330.1",
4
+ "type": "fix",
5
+ "title": "blockDelete entities without anonymize fields no longer log skips or boot warnings"
6
+ },
2
7
  {
3
8
  "version": "0.315.0",
4
9
  "type": "breaking",
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.330.0",
4
+ "type": "improvement",
5
+ "title": "store_delivery_attempts gets an index on (tenant_id, created_at) (fw#3396)",
6
+ "detail": "Windowed per-tenant dashboard aggregates over delivery attempts (metrics feature) filter on tenant_id and created_at.",
7
+ "migration": "Run `kumiko migrate generate` and apply the migration: it adds `store_delivery_attempts_tenant_id_created_at_idx`."
8
+ },
2
9
  {
3
10
  "version": "0.323.0",
4
11
  "type": "improvement",
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.330.0",
4
+ "type": "improvement",
5
+ "title": "store_job_runs gets an index on started_at (fw#3396)",
6
+ "detail": "Windowed dashboard aggregates over job runs (metrics feature) filter on started_at; the index keeps them off a sequential scan.",
7
+ "migration": "Run `kumiko migrate generate` and apply the migration: it adds `store_job_runs_started_at_idx`."
8
+ },
2
9
  {
3
10
  "version": "0.291.0",
4
11
  "type": "improvement",
@@ -0,0 +1,15 @@
1
+ [
2
+ {
3
+ "version": "0.330.0",
4
+ "type": "fix",
5
+ "title": "deliveries-by-channel shows readable channel labels instead of raw channel ids",
6
+ "detail": "Dashboard panels gain the chart kinds `stacked-bars`, `segment-bars` and `stacked-area`, a subtitle, per-series tones, static query `params`, `ignoreScreenFilter`, an empty label and hint, a `span` (half/full width), a stat `sparklineField` and static `tone`, a `negative` tone, an unlabelled `stat-group` KPI strip and bar/badge list columns. Every panel shows skeleton, empty and error states with retry. A screen can declare a `timeRange` control and a `scope` badge and notice. The admin-shell overview screens use all of this and show the metrics of the new `metrics` and `metrics-system` features. `deliveries-by-channel` now labels the email, in-app and push channels."
7
+ },
8
+ {
9
+ "version": "0.330.0",
10
+ "type": "improvement",
11
+ "title": "Add metrics feature: declarative dashboard metrics over TenantDb.aggregate (fw#3396)",
12
+ "detail": "New bundled features `metrics` (tenant scope, `access.admin`) and `metrics-system` (platform-wide, `access.systemAdmin`). A metric is declared once with `defineMetric` (source table, measure, optional timeField, window, bucket, groupBy, stackBy, where) and becomes the query `<feature>:query:<metric.id>` returning a `MetricResult` (total, delta against the previous period, bucketed series, group rows, segments). Definitions are validated when the feature is built. Eight default metrics ship: job-runs-by-status, failed-job-runs, tenant-job-failures, deliveries-by-channel, failed-deliveries, active-users, active-tenants, audit-writes. Pass your own list via `createMetricsFeature({ metrics })`.",
13
+ "migration": "Opt-in: mount `createMetricsFeature(...)` and/or `createSystemMetricsFeature(...)`. The new indexes on jobs, delivery and sessions need `kumiko migrate generate`."
14
+ }
15
+ ]
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "version": "0.330.0",
4
+ "type": "fix",
5
+ "title": "The token list status column shows translated labels instead of raw status values"
6
+ },
2
7
  {
3
8
  "version": "0.202.0",
4
9
  "type": "breaking",
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.330.0",
4
+ "type": "improvement",
5
+ "title": "store_user_sessions gets an index on (tenant_id, last_seen_at) (fw#3396)",
6
+ "detail": "Active-user dashboard aggregates (metrics feature) filter on tenant_id and last_seen_at.",
7
+ "migration": "Run `kumiko migrate generate` and apply the migration: it adds an index on `store_user_sessions (tenant_id, last_seen_at)`."
8
+ },
2
9
  {
3
10
  "version": "0.259.0",
4
11
  "type": "breaking",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.330.0",
4
+ "type": "fix",
5
+ "title": "The privacy-center restriction and deletion dialog titles are registered in English by the feature",
6
+ "detail": "The new i18n-parity test in `use-all-bundled` harvests the English copy from the real registrations (feature translations, client plugins, renderer defaults, mail templates) and fails with `key -> locale` when `locale-de` or `locale-es` lacks a key or an en-catalog drifts. This added the missing German and Spanish copy for billing plans, the privacy-center status field and many more keys, and removed 110 translation keys no feature registers any more. German uses \"Ereignisprotokoll\" for the audit log everywhere. `userDataRights.privacyCenter.restriction.dialogTitle` and `.deletion.dialogTitle` are registered in English by the feature."
7
+ },
2
8
  {
3
9
  "version": "0.320.0",
4
10
  "type": "fix",