@things-factory/worklist 10.1.5 → 10.1.7

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 (130) hide show
  1. package/dist-client/components/activity-approval-ribon.js +17 -2
  2. package/dist-client/components/activity-approval-ribon.js.map +1 -1
  3. package/dist-client/components/activity-instance-preview.js +5 -4
  4. package/dist-client/components/activity-instance-preview.js.map +1 -1
  5. package/dist-client/components/activity-starter-form.js +14 -10
  6. package/dist-client/components/activity-starter-form.js.map +1 -1
  7. package/dist-client/components/activity-thread-timeline.d.ts +2 -2
  8. package/dist-client/components/decision-slots.d.ts +118 -0
  9. package/dist-client/components/decision-slots.js +125 -0
  10. package/dist-client/components/decision-slots.js.map +1 -0
  11. package/dist-client/components/mutation-outcome.d.ts +57 -0
  12. package/dist-client/components/mutation-outcome.js +78 -0
  13. package/dist-client/components/mutation-outcome.js.map +1 -0
  14. package/dist-client/components/say-outcome.d.ts +25 -0
  15. package/dist-client/components/say-outcome.js +51 -0
  16. package/dist-client/components/say-outcome.js.map +1 -0
  17. package/dist-client/pages/activity/activity-list-page.js +28 -12
  18. package/dist-client/pages/activity/activity-list-page.js.map +1 -1
  19. package/dist-client/pages/activity/activity-model-item-list.js +4 -3
  20. package/dist-client/pages/activity/activity-model-item-list.js.map +1 -1
  21. package/dist-client/pages/activity/activity-page.d.ts +2 -2
  22. package/dist-client/pages/activity/activity-page.js +25 -21
  23. package/dist-client/pages/activity/activity-page.js.map +1 -1
  24. package/dist-client/pages/activity-approval/activity-approval-importer.js +4 -3
  25. package/dist-client/pages/activity-approval/activity-approval-importer.js.map +1 -1
  26. package/dist-client/pages/activity-approval/activity-approval-list-page.js +8 -9
  27. package/dist-client/pages/activity-approval/activity-approval-list-page.js.map +1 -1
  28. package/dist-client/pages/activity-approval/activity-approval-page.d.ts +75 -1
  29. package/dist-client/pages/activity-approval/activity-approval-page.js +487 -57
  30. package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
  31. package/dist-client/pages/activity-catalog/activity-catalog-page.d.ts +11 -0
  32. package/dist-client/pages/activity-catalog/activity-catalog-page.js +48 -1
  33. package/dist-client/pages/activity-catalog/activity-catalog-page.js.map +1 -1
  34. package/dist-client/pages/activity-instance/activity-instance-start-page.js +24 -18
  35. package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
  36. package/dist-client/pages/activity-instance/activity-instance-view.js +1 -0
  37. package/dist-client/pages/activity-instance/activity-instance-view.js.map +1 -1
  38. package/dist-client/pages/activity-stats/activity-stats-importer.js +4 -3
  39. package/dist-client/pages/activity-stats/activity-stats-importer.js.map +1 -1
  40. package/dist-client/pages/activity-stats/activity-stats-list-page.js +8 -9
  41. package/dist-client/pages/activity-stats/activity-stats-list-page.js.map +1 -1
  42. package/dist-client/pages/activity-template/activity-template-importer.js +4 -3
  43. package/dist-client/pages/activity-template/activity-template-importer.js.map +1 -1
  44. package/dist-client/pages/activity-template/activity-template-list-page.js +8 -9
  45. package/dist-client/pages/activity-template/activity-template-list-page.js.map +1 -1
  46. package/dist-client/pages/activity-template/activity-template-model-item-list.js +4 -3
  47. package/dist-client/pages/activity-template/activity-template-model-item-list.js.map +1 -1
  48. package/dist-client/pages/activity-thread/activity-thread-importer.js +4 -3
  49. package/dist-client/pages/activity-thread/activity-thread-importer.js.map +1 -1
  50. package/dist-client/pages/activity-thread/activity-thread-list-page.js +8 -9
  51. package/dist-client/pages/activity-thread/activity-thread-list-page.js.map +1 -1
  52. package/dist-client/pages/activity-thread/activity-thread-page.d.ts +1 -1
  53. package/dist-client/pages/activity-thread/activity-thread-page.js +22 -20
  54. package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
  55. package/dist-client/pages/activity-thread/activity-thread-view-page.d.ts +1 -1
  56. package/dist-client/pages/activity-thread/activity-thread-view-page.js +1 -0
  57. package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
  58. package/dist-client/pages/activity-thread/activity-thread-view.d.ts +1 -1
  59. package/dist-client/pages/installable-activity/installable-activity-list-page.d.ts +27 -0
  60. package/dist-client/pages/installable-activity/installable-activity-list-page.js +70 -11
  61. package/dist-client/pages/installable-activity/installable-activity-list-page.js.map +1 -1
  62. package/dist-client/pages/todo/draft-list-page.js +1 -0
  63. package/dist-client/pages/todo/draft-list-page.js.map +1 -1
  64. package/dist-client/route.d.ts +1 -1
  65. package/dist-client/tsconfig.tsbuildinfo +1 -1
  66. package/dist-server/controllers/activity-approval/approval-line.d.ts +32 -0
  67. package/dist-server/controllers/activity-approval/approval-line.js +35 -0
  68. package/dist-server/controllers/activity-approval/approval-line.js.map +1 -1
  69. package/dist-server/controllers/activity-approval/approve.js +17 -0
  70. package/dist-server/controllers/activity-approval/approve.js.map +1 -1
  71. package/dist-server/controllers/activity-approval/reject.js +21 -1
  72. package/dist-server/controllers/activity-approval/reject.js.map +1 -1
  73. package/dist-server/controllers/activity-installation/activity-installation-controller.js +5 -9
  74. package/dist-server/controllers/activity-installation/activity-installation-controller.js.map +1 -1
  75. package/dist-server/controllers/activity-installation/installable-to-activity.d.ts +62 -0
  76. package/dist-server/controllers/activity-installation/installable-to-activity.js +53 -0
  77. package/dist-server/controllers/activity-installation/installable-to-activity.js.map +1 -0
  78. package/dist-server/controllers/activity-installation/registry-conflict.d.ts +32 -0
  79. package/dist-server/controllers/activity-installation/registry-conflict.js +21 -0
  80. package/dist-server/controllers/activity-installation/registry-conflict.js.map +1 -0
  81. package/dist-server/controllers/activity-instance/draft.js +3 -0
  82. package/dist-server/controllers/activity-instance/draft.js.map +1 -1
  83. package/dist-server/controllers/activity-instance/entitlement.d.ts +25 -0
  84. package/dist-server/controllers/activity-instance/entitlement.js +108 -0
  85. package/dist-server/controllers/activity-instance/entitlement.js.map +1 -0
  86. package/dist-server/controllers/activity-instance/issue.js +10 -0
  87. package/dist-server/controllers/activity-instance/issue.js.map +1 -1
  88. package/dist-server/controllers/activity-instance/pick.js +8 -0
  89. package/dist-server/controllers/activity-instance/pick.js.map +1 -1
  90. package/dist-server/service/activity/activity-decision-key-item-type.d.ts +160 -0
  91. package/dist-server/service/activity/activity-decision-key-item-type.js +176 -0
  92. package/dist-server/service/activity/activity-decision-key-item-type.js.map +1 -0
  93. package/dist-server/service/activity/activity-type.d.ts +3 -0
  94. package/dist-server/service/activity/activity-type.js +8 -0
  95. package/dist-server/service/activity/activity-type.js.map +1 -1
  96. package/dist-server/service/activity/activity.d.ts +30 -0
  97. package/dist-server/service/activity/activity.js +14 -0
  98. package/dist-server/service/activity/activity.js.map +1 -1
  99. package/dist-server/service/activity/decision-key-declaration.d.ts +33 -0
  100. package/dist-server/service/activity/decision-key-declaration.js +67 -0
  101. package/dist-server/service/activity/decision-key-declaration.js.map +1 -0
  102. package/dist-server/service/activity/{credential-guard-subscriber.d.ts → declaration-guard-subscriber.d.ts} +6 -1
  103. package/dist-server/service/activity/declaration-guard-subscriber.js +46 -0
  104. package/dist-server/service/activity/declaration-guard-subscriber.js.map +1 -0
  105. package/dist-server/service/activity/index.d.ts +2 -2
  106. package/dist-server/service/activity/index.js +2 -2
  107. package/dist-server/service/activity/index.js.map +1 -1
  108. package/dist-server/service/activity/search-key-declaration.d.ts +38 -0
  109. package/dist-server/service/activity/search-key-declaration.js +49 -0
  110. package/dist-server/service/activity/search-key-declaration.js.map +1 -0
  111. package/dist-server/service/activity-instance/activity-instance-mutation.js +20 -9
  112. package/dist-server/service/activity-instance/activity-instance-mutation.js.map +1 -1
  113. package/dist-server/service/activity-instance/activity-instance-query.js +25 -2
  114. package/dist-server/service/activity-instance/activity-instance-query.js.map +1 -1
  115. package/dist-server/service/activity-instance/activity-instance-type.js.map +1 -1
  116. package/dist-server/service/index.d.ts +2 -2
  117. package/dist-server/service/installable-activity/installable-activity-mutation.js +24 -0
  118. package/dist-server/service/installable-activity/installable-activity-mutation.js.map +1 -1
  119. package/dist-server/service/installable-activity/installable-activity.d.ts +9 -0
  120. package/dist-server/service/installable-activity/installable-activity.js +5 -0
  121. package/dist-server/service/installable-activity/installable-activity.js.map +1 -1
  122. package/dist-server/tsconfig.tsbuildinfo +1 -1
  123. package/package.json +11 -11
  124. package/translations/en.json +33 -6
  125. package/translations/ja.json +27 -0
  126. package/translations/ko.json +33 -6
  127. package/translations/ms.json +27 -0
  128. package/translations/zh.json +27 -0
  129. package/dist-server/service/activity/credential-guard-subscriber.js +0 -35
  130. package/dist-server/service/activity/credential-guard-subscriber.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/icon/icon.js';
2
3
  import '@operato/property-editor/ox-properties-dynamic-view.js';
3
4
  import '@operato/board/ox-board-viewer.js';
4
5
  import '../../components/activity-approval-ribon.js';
@@ -10,10 +11,13 @@ import { customElement, query, state } from 'lit/decorators.js';
10
11
  import { keyed } from 'lit/directives/keyed.js';
11
12
  import { client } from '@operato/graphql';
12
13
  import { i18next, localize } from '@operato/i18n';
14
+ import { ranMutation } from '../../components/say-outcome.js';
13
15
  import { PageView } from '@operato/shell';
14
16
  import { CommonButtonStyles, ScrollbarStyles } from '@operato/styles';
15
17
  import { provider } from '@things-factory/board-ui/dist-client/board-provider.js';
16
- import { ActivityFieldsStyles, inputSpecOf, outputSpecOf, renderGiven } from '../../components/activity-fields.js';
18
+ import { isReferenceStep } from '@things-factory/organization/dist-client';
19
+ import { ActivityFieldsStyles, fieldValue, inputSpecOf, outputSpecOf, renderGiven } from '../../components/activity-fields.js';
20
+ import { decisionSlots, reversibilityIsMalformed, reversibilityOf, slotOfRole, slotsOfRole } from '../../components/decision-slots.js';
17
21
  const ActivityApprovalFetchResult = `\
18
22
  { id
19
23
  judgment
@@ -57,6 +61,12 @@ const ActivityApprovalFetchResult = `\
57
61
  description
58
62
  state
59
63
  activity { id
64
+ label
65
+ decisionKeys { role
66
+ inputPath
67
+ tKey
68
+ unit
69
+ }
60
70
  model { name
61
71
  description
62
72
  active
@@ -72,6 +82,7 @@ const ActivityApprovalFetchResult = `\
72
82
  }
73
83
  approvalLine { type
74
84
  value
85
+ participation
75
86
  approver { id
76
87
  name
77
88
  description
@@ -214,8 +225,154 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
214
225
  color: var(--ui-faint, #8a959c);
215
226
  word-break: keep-all;
216
227
  }
228
+
229
+ /*
230
+ * The decision, drawn from the activity's declaration.
231
+ *
232
+ * The order is the declaration's and the weight descends with it: what is being asked reads
233
+ * first at title size, then what it is about, then the words a person wrote, then what changes
234
+ * on approval, and last why it came up at all.
235
+ */
236
+ section[decision] .headline {
237
+ font-size: var(--ui-t-headline, 19px);
238
+ font-weight: 600;
239
+ line-height: 1.35;
240
+ color: var(--ui-ink, #17242c);
241
+ word-break: keep-all;
242
+ }
243
+
244
+ section[decision] .subject {
245
+ margin-top: 3px;
246
+ font-family: var(--ui-mono, ui-monospace, Menlo, monospace);
247
+ font-size: var(--ui-t-body, 13px);
248
+ color: var(--ui-muted, #57676f);
249
+ }
250
+
251
+ section[decision] .block {
252
+ margin-top: 14px;
253
+ }
254
+
255
+ section[decision] .block > .label {
256
+ margin-bottom: 4px;
257
+ font-size: var(--ui-t-small, 11.5px);
258
+ color: var(--ui-muted, #57676f);
259
+ }
260
+
261
+ /* What a person wrote is set apart, because it is the only part nobody computed. */
262
+ section[decision] .said {
263
+ padding: 9px 11px;
264
+ border-left: 3px solid var(--ui-line, #d5dce0);
265
+ background: var(--ui-line-soft, #f2f5f7);
266
+ color: var(--ui-ink, #17242c);
267
+ word-break: break-word;
268
+ }
269
+
270
+ section[decision] .none {
271
+ color: var(--ui-faint, #8a959c);
272
+ }
273
+
274
+ section[decision] .consequence {
275
+ margin-top: 8px;
276
+ padding: 9px 11px;
277
+ border: 1px solid var(--ui-line, #d5dce0);
278
+ border-radius: var(--ui-r-small, 6px);
279
+ }
280
+
281
+ section[decision] .consequence > .label {
282
+ margin-bottom: 3px;
283
+ font-size: var(--ui-t-small, 11.5px);
284
+ color: var(--ui-muted, #57676f);
285
+ }
286
+
287
+ /*
288
+ * Whether approving can be undone reads as the frame around the decision, not as one of its
289
+ * consequences. Among them it carries the same weight as "four orders get pushed back", and
290
+ * on an approval that moves equipment it is the heaviest line on the screen.
291
+ */
292
+ section[decision] .reversibility {
293
+ display: flex;
294
+ align-items: center;
295
+ gap: 6px;
296
+
297
+ margin-top: 12px;
298
+ padding: 8px 11px;
299
+ border-radius: var(--ui-r-small, 6px);
300
+
301
+ background: var(--ui-line-soft, #f2f5f7);
302
+ color: var(--ui-muted, #57676f);
303
+ font-size: var(--ui-t-small, 11.5px);
304
+ }
305
+
306
+ section[decision] .reversibility[irreversible] {
307
+ background: var(--md-sys-color-error-container, #ffdad6);
308
+ color: var(--md-sys-color-on-error-container, #410002);
309
+ font-weight: 600;
310
+ }
311
+
312
+ section[decision] .reversibility md-icon {
313
+ font-size: 1.15em;
314
+ }
315
+
316
+ /*
317
+ * What the decision was made from. Set flatter than the consequences — it is the record the
318
+ * approver checks, not something the approval brings about.
319
+ */
320
+ section[decision] .evidence {
321
+ margin-top: 8px;
322
+ padding: 7px 0 7px 11px;
323
+ border-left: 2px solid var(--ui-line, #d5dce0);
324
+ }
325
+
326
+ section[decision] .evidence > .label {
327
+ margin-bottom: 2px;
328
+ font-size: var(--ui-t-small, 11.5px);
329
+ color: var(--ui-muted, #57676f);
330
+ }
331
+
332
+ /*
333
+ * What is true right now sits apart from what the approval would change. Sharing the box the
334
+ * consequences use would say the approval causes these readings.
335
+ */
336
+ section[decision] .current-state {
337
+ margin-top: 14px;
338
+ padding: 9px 11px;
339
+ border: 1px dashed var(--ui-line, #d5dce0);
340
+ border-radius: var(--ui-r-small, 6px);
341
+ }
342
+
343
+ section[decision] .current-state > .label {
344
+ margin-bottom: 3px;
345
+ font-size: var(--ui-t-small, 11.5px);
346
+ color: var(--ui-muted, #57676f);
347
+ }
348
+
349
+ section[decision] .policy {
350
+ margin-top: 14px;
351
+ padding-top: 11px;
352
+ border-top: 1px solid var(--ui-line, #d5dce0);
353
+ font-size: var(--ui-t-small, 11.5px);
354
+ color: var(--ui-muted, #57676f);
355
+ }
217
356
  `
218
357
  ]; }
358
+ /**
359
+ * Is the reader seated here as a reference — someone who comments and does not decide?
360
+ *
361
+ * The rule comes from organization's client types, the same function its own approval-line editor
362
+ * and list use, so the two wire spellings of the enum are handled in one place. The approval line
363
+ * answers rather than ActivityApproval.participation, matching what the server refuses on
364
+ * (isReferenceOrder in approval-line.ts): the line decides who comes next, so letting the row
365
+ * decide who may judge would leave two authorities with nothing to settle a disagreement.
366
+ *
367
+ * The value comes back false while the query is still out. That is the same answer as for an
368
+ * approver, so the buttons show for a moment on a reference's screen — pressing one is refused by
369
+ * the server, which is why the server closed first.
370
+ */
371
+ get isReference() {
372
+ const approvalLine = this.activityThread?.activityInstance?.approvalLine;
373
+ const order = Number(this.activityApproval?.order ?? 0);
374
+ return isReferenceStep(approvalLine?.[order - 1]);
375
+ }
219
376
  get context() {
220
377
  const judgment = this.activityApproval?.judgment;
221
378
  return { title: this.lifecycle?.params?.['title'] || i18next.t('title.activity approval'),
@@ -227,6 +384,17 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
227
384
  * 어느 목록에서 열리는지는 이 화면이 안다. 그것을 적어 둔다.
228
385
  */
229
386
  menu: 'approval-pending-list',
387
+ /*
388
+ * A reference is offered neither approve nor reject.
389
+ *
390
+ * ActivityApprovalJudgment has carried Closed for this seat from the start — "the case ended"
391
+ * rather than a judgment — so a reference row was never meant to end in approved or rejected.
392
+ * The buttons were offered anyway, and the server accepted them until 2026-09-09.
393
+ *
394
+ * What stays is the note, because a comment is the whole point of the seat, and close. The
395
+ * hint under the textarea says which seat this is; buttons that simply vanish leave the
396
+ * reader looking for them.
397
+ */
230
398
  actions: judgment
231
399
  ? [
232
400
  { title: i18next.t('button.close'),
@@ -236,48 +404,16 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
236
404
  ...CommonButtonStyles.back
237
405
  }
238
406
  ]
239
- : /*
240
- * ── 세 단추의 순서와 무게 (2026-09-06) ──────────────────────────
241
- * 전에는 반려가 첫 자리였고, 승인 · 반려 · 저장이 모두 같은 모양이었다. 되돌리기 어려운
242
- * 것이 앞에 서고 무엇이 주된 흐름인지 화면이 말하지 않았다.
243
- *
244
- * 승인이 주 동작이다 — 채운 단추 하나. 반려는 대등한 선택이지만 예외 경로라 테두리
245
- * 단추로 그 옆에 둔다.
246
- *
247
- * 「저장」은 이름이 뜻을 말하지 않았다. 이 단추가 저장하는 것은 **의견 하나**이고
248
- * (saveActivityApproval 은 id 와 comment 만 보낸다), 판단은 미뤄 둔다. 그리고 그 결과는
249
- * 목록에서 「메모 있음」 표식으로 나타난다(activity-cells 의 approvalMarks). 목록이
250
- * 부르는 이름과 같은 낱말로 맞춘다.
251
- */
252
- [
253
- { title: i18next.t('button.approve'),
254
- action: this.approveActivityApproval.bind(this),
255
- icon: 'approval',
256
- emphasis: { raised: true,
257
- outlined: false,
258
- dense: false,
259
- danger: false
260
- }
261
- },
262
- { title: i18next.t('button.reject'),
263
- action: this.rejectActivityApproval.bind(this),
264
- icon: 'assignment_return',
265
- emphasis: { raised: false,
266
- outlined: true,
267
- dense: false,
268
- danger: false
269
- }
270
- },
271
- { title: i18next.t('button.save-note'),
407
+ : this.isReference
408
+ ? [
409
+ {
410
+ title: i18next.t('button.save-note'),
272
411
  action: this.saveActivityApproval.bind(this),
273
412
  icon: 'edit_note',
274
- emphasis: { raised: false,
275
- outlined: true,
276
- dense: false,
277
- danger: false
278
- }
413
+ emphasis: { raised: true, outlined: false, dense: false, danger: false }
279
414
  },
280
- { title: i18next.t('button.close'),
415
+ {
416
+ title: i18next.t('button.close'),
281
417
  action: () => {
282
418
  history.back();
283
419
  },
@@ -285,6 +421,55 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
285
421
  icon: 'close'
286
422
  }
287
423
  ]
424
+ : /*
425
+ * ── 세 단추의 순서와 무게 (2026-09-06) ──────────────────────────
426
+ * 전에는 반려가 첫 자리였고, 승인 · 반려 · 저장이 모두 같은 모양이었다. 되돌리기 어려운
427
+ * 것이 앞에 서고 무엇이 주된 흐름인지 화면이 말하지 않았다.
428
+ *
429
+ * 승인이 주 동작이다 — 채운 단추 하나. 반려는 대등한 선택이지만 예외 경로라 테두리
430
+ * 단추로 그 옆에 둔다.
431
+ *
432
+ * 「저장」은 이름이 뜻을 말하지 않았다. 이 단추가 저장하는 것은 **의견 하나**이고
433
+ * (saveActivityApproval 은 id 와 comment 만 보낸다), 판단은 미뤄 둔다. 그리고 그 결과는
434
+ * 목록에서 「메모 있음」 표식으로 나타난다(activity-cells 의 approvalMarks). 목록이
435
+ * 부르는 이름과 같은 낱말로 맞춘다.
436
+ */
437
+ [
438
+ { title: i18next.t('button.approve'),
439
+ action: this.approveActivityApproval.bind(this),
440
+ icon: 'approval',
441
+ emphasis: { raised: true,
442
+ outlined: false,
443
+ dense: false,
444
+ danger: false
445
+ }
446
+ },
447
+ { title: i18next.t('button.reject'),
448
+ action: this.rejectActivityApproval.bind(this),
449
+ icon: 'assignment_return',
450
+ emphasis: { raised: false,
451
+ outlined: true,
452
+ dense: false,
453
+ danger: false
454
+ }
455
+ },
456
+ { title: i18next.t('button.save-note'),
457
+ action: this.saveActivityApproval.bind(this),
458
+ icon: 'edit_note',
459
+ emphasis: { raised: false,
460
+ outlined: true,
461
+ dense: false,
462
+ danger: false
463
+ }
464
+ },
465
+ { title: i18next.t('button.close'),
466
+ action: () => {
467
+ history.back();
468
+ },
469
+ ...CommonButtonStyles.back,
470
+ icon: 'close'
471
+ }
472
+ ]
288
473
  // activityApproval: this.activityApproval
289
474
  };
290
475
  }
@@ -317,7 +502,9 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
317
502
  <section>
318
503
  <h2>${i18next.t('field.wl-comment')}</h2>
319
504
  <textarea
320
- placeholder=${String(i18next.t('text.explain the reason for approval/rejection'))}
505
+ placeholder=${String(i18next.t(this.isReference
506
+ ? 'text.write-your-comment-as-reference'
507
+ : 'text.explain the reason for approval/rejection'))}
321
508
  .value=${this.activityApproval?.comment}
322
509
  @change=${(e) => {
323
510
  this.activityApproval.comment = e.target.value;
@@ -326,8 +513,14 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
326
513
  <!--
327
514
  반려는 의견이 필수다(rejectActivityApproval). 전에는 그 규칙을 **누른 뒤에** 토스트로
328
515
  알렸다. 되돌릴 수 없는 것과 뜻이 다른 동작은 하기 전에 말한다.
516
+
517
+ A reference reads a different line: this one names the seat, because the approve
518
+ and reject buttons are absent for them and a reader whose buttons are missing
519
+ goes looking for them.
329
520
  -->
330
- <p class="hint">${i18next.t('text.approval-comment-hint')}</p>
521
+ <p class="hint">
522
+ ${i18next.t(this.isReference ? 'text.reference-comment-hint' : 'text.approval-comment-hint')}
523
+ </p>
331
524
  </section>
332
525
  </div>
333
526
  `
@@ -348,8 +541,36 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
348
541
  /* board 는 전체 화면을 쓰는 캔버스라 카드에 담지 않는다(hostedContent 머리말). */
349
542
  case 'board':
350
543
  return this.boardContent();
351
- case 'custom-element':
352
- return this.hostedContent(this.customElementContent());
544
+ case 'custom-element': {
545
+ /*
546
+ * The wrapper is for the element, not for the notice.
547
+ *
548
+ * hostedContent puts the content in a card so every ui type sits at the same place with the
549
+ * same frame. The not-loaded notice is already a card — wrapping it drew a card inside a
550
+ * card, two borders deep, and its margins landed unevenly: 16 above and none below, inside
551
+ * a box that had its own padding. The generated branch returns the same notice unwrapped
552
+ * and looks right.
553
+ */
554
+ const { viewSource: tagName } = this.activityThread?.activityInstance || {};
555
+ /*
556
+ * Naming no element is not the same as naming one that is missing.
557
+ *
558
+ * A template that stops providing a screen clears the field — installActivity does not
559
+ * blank what a template leaves undefined, so clearing is written as an empty string. That
560
+ * left viewType saying custom-element with nothing named, and this branch answered "the
561
+ * screen could not be loaded": a failure, for an activity that simply has no screen of its
562
+ * own. Those read as different things to whoever is approving, and the second one is not
563
+ * true.
564
+ *
565
+ * With nothing named, the generated screen is what this activity has — and it draws a
566
+ * decision when the activity declares one, which the two plant forms that hit this do.
567
+ */
568
+ if (!tagName) {
569
+ return this.generatedContent();
570
+ }
571
+ const element = this.customElementContent();
572
+ return element ? this.hostedContent(element) : this.screenNotLoaded();
573
+ }
353
574
  case 'page':
354
575
  case 'external':
355
576
  return html ``;
@@ -387,12 +608,179 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
387
608
  * approveActivityApproval / rejectActivityApproval 은 id 와 comment 만 보낸다 — 그 값은 어디에도
388
609
  * 가지 않는다. 결재자가 적을 수 있는 것은 의견 하나이고, 그것은 화면 아래 칸이 받는다.
389
610
  */
611
+ /**
612
+ * The decision the activity declared, or nothing when it declared none.
613
+ *
614
+ * Returning nothing is how generatedContent knows to fall back to the model. An activity with no
615
+ * decisionKeys draws exactly what it drew before.
616
+ *
617
+ * A slot whose path led nowhere is skipped rather than drawn empty. Older instances of an activity
618
+ * predate a field the declaration names — plant hit that with its `policy` value — and an empty
619
+ * bar on the screen would read as "there is no reason" when the truth is that this record has no
620
+ * such value. What cannot be shown is left out; what is shown is only what the payload actually
621
+ * carries.
622
+ *
623
+ * `reason` is the exception. Missing and blank mean the same thing to whoever is reading — nobody
624
+ * wrote one — so both draw the grey line rather than disappearing. A reason that silently vanishes
625
+ * would let an approver assume it was never asked for.
626
+ */
627
+ decisionContent() {
628
+ const { activityInstance } = this.activityThread || {};
629
+ const slots = decisionSlots(activityInstance?.activity?.decisionKeys, activityInstance?.input);
630
+ if (!slots.length) {
631
+ return nothing;
632
+ }
633
+ const headline = slotOfRole(slots, 'headline');
634
+ const subject = slotOfRole(slots, 'subject');
635
+ const reason = slotOfRole(slots, 'reason');
636
+ const consequences = slotsOfRole(slots, 'consequence').filter(slot => slot.found);
637
+ const policy = slotOfRole(slots, 'policy');
638
+ const evidence = slotsOfRole(slots, 'evidence').filter(slot => slot.found);
639
+ const currentState = slotOfRole(slots, 'currentState');
640
+ const reversibility = slotOfRole(slots, 'reversibility');
641
+ const drawable = [headline, subject, policy, currentState].some(slot => slot?.found) ||
642
+ !!reason ||
643
+ consequences.length ||
644
+ evidence.length ||
645
+ reversibilityOf(reversibility) !== undefined;
646
+ /*
647
+ * Every declared slot missed this payload. Saying so is not this screen's job — falling back to
648
+ * the model at least shows the values that are there, and the reader is no worse off than before
649
+ * the declaration existed.
650
+ */
651
+ if (!drawable) {
652
+ return nothing;
653
+ }
654
+ return keyed(this.activityApproval.id, html `
655
+ <section decision>
656
+ ${headline?.found ? html `<div class="headline">${this.slotText(headline)}</div>` : ''}
657
+ ${subject?.found ? html `<div class="subject">${this.slotText(subject)}</div>` : ''}
658
+ ${reason
659
+ ? html `
660
+ <div class="block">
661
+ <div class="label">${i18next.t('field.reason')}</div>
662
+ <div class="said ${this.hasWords(reason) ? '' : 'none'}">
663
+ ${this.hasWords(reason) ? this.slotText(reason) : i18next.t('text.no-reason-given')}
664
+ </div>
665
+ </div>
666
+ `
667
+ : ''}
668
+ ${this.renderReversibility(reversibility)}
669
+ ${evidence.length
670
+ ? html `
671
+ <div class="block">
672
+ <div class="label">${i18next.t('label.what-it-was-judged-from')}</div>
673
+ ${evidence.map(slot => html `
674
+ <div class="evidence">
675
+ ${slot.tKey ? html `<div class="label">${i18next.t(slot.tKey)}</div>` : ''}
676
+ <div>${fieldValue(slot.value)}</div>
677
+ </div>
678
+ `)}
679
+ </div>
680
+ `
681
+ : ''}
682
+ ${currentState?.found
683
+ ? html `
684
+ <div class="current-state">
685
+ <div class="label">${i18next.t(currentState.tKey || 'label.current-state')}</div>
686
+ <div>${fieldValue(currentState.value)}</div>
687
+ </div>
688
+ `
689
+ : ''}
690
+ ${consequences.length
691
+ ? html `
692
+ <div class="block">
693
+ <div class="label">${i18next.t('label.what-changes')}</div>
694
+ ${consequences.map(slot => html `
695
+ <div class="consequence">
696
+ ${slot.tKey ? html `<div class="label">${i18next.t(slot.tKey)}</div>` : ''}
697
+ <div>${fieldValue(slot.value)}</div>
698
+ </div>
699
+ `)}
700
+ </div>
701
+ `
702
+ : ''}
703
+ ${policy?.found
704
+ ? html `
705
+ <div class="policy">
706
+ ${i18next.t('label.why-approval')} — ${this.slotText(policy)}
707
+ </div>
708
+ `
709
+ : ''}
710
+ </section>
711
+ `);
712
+ }
713
+ /**
714
+ * Whether approving can be undone, drawn as the frame around the decision.
715
+ *
716
+ * Only an explicit true or false draws. Anything else — the path led nowhere, or the value is not
717
+ * a boolean — draws nothing, because saying "this can be undone" on a screen that does not know is
718
+ * the one mistake this line must never make.
719
+ *
720
+ * This is not rejectionIsFinal. That one says whether **rejecting** ends the case for good; this
721
+ * says whether **approving** can be taken back. The twin actuation carries both, which is exactly
722
+ * why they must not share a sentence.
723
+ */
724
+ renderReversibility(slot) {
725
+ const said = reversibilityOf(slot);
726
+ if (!said) {
727
+ if (reversibilityIsMalformed(slot)) {
728
+ /*
729
+ * Declared, and the value is there, but not the shape this role takes — a bare sentence
730
+ * where { reversible, caveat } belongs. Nothing is drawn, because this side cannot tell
731
+ * which way it goes; the log is so a developer does not have to find it by hand, which is
732
+ * how it was found the first time.
733
+ */
734
+ console.error(`[worklist] reversibility at "${slot?.inputPath}" is not { reversible, caveat } — nothing drawn`);
735
+ }
736
+ return '';
737
+ }
738
+ /*
739
+ * The module's own sentence when it wrote one. 「버린 자재는 돌아오지 않습니다」 says why, which
740
+ * the plain wording cannot; the boolean beside it is what decides how heavily this is drawn.
741
+ */
742
+ return html `
743
+ <div class="reversibility" ?irreversible=${!said.reversible}>
744
+ <md-icon>${said.reversible ? 'undo' : 'lock'}</md-icon>
745
+ <span
746
+ >${said.caveat ||
747
+ i18next.t(said.reversible ? 'text.approval-is-reversible' : 'text.approval-is-irreversible')}</span
748
+ >
749
+ </div>
750
+ `;
751
+ }
752
+ /**
753
+ * One slot's value as something to read.
754
+ *
755
+ * A scalar prints with its unit when one was declared. Anything shaped — an object, a list — goes
756
+ * through fieldValue, the same laying-out the model sections use, so a payload reads the same way
757
+ * wherever it appears and a four-level object does not become one long line.
758
+ */
759
+ slotText(slot) {
760
+ const scalar = slot.value !== null && slot.value !== undefined && typeof slot.value !== 'object';
761
+ if (scalar && slot.unit) {
762
+ return `${slot.value} ${slot.unit}`;
763
+ }
764
+ return scalar ? String(slot.value) : fieldValue(slot.value);
765
+ }
766
+ /** Did a person actually write something here. Whitespace is not words. */
767
+ hasWords(slot) {
768
+ return typeof slot.value === 'string' ? slot.value.trim().length > 0 : slot.found && slot.value != null;
769
+ }
390
770
  generatedContent() {
391
771
  if (!this.activityApproval) {
392
772
  return html ``;
393
773
  }
394
774
  const { output, activityInstance } = this.activityThread || {};
395
775
  const { input } = activityInstance || {};
776
+ /*
777
+ * The declaration goes first. It answers "what happens if I approve", which is the question this
778
+ * screen exists for; the model answers "what are the values", which is the fallback.
779
+ */
780
+ const decision = this.decisionContent();
781
+ if (decision !== nothing) {
782
+ return decision;
783
+ }
396
784
  const model = activityInstance?.activity?.model;
397
785
  const inputSpec = inputSpecOf(model);
398
786
  const outputSpec = outputSpecOf(model);
@@ -449,12 +837,33 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
449
837
  ></ox-board-viewer>
450
838
  `);
451
839
  }
840
+ /**
841
+ * The element the activity names, or a plain statement that it could not be loaded.
842
+ *
843
+ * document.createElement answers something for any name, so an activity pointing at an element
844
+ * nobody registered used to produce an empty card. The approver got a screen with nothing on it
845
+ * and the approve button still worked — plant shipped two such forms in one day and found them by
846
+ * reading the file list, not by opening the screen.
847
+ *
848
+ * The reader is told what to judge by instead. Same shape as the empty generated screen: what is
849
+ * missing, and where to look — the submission reason and the history in the timeline below.
850
+ *
851
+ * ── Waiting for a late definition ─────────────────────────────────────────
852
+ * An element can be registered after this page renders, and answering "not loaded" forever in
853
+ * that case would be a screen inventing a defect. whenDefined resolves whenever it arrives and
854
+ * the page redraws; a name nobody ever registers simply never resolves, and the statement stands.
855
+ */
452
856
  customElementContent() {
453
857
  if (!this.activityApproval) {
454
858
  return html ``;
455
859
  }
456
860
  const { output, activityInstance } = this.activityThread;
457
861
  const { viewSource: tagName, input } = activityInstance || {};
862
+ if (!customElements.get(tagName)) {
863
+ customElements.whenDefined(tagName).then(() => this.requestUpdate());
864
+ /* The caller draws the notice — unwrapped, because the notice is its own card. */
865
+ return undefined;
866
+ }
458
867
  const element = document.createElement(tagName);
459
868
  element.id = 'custom-content';
460
869
  element.activityId = activityInstance.activity.id;
@@ -463,6 +872,13 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
463
872
  element.activityApproval = this.activityApproval;
464
873
  return element;
465
874
  }
875
+ /** The activity names a screen and nothing registered it. Says what to judge by instead. */
876
+ screenNotLoaded() {
877
+ return html `<section empty>
878
+ <span class="title">${i18next.t('text.approval-view-not-loaded')}</span>
879
+ <span class="why">${i18next.t('text.no-approval-content-why')}</span>
880
+ </section>`;
881
+ }
466
882
  getOutputFromBoard() {
467
883
  const outputSpec = outputSpecOf(this.activityThread?.activityInstance?.activity?.model);
468
884
  const data = this.oxBoardViewer.getSceneValues();
@@ -512,16 +928,16 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
512
928
  this.commentInput.focus();
513
929
  return;
514
930
  }
515
- const response = await client.mutate({ mutation: gql `
931
+ const outcome = await ranMutation(() => client.mutate({ mutation: gql `
516
932
  mutation ($id: String!, $comment: String!) { rejectActivityApproval(id: $id, comment: $comment) ${ActivityApprovalFetchResult}
517
933
  }
518
934
  `,
519
935
  variables: { id,
520
936
  comment
521
937
  }
522
- });
523
- if (!response.errors) {
524
- this.activityApproval = response.data.rejectActivityApproval;
938
+ }));
939
+ if (outcome.ok) {
940
+ this.activityApproval = outcome.data.rejectActivityApproval;
525
941
  this.activityThread = this.activityApproval.activityThread;
526
942
  this.updateContext();
527
943
  document.dispatchEvent(new CustomEvent('notify', { detail: { message: i18next.t('text.task_is_rejected_successfully')
@@ -531,16 +947,16 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
531
947
  }
532
948
  async approveActivityApproval() {
533
949
  var { id, comment } = this.activityApproval;
534
- const response = await client.mutate({ mutation: gql `
950
+ const outcome = await ranMutation(() => client.mutate({ mutation: gql `
535
951
  mutation ($id: String!, $comment: String) { approveActivityApproval(id: $id, comment: $comment) ${ActivityApprovalFetchResult}
536
952
  }
537
953
  `,
538
954
  variables: { id,
539
955
  comment
540
956
  }
541
- });
542
- if (!response.errors) {
543
- this.activityApproval = response.data.approveActivityApproval;
957
+ }));
958
+ if (outcome.ok) {
959
+ this.activityApproval = outcome.data.approveActivityApproval;
544
960
  this.activityThread = this.activityApproval.activityThread;
545
961
  this.updateContext();
546
962
  document.dispatchEvent(new CustomEvent('notify', { detail: { message: i18next.t('text.task_is_approved_successfully')
@@ -550,16 +966,16 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
550
966
  }
551
967
  async saveActivityApproval() {
552
968
  var { id, comment } = this.activityApproval;
553
- const response = await client.mutate({ mutation: gql `
969
+ const outcome = await ranMutation(() => client.mutate({ mutation: gql `
554
970
  mutation ($id: String!, $comment: String) { saveActivityApproval(id: $id, comment: $comment) ${ActivityApprovalFetchResult}
555
971
  }
556
972
  `,
557
973
  variables: { id,
558
974
  comment
559
975
  }
560
- });
561
- if (!response.errors) {
562
- this.activityApproval = response.data.saveActivityApproval;
976
+ }));
977
+ if (outcome.ok) {
978
+ this.activityApproval = outcome.data.saveActivityApproval;
563
979
  this.activityThread = this.activityApproval.activityThread;
564
980
  this.updateContext();
565
981
  document.dispatchEvent(new CustomEvent('notify', { detail: { message: i18next.t('text.task_is_saved_successfully')
@@ -586,7 +1002,21 @@ __decorate([
586
1002
  ], ActivityApprovalPage.prototype, "board", void 0);
587
1003
  __decorate([
588
1004
  query('textarea'),
589
- __metadata("design:type", HTMLTextAreaElement)
1005
+ __metadata("design:type", HTMLTextAreaElement
1006
+ /**
1007
+ * Is the reader seated here as a reference — someone who comments and does not decide?
1008
+ *
1009
+ * The rule comes from organization's client types, the same function its own approval-line editor
1010
+ * and list use, so the two wire spellings of the enum are handled in one place. The approval line
1011
+ * answers rather than ActivityApproval.participation, matching what the server refuses on
1012
+ * (isReferenceOrder in approval-line.ts): the line decides who comes next, so letting the row
1013
+ * decide who may judge would leave two authorities with nothing to settle a disagreement.
1014
+ *
1015
+ * The value comes back false while the query is still out. That is the same answer as for an
1016
+ * approver, so the buttons show for a moment on a reference's screen — pressing one is refused by
1017
+ * the server, which is why the server closed first.
1018
+ */
1019
+ )
590
1020
  ], ActivityApprovalPage.prototype, "commentInput", void 0);
591
1021
  __decorate([
592
1022
  query('ox-board-viewer'),