@cosmicdrift/kumiko-samples 0.383.0 → 0.384.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 (66) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +6 -1
  3. package/packages/bundled-features/src/audit/changes.json +6 -0
  4. package/packages/bundled-features/src/audit/event-classification.ts +83 -0
  5. package/packages/bundled-features/src/audit/feature.ts +3 -2
  6. package/packages/bundled-features/src/audit/handlers/audit-events.ts +63 -5
  7. package/packages/bundled-features/src/audit/handlers/details-all-tenants.query.ts +6 -5
  8. package/packages/bundled-features/src/audit/handlers/details.query.ts +12 -6
  9. package/packages/bundled-features/src/audit/handlers/list-all-tenants.query.ts +20 -12
  10. package/packages/bundled-features/src/audit/handlers/list.query.ts +14 -3
  11. package/packages/bundled-features/src/audit/screens.ts +32 -4
  12. package/packages/bundled-features/src/audit/web/audit-event-cell.tsx +22 -0
  13. package/packages/bundled-features/src/audit/web/audit-event-labels.ts +30 -0
  14. package/packages/bundled-features/src/audit/web/client-plugin.tsx +10 -1
  15. package/packages/bundled-features/src/audit/web/constants.ts +11 -0
  16. package/packages/bundled-features/src/audit/web/i18n.ts +48 -1
  17. package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
  18. package/packages/bundled-features/src/auth-email-password/feature.ts +3 -2
  19. package/packages/bundled-features/src/auth-email-password/handlers/login-options.query.ts +19 -0
  20. package/packages/bundled-features/src/auth-email-password/index.ts +7 -1
  21. package/packages/bundled-features/src/auth-email-password/{auth-paths.ts → web/auth-paths.ts} +7 -4
  22. package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +7 -0
  23. package/packages/bundled-features/src/auth-email-password/web/constants.ts +1 -0
  24. package/packages/bundled-features/src/auth-email-password/web/default-auth-pages-gate.tsx +31 -0
  25. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +68 -42
  26. package/packages/bundled-features/src/jobs/changes.json +6 -0
  27. package/packages/bundled-features/src/jobs/feature.ts +34 -127
  28. package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +98 -0
  29. package/packages/bundled-features/src/jobs/handlers/consumers.query.ts +137 -0
  30. package/packages/bundled-features/src/jobs/screens.ts +262 -0
  31. package/packages/bundled-features/src/jobs/web/constants.ts +29 -0
  32. package/packages/bundled-features/src/jobs/web/i18n.ts +41 -0
  33. package/packages/bundled-features/src/secrets/i18n.ts +0 -3
  34. package/packages/bundled-features/src/sessions/changes.json +6 -0
  35. package/packages/bundled-features/src/sessions/constants.ts +3 -1
  36. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +7 -3
  37. package/packages/bundled-features/src/sessions/handlers/list.query.ts +16 -10
  38. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +15 -9
  39. package/packages/bundled-features/src/sessions/screens.ts +23 -8
  40. package/packages/bundled-features/src/sessions/web/client-plugin.tsx +17 -0
  41. package/packages/bundled-features/src/sessions/web/constants.ts +3 -0
  42. package/packages/bundled-features/src/sessions/web/default-translations.ts +8 -0
  43. package/packages/bundled-features/src/sessions/web/i18n.ts +5 -1
  44. package/packages/bundled-features/src/sessions/web/index.ts +1 -0
  45. package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +19 -0
  46. package/packages/bundled-features/src/sessions/web/user-agent.ts +74 -0
  47. package/packages/bundled-features/src/template-resolver/changes.json +6 -0
  48. package/packages/bundled-features/src/template-resolver/feature.ts +2 -0
  49. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +34 -1
  50. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-counts.query.ts +53 -0
  51. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +51 -0
  52. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +7 -38
  53. package/packages/bundled-features/src/template-resolver/web/qualified-names.ts +1 -0
  54. package/packages/bundled-features/src/user-data-rights/changes.json +18 -0
  55. package/packages/bundled-features/src/user-data-rights/feature.ts +33 -33
  56. package/packages/bundled-features/src/user-data-rights/web/i18n.ts +4 -1
  57. package/samples/apps/showcase/src/features/items/schema/item.ts +8 -2
  58. package/samples/apps/ui-walkthrough/src/features/tasks/schema/task.ts +2 -2
  59. package/samples/apps/use-all-bundled/feature-manifest.json +4 -0
  60. package/samples/apps/use-all-bundled/src/app/client.tsx +2 -0
  61. package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +2 -1
  62. package/samples/apps/use-all-bundled/src/app/invoice-reports-feature.ts +30 -0
  63. package/samples/apps/use-all-bundled/src/app/seed.ts +51 -0
  64. package/samples/apps/workspaces/src/features/demo/schema/order.ts +2 -2
  65. package/samples/recipes/custom-fields-basic/src/feature.ts +13 -3
  66. package/samples/recipes/designer-demo/src/feature.ts +6 -2
@@ -399,7 +399,9 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
399
399
  },
400
400
  },
401
401
  dormant: true,
402
- description: "userDataRights.privacyCenter.subtitle",
402
+ description: showDeletion
403
+ ? "userDataRights.privacyCenter.subtitle"
404
+ : "userDataRights.privacyCenter.subtitleWithoutDeletion",
403
405
  fieldLabels: {
404
406
  status: i18nKey("userDataRights.privacyCenter.field.status"),
405
407
  gracePeriodEnd: i18nKey("userDataRights.privacyCenter.field.gracePeriodEnd"),
@@ -425,6 +427,17 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
425
427
  renderer: { format: "enumOption", keyPrefix: STATUS_OPTION_KEY_PREFIX },
426
428
  },
427
429
  ],
430
+ actions: [
431
+ {
432
+ id: "restrict",
433
+ icon: "lock",
434
+ label: i18nKey("userDataRights.privacyCenter.restriction.restrict"),
435
+ handler: UserDataRightsHandlers.restrictAccount,
436
+ confirm: i18nKey("userDataRights.privacyCenter.restriction.dialogDescription"),
437
+ visible: { field: "status", ne: "restricted" },
438
+ style: "secondary",
439
+ },
440
+ ],
428
441
  },
429
442
  ...(showDeletion
430
443
  ? [
@@ -441,43 +454,30 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
441
454
  visible: { field: "status", eq: "deletionRequested" },
442
455
  },
443
456
  ],
457
+ actions: [
458
+ {
459
+ id: "request-deletion",
460
+ label: i18nKey("userDataRights.privacyCenter.deletion.delete"),
461
+ handler: UserDataRightsHandlers.requestDeletion,
462
+ confirm: i18nKey("userDataRights.privacyCenter.deletion.dialogDescription"),
463
+ visible: { field: "status", ne: "deletionRequested" },
464
+ style: "danger" as const,
465
+ },
466
+ {
467
+ id: "cancel-deletion",
468
+ label: i18nKey("userDataRights.privacyCenter.deletion.cancel"),
469
+ handler: UserDataRightsHandlers.cancelDeletion,
470
+ visible: { field: "status", eq: "deletionRequested" },
471
+ style: "secondary" as const,
472
+ // Declared: the id's last segment ("deletion") would otherwise resolve to the destructive trash icon.
473
+ icon: "x" as const,
474
+ },
475
+ ],
444
476
  },
445
477
  ]
446
478
  : []),
447
479
  ],
448
480
  },
449
- actions: [
450
- {
451
- id: "restrict",
452
- icon: "lock",
453
- label: i18nKey("userDataRights.privacyCenter.restriction.restrict"),
454
- handler: UserDataRightsHandlers.restrictAccount,
455
- confirm: i18nKey("userDataRights.privacyCenter.restriction.dialogDescription"),
456
- visible: { field: "status", ne: "restricted" },
457
- style: "danger",
458
- },
459
- ...(showDeletion
460
- ? [
461
- {
462
- id: "request-deletion",
463
- label: i18nKey("userDataRights.privacyCenter.deletion.delete"),
464
- handler: UserDataRightsHandlers.requestDeletion,
465
- confirm: i18nKey("userDataRights.privacyCenter.deletion.dialogDescription"),
466
- visible: { field: "status", ne: "deletionRequested" },
467
- style: "danger" as const,
468
- },
469
- {
470
- id: "cancel-deletion",
471
- label: i18nKey("userDataRights.privacyCenter.deletion.cancel"),
472
- handler: UserDataRightsHandlers.cancelDeletion,
473
- visible: { field: "status", eq: "deletionRequested" },
474
- style: "secondary" as const,
475
- // Declared: the id's last segment ("deletion") would otherwise resolve to the destructive trash icon.
476
- icon: "x" as const,
477
- },
478
- ]
479
- : []),
480
- ],
481
481
  });
482
482
 
483
483
  // Magic-link path (anonymous): the email link carries the token as a
@@ -14,7 +14,10 @@ export const USER_DATA_RIGHTS_I18N: Readonly<Record<string, LocalizedString>> =
14
14
  "screen:download-attempt-list.title": { en: "Download attempts" },
15
15
  "screen:privacy-center.title": { en: "Privacy" },
16
16
  "userDataRights.privacyCenter.subtitle": {
17
- en: "Logged-in GDPR self-service page where a user requests and downloads a data export (Art. 20), restricts processing of their account (Art. 18) and requests its deletion (Art. 17).",
17
+ en: "Download a copy of your data (Art. 20), restrict processing of your account (Art. 18) or delete it (Art. 17).",
18
+ },
19
+ "userDataRights.privacyCenter.subtitleWithoutDeletion": {
20
+ en: "Download a copy of your data (Art. 20) or restrict processing of your account (Art. 18).",
18
21
  },
19
22
  "user-data-rights:entity:export-job:field:userId": { en: "User" },
20
23
  "user-data-rights:entity:export-job:field:status": { en: "Status" },
@@ -19,8 +19,14 @@ export const itemEntity = {
19
19
  description:
20
20
  "A showcase task item carrying a title, free-text notes, a numeric priority, a done flag, a due date and a draft/active/blocked/done status, plus an optional parent item and a list of related items.",
21
21
  fields: {
22
- title: { type: "text", required: true, sortable: true, searchable: true },
23
- notes: { type: "text", multiline: { rows: 4 } },
22
+ title: {
23
+ type: "text",
24
+ required: true,
25
+ sortable: true,
26
+ searchable: true,
27
+ allowPlaintext: "is_business_data",
28
+ },
29
+ notes: { type: "text", multiline: { rows: 4 }, allowPlaintext: "is_business_data" },
24
30
  priority: { type: "number", default: 1, sortable: true, filterable: true },
25
31
  isDone: { type: "boolean", default: false, sortable: true, filterable: true },
26
32
  dueDate: { type: "date", sortable: true, filterable: true },
@@ -29,12 +29,12 @@ export const taskEntity = {
29
29
  table: "read_ui_walkthrough_tasks",
30
30
 
31
31
  fields: {
32
- title: { type: "text", required: true, sortable: true },
32
+ title: { type: "text", required: true, sortable: true, allowPlaintext: "is_business_data" },
33
33
  status: { type: "select", options: TASK_STATUSES, display: "radio", sortable: true },
34
34
  area: { type: "select", options: TASK_AREAS },
35
35
  priority: { type: "number" },
36
36
  isUrgent: { type: "boolean", default: false },
37
- notes: { type: "text" },
37
+ notes: { type: "text", allowPlaintext: "is_business_data" },
38
38
  },
39
39
  } as unknown as EntityDefinition;
40
40
 
@@ -1473,6 +1473,10 @@
1473
1473
  "configKeys": [],
1474
1474
  "secrets": [],
1475
1475
  "writeHandlers": [
1476
+ "jobs:write:consumer-disable",
1477
+ "jobs:write:consumer-enable",
1478
+ "jobs:write:consumer-restart",
1479
+ "jobs:write:consumer-skip",
1476
1480
  "jobs:write:retry",
1477
1481
  "jobs:write:trigger"
1478
1482
  ],
@@ -27,6 +27,7 @@ import { deliveryClient } from "@cosmicdrift/kumiko-bundled-features/delivery/we
27
27
  import { featureTogglesClient } from "@cosmicdrift/kumiko-bundled-features/feature-toggles/web";
28
28
  import { foldersClient } from "@cosmicdrift/kumiko-bundled-features/folders/web";
29
29
  import { jobsClient } from "@cosmicdrift/kumiko-bundled-features/jobs/web";
30
+ import { sessionsClient } from "@cosmicdrift/kumiko-bundled-features/sessions/web";
30
31
  import { tagsClient } from "@cosmicdrift/kumiko-bundled-features/tags/web";
31
32
  import { textBlocksClient } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
32
33
  import { tenantClient } from "@cosmicdrift/kumiko-bundled-features/tenant/web";
@@ -103,6 +104,7 @@ createKumikoApp({
103
104
  adminShellClient(),
104
105
  tenantClient(),
105
106
  auditClient(),
107
+ sessionsClient(),
106
108
  billingFoundationClient(),
107
109
  jobsClient(),
108
110
  deliveryClient(),
@@ -15,6 +15,7 @@ import type { RunDevAppOptions } from "@cosmicdrift/kumiko-dev-server";
15
15
  import { SYSTEM_TENANT_ID, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
16
16
  import { AUTH_COMPOSE_OPTIONS, BOOT_FEATURES } from "../run-config";
17
17
  import { ADMIN_EMAIL, ADMIN_PASSWORD, BETA_TENANT_ID, DEV_TENANT_ID } from "./auth-constants";
18
+ import { invoiceReportsFeature } from "./invoice-reports-feature";
18
19
  import { notesFeature } from "./notes-feature";
19
20
  import { appScreensFeature } from "./screens-feature";
20
21
  import { seedScreenshotData } from "./seed";
@@ -27,7 +28,7 @@ import { seedScreenshotData } from "./seed";
27
28
  export const devAppOptions = {
28
29
  // notesFeature is dev-only (app-owned tag host) — see notes-feature.ts for why
29
30
  // it stays out of APP_FEATURES. appScreensFeature places the user-profile screen.
30
- features: [...BOOT_FEATURES, appScreensFeature, notesFeature],
31
+ features: [...BOOT_FEATURES, appScreensFeature, notesFeature, invoiceReportsFeature],
31
32
  featureToggles: true,
32
33
  port: Number.parseInt(process.env["PORT"] ?? "4186", 10),
33
34
  clientEntry: "./src/app/client.tsx",
@@ -0,0 +1,30 @@
1
+ // @runtime dev
2
+ //
3
+ // Dev-/screenshot-only host for realistic event consumers. The consumers never
4
+ // receive an event here; the screenshot seed parks them in the dead/disabled
5
+ // states the jobs:event-consumers screen is meant to show.
6
+
7
+ import { entityEventName } from "@cosmicdrift/kumiko-framework/db";
8
+ import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
9
+
10
+ export const INVOICE_TOTALS_CONSUMER = "invoice-totals";
11
+ export const OVERDUE_REMINDERS_CONSUMER = "overdue-reminders";
12
+ export const CUSTOMER_STATEMENTS_CONSUMER = "customer-statements";
13
+
14
+ const noop = async (): Promise<void> => {};
15
+
16
+ export const invoiceReportsFeature: FeatureDefinition = defineFeature("invoice-reports", (r) => {
17
+ r.describe(
18
+ "Dev-only host for the invoice read models whose consumer state the screenshot seed sets.",
19
+ );
20
+ for (const name of [
21
+ INVOICE_TOTALS_CONSUMER,
22
+ OVERDUE_REMINDERS_CONSUMER,
23
+ CUSTOMER_STATEMENTS_CONSUMER,
24
+ ]) {
25
+ r.multiStreamProjection({
26
+ name,
27
+ apply: { [entityEventName("invoice", "created")]: noop },
28
+ });
29
+ }
30
+ });
@@ -30,6 +30,7 @@ import {
30
30
  type DbConnection,
31
31
  fetchOne,
32
32
  insertOne,
33
+ upsertOnConflict,
33
34
  } from "@cosmicdrift/kumiko-framework/db";
34
35
  import {
35
36
  createEntityExecutor,
@@ -37,11 +38,17 @@ import {
37
38
  SYSTEM_TENANT_ID,
38
39
  type TenantId,
39
40
  } from "@cosmicdrift/kumiko-framework/engine";
41
+ import {
42
+ ConsumerStatuses,
43
+ eventConsumerStateTable,
44
+ SHARED_INSTANCE_SENTINEL,
45
+ } from "@cosmicdrift/kumiko-framework/pipeline";
40
46
  import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
41
47
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
42
48
  import { unsafeCreateEntityTable } from "@cosmicdrift/kumiko-testing/stack";
43
49
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
44
50
  import { ADMIN_EMAIL, BETA_TENANT_ID, DEMO_NOTE_ID, DEV_TENANT_ID } from "./auth-constants";
51
+ import { INVOICE_TOTALS_CONSUMER, OVERDUE_REMINDERS_CONSUMER } from "./invoice-reports-feature";
45
52
  import { noteEntity } from "./notes-feature";
46
53
  import { seedPdfDocuments } from "./seed-documents";
47
54
 
@@ -266,6 +273,48 @@ const PRIVACY_BODY = [
266
273
  "Access, rectification, erasure, restriction, portability, objection.",
267
274
  ].join("\n");
268
275
 
276
+ // Dispatcher state, not an event-sourced table: parks two invoice consumers the
277
+ // way a failing handler and an operator pause would leave them, so the
278
+ // jobs:event-consumers screens have a dead and a paused row to show.
279
+ async function seedEventConsumerStates(stack: Parameters<SeedFn>[0]): Promise<void> {
280
+ const qualifiedNameOf = (consumer: string): string => {
281
+ const found = [...stack.registry.getAllMultiStreamProjections().keys()].find(
282
+ (name) => name === consumer || name.endsWith(`:${consumer}`),
283
+ );
284
+ if (!found) throw new Error(`seedEventConsumerStates: consumer "${consumer}" not registered`);
285
+ return found;
286
+ };
287
+ const parked = [
288
+ {
289
+ consumer: INVOICE_TOTALS_CONSUMER,
290
+ status: ConsumerStatuses.dead,
291
+ attempts: 5,
292
+ lastError:
293
+ 'TypeError: Cannot read properties of undefined (reading "amount") while applying invoice.created (event 48213): payload.lineItems[2].netPrice.amount is missing',
294
+ },
295
+ {
296
+ consumer: OVERDUE_REMINDERS_CONSUMER,
297
+ status: ConsumerStatuses.disabled,
298
+ attempts: 0,
299
+ lastError: null,
300
+ },
301
+ ];
302
+ for (const { consumer, status, attempts, lastError } of parked) {
303
+ await upsertOnConflict(
304
+ stack.db,
305
+ eventConsumerStateTable,
306
+ {
307
+ name: qualifiedNameOf(consumer),
308
+ instanceId: SHARED_INSTANCE_SENTINEL,
309
+ status,
310
+ attempts,
311
+ lastError,
312
+ },
313
+ { conflictKeys: ["name", "instanceId"], update: { status, attempts, lastError } },
314
+ );
315
+ }
316
+ }
317
+
269
318
  export const seedScreenshotData: SeedFn = async (stack) => {
270
319
  const devTenant = DEV_TENANT_ID as TenantId;
271
320
  const betaTenant = BETA_TENANT_ID as TenantId;
@@ -384,6 +433,8 @@ export const seedScreenshotData: SeedFn = async (stack) => {
384
433
  // custom-fields + folders extension sections on note-edit.
385
434
  await seedCustomFieldsAndFolders(stack, devTenant, devAdminId);
386
435
 
436
+ await seedEventConsumerStates(stack);
437
+
387
438
  // personal-access-tokens list for the admin (active tenant = dev).
388
439
  await seedApiTokens(stack.db, devTenant);
389
440
 
@@ -12,8 +12,8 @@ import type {
12
12
  export const orderEntity = {
13
13
  table: "read_ws_orders",
14
14
  fields: {
15
- label: { type: "text", required: true, sortable: true },
16
- status: { type: "text", sortable: true },
15
+ label: { type: "text", required: true, sortable: true, allowPlaintext: "is_business_data" },
16
+ status: { type: "text", sortable: true, allowPlaintext: "is_business_data" },
17
17
  notes: { type: "text", allowPlaintext: "is_business_data" },
18
18
  },
19
19
  } as unknown as EntityDefinition;
@@ -24,7 +24,7 @@ import {
24
24
  wireCustomFieldsFor,
25
25
  } from "@cosmicdrift/kumiko-bundled-features/custom-fields";
26
26
  import { buildEntityTable, createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
27
- import { createEntity, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
27
+ import { createEntity, createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
28
28
  import { z } from "zod";
29
29
 
30
30
  // --- Entity ---
@@ -39,7 +39,12 @@ import { z } from "zod";
39
39
  export const propertyEntity = createEntity({
40
40
  table: "read_sample_cf_properties",
41
41
  fields: {
42
- name: { type: "text", required: true, maxLength: 200 },
42
+ name: createTextField({
43
+ required: true,
44
+ maxLength: 200,
45
+ personal: false,
46
+ reason: "property_name_is_business_data",
47
+ }),
43
48
  customFields: customFieldsField(),
44
49
  },
45
50
  });
@@ -59,7 +64,12 @@ export const propertyFeature = defineFeature("property-management", (r) => {
59
64
  name: "property",
60
65
  table: "read_sample_cf_properties",
61
66
  fields: {
62
- name: { type: "text", required: true, maxLength: 200 },
67
+ name: createTextField({
68
+ required: true,
69
+ maxLength: 200,
70
+ personal: false,
71
+ reason: "property_name_is_business_data",
72
+ }),
63
73
  customFields: { type: "jsonb" },
64
74
  },
65
75
  });
@@ -6,7 +6,7 @@
6
6
  // - nav → ID/Label/Screen-Card
7
7
  // Plus optionalRequires + metric als JSON-Dump-Fallback.
8
8
 
9
- import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
9
+ import { createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
10
10
  import { z } from "zod";
11
11
 
12
12
  defineFeature("designerDemo", (r) => {
@@ -15,7 +15,11 @@ defineFeature("designerDemo", (r) => {
15
15
  r.entity({
16
16
  name: "task",
17
17
  fields: {
18
- title: { type: "text", required: true },
18
+ title: createTextField({
19
+ required: true,
20
+ personal: false,
21
+ reason: "demo_task_title_is_not_personal_data",
22
+ }),
19
23
  done: { type: "boolean", default: false },
20
24
  priority: { type: "select", options: ["low", "medium", "high"], default: "medium" },
21
25
  },