@things-factory/worklist 10.1.6 → 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 (85) hide show
  1. package/dist-client/components/activity-instance-preview.js +5 -4
  2. package/dist-client/components/activity-instance-preview.js.map +1 -1
  3. package/dist-client/components/activity-starter-form.js +14 -10
  4. package/dist-client/components/activity-starter-form.js.map +1 -1
  5. package/dist-client/components/activity-thread-timeline.d.ts +2 -2
  6. package/dist-client/components/decision-slots.d.ts +38 -9
  7. package/dist-client/components/decision-slots.js +24 -10
  8. package/dist-client/components/decision-slots.js.map +1 -1
  9. package/dist-client/components/mutation-outcome.d.ts +57 -0
  10. package/dist-client/components/mutation-outcome.js +78 -0
  11. package/dist-client/components/mutation-outcome.js.map +1 -0
  12. package/dist-client/components/say-outcome.d.ts +25 -0
  13. package/dist-client/components/say-outcome.js +51 -0
  14. package/dist-client/components/say-outcome.js.map +1 -0
  15. package/dist-client/pages/activity/activity-list-page.js +28 -12
  16. package/dist-client/pages/activity/activity-list-page.js.map +1 -1
  17. package/dist-client/pages/activity/activity-model-item-list.js +4 -3
  18. package/dist-client/pages/activity/activity-model-item-list.js.map +1 -1
  19. package/dist-client/pages/activity/activity-page.d.ts +2 -2
  20. package/dist-client/pages/activity/activity-page.js +25 -21
  21. package/dist-client/pages/activity/activity-page.js.map +1 -1
  22. package/dist-client/pages/activity-approval/activity-approval-importer.js +4 -3
  23. package/dist-client/pages/activity-approval/activity-approval-importer.js.map +1 -1
  24. package/dist-client/pages/activity-approval/activity-approval-list-page.js +8 -9
  25. package/dist-client/pages/activity-approval/activity-approval-list-page.js.map +1 -1
  26. package/dist-client/pages/activity-approval/activity-approval-page.d.ts +3 -1
  27. package/dist-client/pages/activity-approval/activity-approval-page.js +76 -28
  28. package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
  29. package/dist-client/pages/activity-instance/activity-instance-start-page.js +24 -18
  30. package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
  31. package/dist-client/pages/activity-instance/activity-instance-view.js +1 -0
  32. package/dist-client/pages/activity-instance/activity-instance-view.js.map +1 -1
  33. package/dist-client/pages/activity-stats/activity-stats-importer.js +4 -3
  34. package/dist-client/pages/activity-stats/activity-stats-importer.js.map +1 -1
  35. package/dist-client/pages/activity-stats/activity-stats-list-page.js +8 -9
  36. package/dist-client/pages/activity-stats/activity-stats-list-page.js.map +1 -1
  37. package/dist-client/pages/activity-template/activity-template-importer.js +4 -3
  38. package/dist-client/pages/activity-template/activity-template-importer.js.map +1 -1
  39. package/dist-client/pages/activity-template/activity-template-list-page.js +8 -9
  40. package/dist-client/pages/activity-template/activity-template-list-page.js.map +1 -1
  41. package/dist-client/pages/activity-template/activity-template-model-item-list.js +4 -3
  42. package/dist-client/pages/activity-template/activity-template-model-item-list.js.map +1 -1
  43. package/dist-client/pages/activity-thread/activity-thread-importer.js +4 -3
  44. package/dist-client/pages/activity-thread/activity-thread-importer.js.map +1 -1
  45. package/dist-client/pages/activity-thread/activity-thread-list-page.js +8 -9
  46. package/dist-client/pages/activity-thread/activity-thread-list-page.js.map +1 -1
  47. package/dist-client/pages/activity-thread/activity-thread-page.d.ts +1 -1
  48. package/dist-client/pages/activity-thread/activity-thread-page.js +22 -20
  49. package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
  50. package/dist-client/pages/activity-thread/activity-thread-view-page.d.ts +1 -1
  51. package/dist-client/pages/activity-thread/activity-thread-view-page.js +1 -0
  52. package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
  53. package/dist-client/pages/activity-thread/activity-thread-view.d.ts +1 -1
  54. package/dist-client/pages/todo/draft-list-page.js +1 -0
  55. package/dist-client/pages/todo/draft-list-page.js.map +1 -1
  56. package/dist-client/route.d.ts +1 -1
  57. package/dist-client/tsconfig.tsbuildinfo +1 -1
  58. package/dist-server/controllers/activity-installation/installable-to-activity.d.ts +14 -1
  59. package/dist-server/controllers/activity-installation/installable-to-activity.js +14 -1
  60. package/dist-server/controllers/activity-installation/installable-to-activity.js.map +1 -1
  61. package/dist-server/controllers/activity-instance/draft.js +3 -0
  62. package/dist-server/controllers/activity-instance/draft.js.map +1 -1
  63. package/dist-server/controllers/activity-instance/entitlement.d.ts +25 -0
  64. package/dist-server/controllers/activity-instance/entitlement.js +108 -0
  65. package/dist-server/controllers/activity-instance/entitlement.js.map +1 -0
  66. package/dist-server/controllers/activity-instance/issue.js +10 -0
  67. package/dist-server/controllers/activity-instance/issue.js.map +1 -1
  68. package/dist-server/controllers/activity-instance/pick.js +8 -0
  69. package/dist-server/controllers/activity-instance/pick.js.map +1 -1
  70. package/dist-server/service/activity/index.d.ts +1 -1
  71. package/dist-server/service/activity-instance/activity-instance-mutation.js +20 -9
  72. package/dist-server/service/activity-instance/activity-instance-mutation.js.map +1 -1
  73. package/dist-server/service/activity-instance/activity-instance-query.js +25 -2
  74. package/dist-server/service/activity-instance/activity-instance-query.js.map +1 -1
  75. package/dist-server/service/activity-instance/index.d.ts +1 -1
  76. package/dist-server/service/activity-template/index.d.ts +1 -1
  77. package/dist-server/service/activity-thread/index.d.ts +1 -1
  78. package/dist-server/service/index.d.ts +3 -3
  79. package/dist-server/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +11 -11
  81. package/translations/en.json +5 -0
  82. package/translations/ja.json +5 -0
  83. package/translations/ko.json +5 -0
  84. package/translations/ms.json +5 -0
  85. package/translations/zh.json +5 -0
@@ -6,6 +6,7 @@ import { css, html, LitElement } from 'lit';
6
6
  import { customElement, property, state } from 'lit/decorators.js';
7
7
  import { client } from '@operato/graphql';
8
8
  import { i18next, localize } from '@operato/i18n';
9
+ import { ranMutation } from './say-outcome.js';
9
10
  import { notify } from '@operato/layout';
10
11
  import { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles';
11
12
  let ActivityInstancePreview = class ActivityInstancePreview extends localize(i18next)(LitElement) {
@@ -142,7 +143,7 @@ let ActivityInstancePreview = class ActivityInstancePreview extends localize(i18
142
143
  this.activityThreads = response.data.activityThreads.items;
143
144
  }
144
145
  async pickActivityInstance() {
145
- const response = await client.mutate({
146
+ const outcome = await ranMutation(() => client.mutate({
146
147
  mutation: gql `
147
148
  mutation ($id: String!) {
148
149
  pickActivityInstance(id: $id) {
@@ -153,10 +154,10 @@ let ActivityInstancePreview = class ActivityInstancePreview extends localize(i18
153
154
  variables: {
154
155
  id: this.activityInstanceId
155
156
  }
156
- });
157
- if (!response.errors) {
157
+ }));
158
+ if (outcome.ok) {
158
159
  this.picked = true;
159
- this.activityThreads = [response.data.pickActivityInstance];
160
+ this.activityThreads = [outcome.data.pickActivityInstance];
160
161
  notify({
161
162
  message: i18next.t('text.info_x_successfully', {
162
163
  x: `${i18next.t('label.activity-instance')} ${i18next.t('text.pick')}`
@@ -1 +1 @@
1
- {"version":3,"file":"activity-instance-preview.js","sourceRoot":"","sources":["../../client/components/activity-instance-preview.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/elevated-button.js'\nimport '@operato/property-editor/ox-properties-dynamic-view.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\n\n@customElement('activity-instance-preview')\nclass ActivityInstancePreview extends localize(i18next)(LitElement) {\n static styles = [\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n background-color: var(--md-sys-color-surface);\n }\n\n content {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: var(--spacing-medium);\n overflow: auto;\n }\n\n section {\n display: flex;\n flex-direction: column;\n }\n\n #description {\n height: 60px;\n }\n `\n ]\n\n @property({ type: String }) activityInstanceId?: string\n @property({ type: Object }) activityInstance: any = {}\n @property({ type: Array }) activityThreads: any\n\n @state() picked: boolean = false\n\n render() {\n const {\n id,\n name,\n description,\n input,\n state,\n threadsMin = 0,\n threadsMax = 0,\n activity\n } = this.activityInstance || {}\n const { model, thumbnail } = activity || {}\n\n const inputSpec = (model || [])\n .filter(item => item.inout === 'in' || item.inout === 'inout')\n .map(item => {\n return {\n ...item,\n label: item.name\n }\n })\n\n const pickable = !this.picked && this.activityThreads?.length < threadsMax\n\n return html`\n <content>\n <section>\n <h2>mission statement</h2>\n <div>title: ${name}</div>\n <div>${description}</div>\n <div>min-max: ${threadsMin} - ${threadsMax}</div>\n <img src=${thumbnail} />\n </section>\n\n <label>Input</label>\n ${!model\n ? html``\n : html`\n <ox-properties-dynamic-view\n .props=${inputSpec}\n .value=${input}\n @property-change=${e => {\n this.activityInstance.input = e.currentTarget.value\n console.log('activity-instance', this.activityInstance.input)\n }}\n ></ox-properties-dynamic-view>\n `}\n </content>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${e => history.back()}><md-icon>close</md-icon>${i18next.t('button.close')}</button>\n ${pickable\n ? html`<button @click=${this.pickActivityInstance.bind(this)} done>\n <md-icon>done</md-icon>${i18next.t('button.pick')}\n </button>`\n : html``}\n </div>\n `\n }\n\n updated(changes) {\n if (changes.has('activityInstanceId')) {\n this.fetchAll(this.activityInstanceId)\n }\n }\n\n async fetchAll(id) {\n const response = await client.query({\n query: gql`\n {\n activityInstance(id: \"${id}\") {\n id\n name\n description\n input\n state\n threadsMin\n threadsMax\n activity {\n id\n name\n description\n state\n thumbnail\n model {\n name\n description\n active\n tag\n inout\n type\n unit\n options\n quantifier\n spec\n }\n }\n }\n\n activityThreads(filters: [{ name: \"activityInstanceId\", operator: \"eq\", value: \"${id}\"}]) {\n items {\n id\n }\n total\n }\n }\n `\n })\n\n this.activityInstance = response.data.activityInstance\n this.activityThreads = response.data.activityThreads.items\n }\n\n async pickActivityInstance() {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n pickActivityInstance(id: $id) {\n id\n }\n }\n `,\n variables: {\n id: this.activityInstanceId\n }\n })\n\n if (!response.errors) {\n this.picked = true\n this.activityThreads = [response.data.pickActivityInstance]\n notify({\n message: i18next.t('text.info_x_successfully', {\n x: `${i18next.t('label.activity-instance')} ${i18next.t('text.pick')}`\n })\n })\n }\n }\n}\n"]}
1
+ {"version":3,"file":"activity-instance-preview.js","sourceRoot":"","sources":["../../client/components/activity-instance-preview.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/elevated-button.js'\nimport '@operato/property-editor/ox-properties-dynamic-view.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { ranMutation } from './say-outcome.js'\nimport { notify } from '@operato/layout'\nimport { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\n\n@customElement('activity-instance-preview')\nclass ActivityInstancePreview extends localize(i18next)(LitElement) {\n static styles = [\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n background-color: var(--md-sys-color-surface);\n }\n\n content {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: var(--spacing-medium);\n overflow: auto;\n }\n\n section {\n display: flex;\n flex-direction: column;\n }\n\n #description {\n height: 60px;\n }\n `\n ]\n\n @property({ type: String }) activityInstanceId?: string\n @property({ type: Object }) activityInstance: any = {}\n @property({ type: Array }) activityThreads: any\n\n @state() picked: boolean = false\n\n render() {\n const {\n id,\n name,\n description,\n input,\n state,\n threadsMin = 0,\n threadsMax = 0,\n activity\n } = this.activityInstance || {}\n const { model, thumbnail } = activity || {}\n\n const inputSpec = (model || [])\n .filter(item => item.inout === 'in' || item.inout === 'inout')\n .map(item => {\n return {\n ...item,\n label: item.name\n }\n })\n\n const pickable = !this.picked && this.activityThreads?.length < threadsMax\n\n return html`\n <content>\n <section>\n <h2>mission statement</h2>\n <div>title: ${name}</div>\n <div>${description}</div>\n <div>min-max: ${threadsMin} - ${threadsMax}</div>\n <img src=${thumbnail} />\n </section>\n\n <label>Input</label>\n ${!model\n ? html``\n : html`\n <ox-properties-dynamic-view\n .props=${inputSpec}\n .value=${input}\n @property-change=${e => {\n this.activityInstance.input = e.currentTarget.value\n console.log('activity-instance', this.activityInstance.input)\n }}\n ></ox-properties-dynamic-view>\n `}\n </content>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${e => history.back()}><md-icon>close</md-icon>${i18next.t('button.close')}</button>\n ${pickable\n ? html`<button @click=${this.pickActivityInstance.bind(this)} done>\n <md-icon>done</md-icon>${i18next.t('button.pick')}\n </button>`\n : html``}\n </div>\n `\n }\n\n updated(changes) {\n if (changes.has('activityInstanceId')) {\n this.fetchAll(this.activityInstanceId)\n }\n }\n\n async fetchAll(id) {\n const response = await client.query({\n query: gql`\n {\n activityInstance(id: \"${id}\") {\n id\n name\n description\n input\n state\n threadsMin\n threadsMax\n activity {\n id\n name\n description\n state\n thumbnail\n model {\n name\n description\n active\n tag\n inout\n type\n unit\n options\n quantifier\n spec\n }\n }\n }\n\n activityThreads(filters: [{ name: \"activityInstanceId\", operator: \"eq\", value: \"${id}\"}]) {\n items {\n id\n }\n total\n }\n }\n `\n })\n\n this.activityInstance = response.data.activityInstance\n this.activityThreads = response.data.activityThreads.items\n }\n\n async pickActivityInstance() {\n const outcome = await ranMutation(() => client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n pickActivityInstance(id: $id) {\n id\n }\n }\n `,\n variables: {\n id: this.activityInstanceId\n }\n }))\n\n if (outcome.ok) {\n this.picked = true\n this.activityThreads = [outcome.data.pickActivityInstance]\n notify({\n message: i18next.t('text.info_x_successfully', {\n x: `${i18next.t('label.activity-instance')} ${i18next.t('text.pick')}`\n })\n })\n }\n }\n}\n"]}
@@ -10,6 +10,7 @@ import { customElement, property, state } from 'lit/decorators.js';
10
10
  import { navigate } from '@operato/shell';
11
11
  import { client } from '@operato/graphql';
12
12
  import { i18next, localize } from '@operato/i18n';
13
+ import { ranMutation } from './say-outcome.js';
13
14
  import { notify } from '@operato/layout';
14
15
  import { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles';
15
16
  let ActivityStarterForm = class ActivityStarterForm extends localize(i18next)(LitElement) {
@@ -139,6 +140,7 @@ let ActivityStarterForm = class ActivityStarterForm extends localize(i18next)(Li
139
140
  }
140
141
  approvalLine {
141
142
  type
143
+ participation
142
144
  value
143
145
  approver {
144
146
  id
@@ -189,7 +191,7 @@ let ActivityStarterForm = class ActivityStarterForm extends localize(i18next)(Li
189
191
  assignees,
190
192
  approvalLine
191
193
  };
192
- const response = await client.mutate({
194
+ const outcome = await ranMutation(() => client.mutate({
193
195
  mutation: gql `
194
196
  mutation ($activityInstance: ActivityInstanceDraft!) {
195
197
  draftActivityInstance(activityInstance: $activityInstance) {
@@ -211,6 +213,7 @@ let ActivityStarterForm = class ActivityStarterForm extends localize(i18next)(Li
211
213
  }
212
214
  approvalLine {
213
215
  type
216
+ participation
214
217
  value
215
218
  approver {
216
219
  id
@@ -227,16 +230,17 @@ let ActivityStarterForm = class ActivityStarterForm extends localize(i18next)(Li
227
230
  variables: {
228
231
  activityInstance
229
232
  }
230
- });
231
- if (!response.errors) {
232
- this.activityInstance = response.data.draftActivityInstance;
233
- notify({
234
- message: i18next.t('text.info_x_successfully', {
235
- x: `${i18next.t('label.activity-instance')} ${i18next.t('text.draft')}`
236
- })
237
- });
238
- this.openActivityInstanceStart(this.activityInstance);
233
+ }));
234
+ if (!outcome.ok) {
235
+ return;
239
236
  }
237
+ this.activityInstance = outcome.data.draftActivityInstance;
238
+ notify({
239
+ message: i18next.t('text.info_x_successfully', {
240
+ x: `${i18next.t('label.activity-instance')} ${i18next.t('text.draft')}`
241
+ })
242
+ });
243
+ this.openActivityInstanceStart(this.activityInstance);
240
244
  }
241
245
  openActivityInstanceStart(activityInstance) {
242
246
  navigate(`activity-instance-start/${activityInstance.id}?title=${activityInstance.name}`);
@@ -1 +1 @@
1
- {"version":3,"file":"activity-starter-form.js","sourceRoot":"","sources":["../../client/components/activity-starter-form.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/textfield/outlined-text-field.js'\nimport '@material/web/textfield/filled-text-field.js'\nimport '@operato/property-editor/ox-properties-dynamic-view.js'\nimport '@things-factory/organization'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { navigate } from '@operato/shell'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\n\n@customElement('activity-starter-form')\nexport class ActivityStarterForm extends localize(i18next)(LitElement) {\n static styles = [\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n color: var(--md-sys-color-on-s);\n background-color: var(--md-sys-color-surface);\n }\n\n content {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: var(--spacing-medium);\n gap: var(--spacing-medium);\n overflow: auto;\n }\n\n content > div {\n display: flex;\n flex-direction: column;\n }\n\n content label {\n font: var(--label-font);\n }\n content input,\n content textarea {\n border-radius: var(--border-radius);\n border: var(--border-dim-color);\n padding: var(--input-padding);\n margin: var(--input-margin);\n font: var(--input-font);\n }\n\n content [desc] {\n font-size: 0.9em;\n }\n\n #description {\n flex: 1;\n }\n `\n ]\n\n @property({ type: String }) activityId?: string\n\n @state() activity: any = {}\n @state() activityInstance: any = {}\n\n render() {\n var { thumbnail, startingType } = this.activity || {}\n var { name, description } = this.activityInstance\n\n return html`\n <content>\n <div desc>\n <span\n >업무의 타이틀과 설명을 작성해서 초안저장을 클릭하시면, 업무 초안이 생성되고 편집할 수 있는 페이지로\n 이동합니다.</span\n >\n\n <span\n >이렇게 생성된 업무 초안은 '작성중인 업무' 리스트에서 찾아볼 수 있으며,\n '${startingType == 'issue' ? '이슈' : '게시'}' 전까지 언제든지 수정할 수 있습니다.</span\n >\n </div>\n\n <md-outlined-text-field\n id=\"name\"\n type=\"text\"\n label=${String(i18next.t('label.title'))}\n .value=${name || this.activity?.name || ''}\n @change=${e => (this.activityInstance.name = e.currentTarget.value)}\n >\n </md-outlined-text-field>\n\n <md-filled-text-field\n id=\"description\"\n type=\"textarea\"\n label=${String(i18next.t('label.description'))}\n resize=\"none\"\n .value=${description || this.activity?.description || ''}\n @change=${e => {\n this.activityInstance.description = e.currentTarget.value\n }}\n >\n </md-filled-text-field>\n </content>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${e => history.back()}><md-icon>close</md-icon>${i18next.t('button.close')}</button>\n <button @click=${this.draftActivityInstance.bind(this)} done>\n <md-icon>save</md-icon>${i18next.t('button.draft')}\n </button>\n </div>\n `\n }\n\n firstUpdated() {\n this.fetchActivity(this.activityId)\n }\n\n async fetchActivity(id) {\n const response = await client.query({\n query: gql`\n query activity($id: String!) {\n activity(id: $id) {\n id\n name\n description\n state\n multiple\n startingType\n assignees {\n type\n value\n assignee {\n id\n name\n description\n controlNo\n }\n }\n approvalLine {\n type\n value\n approver {\n id\n name\n description\n controlNo\n }\n }\n thumbnail\n model {\n name\n description\n active\n tag\n inout\n type\n unit\n options\n quantifier\n spec\n }\n }\n }\n `,\n variables: {\n id\n }\n })\n\n this.activity = response.data.activity\n }\n\n async draftActivityInstance() {\n var { id: activityId } = this.activity\n var { id, name, description, priority, threadsMin, threadsMax, dueAt, input, assignees, approvalLine } =\n this.activityInstance\n\n name ||= this.activity.name\n description ||= this.activity.description\n assignees ||= this.activity.assignees\n approvalLine ||= this.activity.approvalLine\n\n var activityInstance = {\n id,\n activityId,\n name,\n description,\n priority,\n threadsMax,\n threadsMin,\n dueAt,\n input,\n assignees,\n approvalLine\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($activityInstance: ActivityInstanceDraft!) {\n draftActivityInstance(activityInstance: $activityInstance) {\n id\n name\n description\n input\n output\n state\n assignees {\n type\n value\n assignee {\n id\n name\n description\n controlNo\n }\n }\n approvalLine {\n type\n value\n approver {\n id\n name\n description\n controlNo\n }\n }\n threadsMin\n threadsMax\n }\n }\n `,\n variables: {\n activityInstance\n }\n })\n\n if (!response.errors) {\n this.activityInstance = response.data.draftActivityInstance\n notify({\n message: i18next.t('text.info_x_successfully', {\n x: `${i18next.t('label.activity-instance')} ${i18next.t('text.draft')}`\n })\n })\n\n this.openActivityInstanceStart(this.activityInstance)\n }\n }\n\n openActivityInstanceStart(activityInstance) {\n navigate(`activity-instance-start/${activityInstance.id}?title=${activityInstance.name}`)\n }\n}\n"]}
1
+ {"version":3,"file":"activity-starter-form.js","sourceRoot":"","sources":["../../client/components/activity-starter-form.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/textfield/outlined-text-field.js'\nimport '@material/web/textfield/filled-text-field.js'\nimport '@operato/property-editor/ox-properties-dynamic-view.js'\nimport '@things-factory/organization'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { navigate } from '@operato/shell'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { ranMutation } from './say-outcome.js'\nimport { notify } from '@operato/layout'\nimport { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\n\n@customElement('activity-starter-form')\nexport class ActivityStarterForm extends localize(i18next)(LitElement) {\n static styles = [\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n color: var(--md-sys-color-on-s);\n background-color: var(--md-sys-color-surface);\n }\n\n content {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: var(--spacing-medium);\n gap: var(--spacing-medium);\n overflow: auto;\n }\n\n content > div {\n display: flex;\n flex-direction: column;\n }\n\n content label {\n font: var(--label-font);\n }\n content input,\n content textarea {\n border-radius: var(--border-radius);\n border: var(--border-dim-color);\n padding: var(--input-padding);\n margin: var(--input-margin);\n font: var(--input-font);\n }\n\n content [desc] {\n font-size: 0.9em;\n }\n\n #description {\n flex: 1;\n }\n `\n ]\n\n @property({ type: String }) activityId?: string\n\n @state() activity: any = {}\n @state() activityInstance: any = {}\n\n render() {\n var { thumbnail, startingType } = this.activity || {}\n var { name, description } = this.activityInstance\n\n return html`\n <content>\n <div desc>\n <span\n >업무의 타이틀과 설명을 작성해서 초안저장을 클릭하시면, 업무 초안이 생성되고 편집할 수 있는 페이지로\n 이동합니다.</span\n >\n\n <span\n >이렇게 생성된 업무 초안은 '작성중인 업무' 리스트에서 찾아볼 수 있으며,\n '${startingType == 'issue' ? '이슈' : '게시'}' 전까지 언제든지 수정할 수 있습니다.</span\n >\n </div>\n\n <md-outlined-text-field\n id=\"name\"\n type=\"text\"\n label=${String(i18next.t('label.title'))}\n .value=${name || this.activity?.name || ''}\n @change=${e => (this.activityInstance.name = e.currentTarget.value)}\n >\n </md-outlined-text-field>\n\n <md-filled-text-field\n id=\"description\"\n type=\"textarea\"\n label=${String(i18next.t('label.description'))}\n resize=\"none\"\n .value=${description || this.activity?.description || ''}\n @change=${e => {\n this.activityInstance.description = e.currentTarget.value\n }}\n >\n </md-filled-text-field>\n </content>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${e => history.back()}><md-icon>close</md-icon>${i18next.t('button.close')}</button>\n <button @click=${this.draftActivityInstance.bind(this)} done>\n <md-icon>save</md-icon>${i18next.t('button.draft')}\n </button>\n </div>\n `\n }\n\n firstUpdated() {\n this.fetchActivity(this.activityId)\n }\n\n async fetchActivity(id) {\n const response = await client.query({\n query: gql`\n query activity($id: String!) {\n activity(id: $id) {\n id\n name\n description\n state\n multiple\n startingType\n assignees {\n type\n value\n assignee {\n id\n name\n description\n controlNo\n }\n }\n approvalLine {\n type\n participation\n value\n approver {\n id\n name\n description\n controlNo\n }\n }\n thumbnail\n model {\n name\n description\n active\n tag\n inout\n type\n unit\n options\n quantifier\n spec\n }\n }\n }\n `,\n variables: {\n id\n }\n })\n\n this.activity = response.data.activity\n }\n\n async draftActivityInstance() {\n var { id: activityId } = this.activity\n var { id, name, description, priority, threadsMin, threadsMax, dueAt, input, assignees, approvalLine } =\n this.activityInstance\n\n name ||= this.activity.name\n description ||= this.activity.description\n assignees ||= this.activity.assignees\n approvalLine ||= this.activity.approvalLine\n\n var activityInstance = {\n id,\n activityId,\n name,\n description,\n priority,\n threadsMax,\n threadsMin,\n dueAt,\n input,\n assignees,\n approvalLine\n }\n\n const outcome = await ranMutation(() => client.mutate({\n mutation: gql`\n mutation ($activityInstance: ActivityInstanceDraft!) {\n draftActivityInstance(activityInstance: $activityInstance) {\n id\n name\n description\n input\n output\n state\n assignees {\n type\n value\n assignee {\n id\n name\n description\n controlNo\n }\n }\n approvalLine {\n type\n participation\n value\n approver {\n id\n name\n description\n controlNo\n }\n }\n threadsMin\n threadsMax\n }\n }\n `,\n variables: {\n activityInstance\n }\n }))\n\n if (!outcome.ok) { return\n }\n\n this.activityInstance = outcome.data.draftActivityInstance\n notify({\n message: i18next.t('text.info_x_successfully', {\n x: `${i18next.t('label.activity-instance')} ${i18next.t('text.draft')}`\n })\n })\n\n this.openActivityInstanceStart(this.activityInstance)\n }\n\n openActivityInstanceStart(activityInstance) {\n navigate(`activity-instance-start/${activityInstance.id}?title=${activityInstance.name}`)\n }\n}\n"]}
@@ -17,12 +17,12 @@ export declare class ActivityThreadTimeline extends LitElement {
17
17
  * 보일 항목 없음 화면이 「타임라인의 상신 사유를 보고 판단하세요」로 여기를 가리킨다
18
18
  */
19
19
  open?: boolean;
20
- render(): import("lit-html").TemplateResult<1> | typeof nothing;
20
+ render(): typeof nothing | import("lit-html").TemplateResult<1>;
21
21
  /** 접혔을 때 보이는 한 줄 — 마지막 기록의 「무엇을 · 누가 · 언제」. */
22
22
  lastLine(item: any): string;
23
23
  renderActivityThreadHistory(activityThreadHistory: ActivityThreadHistory): import("lit-html").TemplateResult<1>;
24
24
  renderActivityApproval(activityApproval: ActivityApproval): import("lit-html").TemplateResult<1>;
25
25
  /** 적힌 말이 없으면 상자를 그리지 않는다 — 빈 회색 상자는 내용이 있는 줄과 구별되지 않는다. */
26
- renderSaid(said?: string): import("lit-html").TemplateResult<1> | typeof nothing;
26
+ renderSaid(said?: string): typeof nothing | import("lit-html").TemplateResult<1>;
27
27
  renderOutput(output: any): string;
28
28
  }
@@ -75,15 +75,44 @@ export declare function slotsOfRole(slots: DecisionSlot[], role: string): Decisi
75
75
  */
76
76
  export declare function slotOfRole(slots: DecisionSlot[], role: string): DecisionSlot | undefined;
77
77
  /**
78
- * Whether approving can be undone: true, false, or undefined for "the screen does not know".
78
+ * What the declaration says about undoing this approval.
79
79
  *
80
- * Three answers, not two. A path that led nowhere and a value that is not a boolean both mean the
81
- * payload did not say, and the screen must draw nothing then telling an approver that an action
82
- * can be taken back when nobody said so is the one mistake this line cannot make. Reading a
83
- * truthy value as reversible would do exactly that, since a non-empty string is truthy.
80
+ * The value is an object: `reversible` is required and `caveat` is the module's own sentence about
81
+ * it. The asymmetry is the point the boolean decides how heavily the screen draws the band, and
82
+ * a sentence alone cannot decide that.
84
83
  *
85
- * This is not rejectionIsFinal. That says whether **rejecting** ends the case for good; this says
86
- * whether **approving** can be taken back. The twin actuation carries both, which is why they must
87
- * never share a sentence.
84
+ * { reversible: false, caveat: '버린 자재는 돌아오지 않습니다' }
85
+ *
86
+ * ── A condition is written as false ───────────────────────────────────────
87
+ * 「되돌릴 수 있습니다 — 단, 실적이 마감되기 전까지」 is written `false` with the condition in the
88
+ * caveat. Written `true` the screen draws it lightly and the reader walks past the condition. There
89
+ * is no third value: a middle one always draws ambiguously, and the reader has to guess which side
90
+ * it is on. (2026-09-11, architect's ruling)
91
+ *
92
+ * ── Not `note` ───────────────────────────────────────────────────────────
93
+ * ops-contract already has CommandApproval.note, and that one is **the approver's own comment** —
94
+ * a person's words, in the same neighbourhood as this screen. Same word for a module's sentence
95
+ * would be read as the person's. `caveat` is used nowhere in the three repositories.
96
+ */
97
+ export type Reversibility = {
98
+ reversible: boolean;
99
+ caveat?: string;
100
+ };
101
+ /**
102
+ * What the slot says, or undefined when it does not say.
103
+ *
104
+ * Undefined covers both "the path led nowhere" and "nobody declared the role". Drawing nothing is
105
+ * right for both: telling an approver an action can be taken back when the payload never said so
106
+ * is the one mistake this line cannot make, and a truthy read would make it — the string
107
+ * '되돌릴 수 없습니다…' is truthy.
108
+ */
109
+ export declare function reversibilityOf(slot?: DecisionSlot): Reversibility | undefined;
110
+ /**
111
+ * The value is there but not the shape this role takes.
112
+ *
113
+ * plant declared the role and put a sentence at that path, so the band drew nothing at all and the
114
+ * heaviest line on the screen was silently missing. Reporting nothing to the reader is right — the
115
+ * screen cannot tell which way it goes — but a developer should not have to find it by hand. The
116
+ * caller logs this; the rule stays free of side effects so a test can hold it.
88
117
  */
89
- export declare function reversibilityOf(slot?: DecisionSlot): boolean | undefined;
118
+ export declare function reversibilityIsMalformed(slot?: DecisionSlot): boolean;
@@ -94,18 +94,32 @@ export function slotOfRole(slots, role) {
94
94
  return slots.find(slot => slot.role === role);
95
95
  }
96
96
  /**
97
- * Whether approving can be undone: true, false, or undefined for "the screen does not know".
97
+ * What the slot says, or undefined when it does not say.
98
98
  *
99
- * Three answers, not two. A path that led nowhere and a value that is not a boolean both mean the
100
- * payload did not say, and the screen must draw nothing then telling an approver that an action
101
- * can be taken back when nobody said so is the one mistake this line cannot make. Reading a
102
- * truthy value as reversible would do exactly that, since a non-empty string is truthy.
103
- *
104
- * This is not rejectionIsFinal. That says whether **rejecting** ends the case for good; this says
105
- * whether **approving** can be taken back. The twin actuation carries both, which is why they must
106
- * never share a sentence.
99
+ * Undefined covers both "the path led nowhere" and "nobody declared the role". Drawing nothing is
100
+ * right for both: telling an approver an action can be taken back when the payload never said so
101
+ * is the one mistake this line cannot make, and a truthy read would make it — the string
102
+ * '되돌릴 없습니다…' is truthy.
107
103
  */
108
104
  export function reversibilityOf(slot) {
109
- return typeof slot?.value === 'boolean' ? slot.value : undefined;
105
+ const value = slot?.value;
106
+ if (!value || typeof value !== 'object' || typeof value.reversible !== 'boolean') {
107
+ return undefined;
108
+ }
109
+ return {
110
+ reversible: value.reversible,
111
+ ...(typeof value.caveat === 'string' && value.caveat.trim() ? { caveat: value.caveat } : {})
112
+ };
113
+ }
114
+ /**
115
+ * The value is there but not the shape this role takes.
116
+ *
117
+ * plant declared the role and put a sentence at that path, so the band drew nothing at all and the
118
+ * heaviest line on the screen was silently missing. Reporting nothing to the reader is right — the
119
+ * screen cannot tell which way it goes — but a developer should not have to find it by hand. The
120
+ * caller logs this; the rule stays free of side effects so a test can hold it.
121
+ */
122
+ export function reversibilityIsMalformed(slot) {
123
+ return !!slot && slot.found && slot.value != null && reversibilityOf(slot) === undefined;
110
124
  }
111
125
  //# sourceMappingURL=decision-slots.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"decision-slots.js","sourceRoot":"","sources":["../../client/components/decision-slots.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Turns an activity's decisionKeys declaration plus an instance's input into the slots a decision\n * screen draws.\n *\n * ── Nothing is imported ───────────────────────────────────────────────────\n * No lit, no i18next. The rule is a mapping from a declaration and a payload to an ordered list,\n * and keeping it free of imports is what lets the test runner hold it: that runner parses\n * TypeScript by stripping types and reads neither decorators nor enums, so one heavy import takes\n * a whole test file down with it. Labels and markup are the caller's half.\n */\n\n/** The roles that may appear more than once. Every other role names one value. */\nexport const REPEATABLE_ROLES = ['consequence', 'evidence'] as const\n\n/** Listed in the order the screen reads them: what is asked, then past, present, future. */\nexport const DECISION_ROLES = [\n 'headline',\n 'subject',\n 'reversibility',\n 'reason',\n 'evidence',\n 'currentState',\n 'consequence',\n 'policy'\n] as const\n\nexport type DecisionKeyDeclaration = {\n role?: string\n inputPath?: string\n name?: string\n tKey?: string\n unit?: string\n}\n\nexport type DecisionSlot = {\n role: string\n inputPath: string\n tKey?: string\n unit?: string\n /** What the path led to. Undefined both when the path leads nowhere and when the value is unset. */\n value: unknown\n /**\n * Whether the path led anywhere at all.\n *\n * A reason nobody wrote and a reason whose path is wrong look identical in `value`, and the screen\n * owes different answers: the first is 「사유 없음」 in grey, the second is a declaration that does\n * not match the payload and must not be drawn as if the person left it blank. Folding them into\n * one value is the fallback that shifts meaning.\n */\n found: boolean\n}\n\n/**\n * Reads a dotted path out of the input.\n *\n * Dots only. An array index or a wildcard is not part of the path language — drawing a list is the\n * slot's job — so 'shift.passes[0]' looks for a key spelled \"passes[0]\", finds nothing, and reports\n * found: false. The declaration guard refuses such a path when it arrives, so this is the second\n * line rather than the first.\n *\n * A path that runs into a non-object partway reports found: false rather than throwing. The screen\n * has to draw something either way, and a thrown error here would take the whole approval down over\n * one slot.\n */\nexport function valueAtPath(input: any, path: string): { value: unknown; found: boolean } {\n const steps = String(path ?? '')\n .split('.')\n .filter(step => step.length > 0)\n\n if (!steps.length) {\n return { value: undefined, found: false }\n }\n\n let at: any = input\n\n for (const step of steps) {\n if (at === null || at === undefined || typeof at !== 'object') {\n return { value: undefined, found: false }\n }\n\n if (!Object.prototype.hasOwnProperty.call(at, step)) {\n return { value: undefined, found: false }\n }\n\n at = at[step]\n }\n\n return { value: at, found: true }\n}\n\n/**\n * The slots to draw, in the order they were declared.\n *\n * Declaration order is the screen's hierarchy: plant wrote four of these screens by hand and all\n * four put the headline first, then the subject, the reason, what changes, and why it came up. That\n * agreement is what makes the order part of the contract rather than a preference, so this function\n * neither sorts nor groups.\n *\n * Every declared key produces a slot, including one whose path leads nowhere. Dropping those would\n * leave the screen unable to say that a declaration and a payload disagree, and a reader would see\n * a screen that looks complete.\n */\nexport function decisionSlots(\n decisionKeys: DecisionKeyDeclaration[] | null | undefined,\n input: any\n): DecisionSlot[] {\n if (!decisionKeys?.length) {\n return []\n }\n\n return decisionKeys.map(key => {\n const { value, found } = valueAtPath(input, String(key?.inputPath ?? ''))\n\n return {\n role: String(key?.role ?? ''),\n inputPath: String(key?.inputPath ?? ''),\n tKey: key?.tKey,\n unit: key?.unit,\n value,\n found\n }\n })\n}\n\n/** The slots for one role, in declared order. Empty when the activity declared none. */\nexport function slotsOfRole(slots: DecisionSlot[], role: string): DecisionSlot[] {\n return slots.filter(slot => slot.role === role)\n}\n\n/**\n * The single slot for a role that names one value, or undefined.\n *\n * Reaching for a second one would be reading a declaration the guard refuses, so this answers the\n * first and does not quietly merge.\n */\nexport function slotOfRole(slots: DecisionSlot[], role: string): DecisionSlot | undefined {\n return slots.find(slot => slot.role === role)\n}\n\n/**\n * Whether approving can be undone: true, false, or undefined for \"the screen does not know\".\n *\n * Three answers, not two. A path that led nowhere and a value that is not a boolean both mean the\n * payload did not say, and the screen must draw nothing then — telling an approver that an action\n * can be taken back when nobody said so is the one mistake this line cannot make. Reading a\n * truthy value as reversible would do exactly that, since a non-empty string is truthy.\n *\n * This is not rejectionIsFinal. That says whether **rejecting** ends the case for good; this says\n * whether **approving** can be taken back. The twin actuation carries both, which is why they must\n * never share a sentence.\n */\nexport function reversibilityOf(slot?: DecisionSlot): boolean | undefined {\n return typeof slot?.value === 'boolean' ? slot.value : undefined\n}\n"]}
1
+ {"version":3,"file":"decision-slots.js","sourceRoot":"","sources":["../../client/components/decision-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,kFAAkF;AAClF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,aAAa,EAAE,UAAU,CAAU,CAAA;AAEpE,4FAA4F;AAC5F,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU;IACV,SAAS;IACT,eAAe;IACf,QAAQ;IACR,UAAU;IACV,cAAc;IACd,aAAa;IACb,QAAQ;CACA,CAAA;AA4BV;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,KAAU,EAAE,IAAY;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;SAC7B,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAElC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IAC3C,CAAC;IAED,IAAI,EAAE,GAAQ,KAAK,CAAA;IAEnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,SAAS,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC9D,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;QAC3C,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;YACpD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;QAC3C,CAAC;QAED,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IACf,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACnC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAC3B,YAAyD,EACzD,KAAU;IAEV,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAA;IACX,CAAC;IAED,OAAO,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC5B,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,CAAA;QAEzE,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC;YAC7B,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,SAAS,IAAI,EAAE,CAAC;YACvC,IAAI,EAAE,GAAG,EAAE,IAAI;YACf,IAAI,EAAE,GAAG,EAAE,IAAI;YACf,KAAK;YACL,KAAK;SACN,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,WAAW,CAAC,KAAqB,EAAE,IAAY;IAC7D,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAAqB,EAAE,IAAY;IAC5D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AAC/C,CAAC;AAwBD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,MAAM,KAAK,GAAQ,IAAI,EAAE,KAAK,CAAA;IAE9B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACjF,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,OAAO;QACL,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,GAAG,CAAC,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7F,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAmB;IAC1D,OAAO,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,SAAS,CAAA;AAC1F,CAAC","sourcesContent":["/**\n * Turns an activity's decisionKeys declaration plus an instance's input into the slots a decision\n * screen draws.\n *\n * ── Nothing is imported ───────────────────────────────────────────────────\n * No lit, no i18next. The rule is a mapping from a declaration and a payload to an ordered list,\n * and keeping it free of imports is what lets the test runner hold it: that runner parses\n * TypeScript by stripping types and reads neither decorators nor enums, so one heavy import takes\n * a whole test file down with it. Labels and markup are the caller's half.\n */\n\n/** The roles that may appear more than once. Every other role names one value. */\nexport const REPEATABLE_ROLES = ['consequence', 'evidence'] as const\n\n/** Listed in the order the screen reads them: what is asked, then past, present, future. */\nexport const DECISION_ROLES = [\n 'headline',\n 'subject',\n 'reversibility',\n 'reason',\n 'evidence',\n 'currentState',\n 'consequence',\n 'policy'\n] as const\n\nexport type DecisionKeyDeclaration = {\n role?: string\n inputPath?: string\n name?: string\n tKey?: string\n unit?: string\n}\n\nexport type DecisionSlot = {\n role: string\n inputPath: string\n tKey?: string\n unit?: string\n /** What the path led to. Undefined both when the path leads nowhere and when the value is unset. */\n value: unknown\n /**\n * Whether the path led anywhere at all.\n *\n * A reason nobody wrote and a reason whose path is wrong look identical in `value`, and the screen\n * owes different answers: the first is 「사유 없음」 in grey, the second is a declaration that does\n * not match the payload and must not be drawn as if the person left it blank. Folding them into\n * one value is the fallback that shifts meaning.\n */\n found: boolean\n}\n\n/**\n * Reads a dotted path out of the input.\n *\n * Dots only. An array index or a wildcard is not part of the path language — drawing a list is the\n * slot's job — so 'shift.passes[0]' looks for a key spelled \"passes[0]\", finds nothing, and reports\n * found: false. The declaration guard refuses such a path when it arrives, so this is the second\n * line rather than the first.\n *\n * A path that runs into a non-object partway reports found: false rather than throwing. The screen\n * has to draw something either way, and a thrown error here would take the whole approval down over\n * one slot.\n */\nexport function valueAtPath(input: any, path: string): { value: unknown; found: boolean } {\n const steps = String(path ?? '')\n .split('.')\n .filter(step => step.length > 0)\n\n if (!steps.length) {\n return { value: undefined, found: false }\n }\n\n let at: any = input\n\n for (const step of steps) {\n if (at === null || at === undefined || typeof at !== 'object') {\n return { value: undefined, found: false }\n }\n\n if (!Object.prototype.hasOwnProperty.call(at, step)) {\n return { value: undefined, found: false }\n }\n\n at = at[step]\n }\n\n return { value: at, found: true }\n}\n\n/**\n * The slots to draw, in the order they were declared.\n *\n * Declaration order is the screen's hierarchy: plant wrote four of these screens by hand and all\n * four put the headline first, then the subject, the reason, what changes, and why it came up. That\n * agreement is what makes the order part of the contract rather than a preference, so this function\n * neither sorts nor groups.\n *\n * Every declared key produces a slot, including one whose path leads nowhere. Dropping those would\n * leave the screen unable to say that a declaration and a payload disagree, and a reader would see\n * a screen that looks complete.\n */\nexport function decisionSlots(\n decisionKeys: DecisionKeyDeclaration[] | null | undefined,\n input: any\n): DecisionSlot[] {\n if (!decisionKeys?.length) {\n return []\n }\n\n return decisionKeys.map(key => {\n const { value, found } = valueAtPath(input, String(key?.inputPath ?? ''))\n\n return {\n role: String(key?.role ?? ''),\n inputPath: String(key?.inputPath ?? ''),\n tKey: key?.tKey,\n unit: key?.unit,\n value,\n found\n }\n })\n}\n\n/** The slots for one role, in declared order. Empty when the activity declared none. */\nexport function slotsOfRole(slots: DecisionSlot[], role: string): DecisionSlot[] {\n return slots.filter(slot => slot.role === role)\n}\n\n/**\n * The single slot for a role that names one value, or undefined.\n *\n * Reaching for a second one would be reading a declaration the guard refuses, so this answers the\n * first and does not quietly merge.\n */\nexport function slotOfRole(slots: DecisionSlot[], role: string): DecisionSlot | undefined {\n return slots.find(slot => slot.role === role)\n}\n\n/**\n * What the declaration says about undoing this approval.\n *\n * The value is an object: `reversible` is required and `caveat` is the module's own sentence about\n * it. The asymmetry is the point — the boolean decides how heavily the screen draws the band, and\n * a sentence alone cannot decide that.\n *\n * { reversible: false, caveat: '버린 자재는 돌아오지 않습니다' }\n *\n * ── A condition is written as false ───────────────────────────────────────\n * 「되돌릴 수 있습니다 — 단, 실적이 마감되기 전까지」 is written `false` with the condition in the\n * caveat. Written `true` the screen draws it lightly and the reader walks past the condition. There\n * is no third value: a middle one always draws ambiguously, and the reader has to guess which side\n * it is on. (2026-09-11, architect's ruling)\n *\n * ── Not `note` ───────────────────────────────────────────────────────────\n * ops-contract already has CommandApproval.note, and that one is **the approver's own comment** —\n * a person's words, in the same neighbourhood as this screen. Same word for a module's sentence\n * would be read as the person's. `caveat` is used nowhere in the three repositories.\n */\nexport type Reversibility = { reversible: boolean; caveat?: string }\n\n/**\n * What the slot says, or undefined when it does not say.\n *\n * Undefined covers both \"the path led nowhere\" and \"nobody declared the role\". Drawing nothing is\n * right for both: telling an approver an action can be taken back when the payload never said so\n * is the one mistake this line cannot make, and a truthy read would make it — the string\n * '되돌릴 수 없습니다…' is truthy.\n */\nexport function reversibilityOf(slot?: DecisionSlot): Reversibility | undefined {\n const value: any = slot?.value\n\n if (!value || typeof value !== 'object' || typeof value.reversible !== 'boolean') {\n return undefined\n }\n\n return {\n reversible: value.reversible,\n ...(typeof value.caveat === 'string' && value.caveat.trim() ? { caveat: value.caveat } : {})\n }\n}\n\n/**\n * The value is there but not the shape this role takes.\n *\n * plant declared the role and put a sentence at that path, so the band drew nothing at all and the\n * heaviest line on the screen was silently missing. Reporting nothing to the reader is right — the\n * screen cannot tell which way it goes — but a developer should not have to find it by hand. The\n * caller logs this; the rule stays free of side effects so a test can hold it.\n */\nexport function reversibilityIsMalformed(slot?: DecisionSlot): boolean {\n return !!slot && slot.found && slot.value != null && reversibilityOf(slot) === undefined\n}\n"]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * What came back from a mutation, in the terms a screen has to speak.
3
+ *
4
+ * ── Two failures that arrive by different roads ───────────────────────────
5
+ * A privilege refusal is a GraphQL error. The server marks it itself
6
+ * ([읽었음] auth-base/server/service/privilege/privilege-rejection.ts:61) with
7
+ * `extensions: { code: 'FORBIDDEN', requires }`, and `mutate` is configured with
8
+ * `errorPolicy: 'all'` ([읽었음] operato/webcomponents/graphql/src/graphql-client.ts:18), so it
9
+ * **resolves** with `errors` rather than throwing.
10
+ *
11
+ * A missing or expired session never reaches GraphQL at all:
12
+ *
13
+ * [확인함] curl -X POST http://localhost:4000/graphql -d '{"query":"{ __typename }"}'
14
+ * HTTP 401 · body: No auth token
15
+ *
16
+ * That is an HTTP failure with a plain-text body, so it arrives as a thrown error, not as `errors`.
17
+ * A handler that only looks at `errors` misses it, and the throw takes the rest of the handler with
18
+ * it. Both roads are handled here so a caller does not have to remember there are two.
19
+ *
20
+ * ── Why they must not share a sentence ────────────────────────────────────
21
+ * Telling someone whose session expired to ask an administrator for a privilege sends them to the
22
+ * wrong place, and they will wait for something nobody is going to do. (2026-09-11, architect)
23
+ *
24
+ * Nothing is imported: the test runner strips types without reading decorators, and one heavy
25
+ * import takes a whole test file down — that happened three times in one day.
26
+ */
27
+ export type MutationOutcome =
28
+ /** It went through. `data` is what the mutation answered, so the caller need not keep the response. */
29
+ {
30
+ ok: true;
31
+ data: any;
32
+ }
33
+ /** The server said this user may not. `requires` is what it said is needed, when it said. */
34
+ | {
35
+ ok: false;
36
+ kind: 'forbidden';
37
+ said: string;
38
+ requires?: unknown;
39
+ }
40
+ /** No session reached the server. Signing in again is the answer, not a privilege. */
41
+ | {
42
+ ok: false;
43
+ kind: 'signed-out';
44
+ said: string;
45
+ }
46
+ /** Something else. `said` is the server's own sentence — this side does not know the cause. */
47
+ | {
48
+ ok: false;
49
+ kind: 'failed';
50
+ said: string;
51
+ };
52
+ /** Reading a resolved response. */
53
+ export declare function outcomeOf(response: any): MutationOutcome;
54
+ /** Reading a thrown error — the road a 401 takes. */
55
+ export declare function outcomeOfThrow(thrown: any): MutationOutcome;
56
+ /** The i18n key for a failure, so every screen says the same thing about the same thing. */
57
+ export declare function outcomeKey(outcome: MutationOutcome): string;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * What came back from a mutation, in the terms a screen has to speak.
3
+ *
4
+ * ── Two failures that arrive by different roads ───────────────────────────
5
+ * A privilege refusal is a GraphQL error. The server marks it itself
6
+ * ([읽었음] auth-base/server/service/privilege/privilege-rejection.ts:61) with
7
+ * `extensions: { code: 'FORBIDDEN', requires }`, and `mutate` is configured with
8
+ * `errorPolicy: 'all'` ([읽었음] operato/webcomponents/graphql/src/graphql-client.ts:18), so it
9
+ * **resolves** with `errors` rather than throwing.
10
+ *
11
+ * A missing or expired session never reaches GraphQL at all:
12
+ *
13
+ * [확인함] curl -X POST http://localhost:4000/graphql -d '{"query":"{ __typename }"}'
14
+ * HTTP 401 · body: No auth token
15
+ *
16
+ * That is an HTTP failure with a plain-text body, so it arrives as a thrown error, not as `errors`.
17
+ * A handler that only looks at `errors` misses it, and the throw takes the rest of the handler with
18
+ * it. Both roads are handled here so a caller does not have to remember there are two.
19
+ *
20
+ * ── Why they must not share a sentence ────────────────────────────────────
21
+ * Telling someone whose session expired to ask an administrator for a privilege sends them to the
22
+ * wrong place, and they will wait for something nobody is going to do. (2026-09-11, architect)
23
+ *
24
+ * Nothing is imported: the test runner strips types without reading decorators, and one heavy
25
+ * import takes a whole test file down — that happened three times in one day.
26
+ */
27
+ /** Reading a resolved response. */
28
+ export function outcomeOf(response) {
29
+ const errors = response?.errors || [];
30
+ if (!errors.length && response?.data) {
31
+ return { ok: true, data: response.data };
32
+ }
33
+ const forbidden = errors.find((error) => error?.extensions?.code == 'FORBIDDEN');
34
+ if (forbidden) {
35
+ return {
36
+ ok: false,
37
+ kind: 'forbidden',
38
+ said: String(forbidden.message || ''),
39
+ requires: forbidden.extensions?.requires
40
+ };
41
+ }
42
+ const signedOut = errors.find((error) => looksSignedOut(error?.message));
43
+ if (signedOut) {
44
+ return { ok: false, kind: 'signed-out', said: String(signedOut.message || '') };
45
+ }
46
+ return { ok: false, kind: 'failed', said: String(errors[0]?.message || '') };
47
+ }
48
+ /** Reading a thrown error — the road a 401 takes. */
49
+ export function outcomeOfThrow(thrown) {
50
+ const said = String(thrown?.message || thrown || '');
51
+ /*
52
+ * Apollo wraps an HTTP failure, so the 401's body travels inside the message. Matching the text
53
+ * is not ideal; it is what the server gives, since the body is plain text rather than a GraphQL
54
+ * error with a code. If the server ever answers 401 as a GraphQL error, outcomeOf catches it on
55
+ * the other road and this one keeps working.
56
+ */
57
+ if (looksSignedOut(said) || thrown?.statusCode == 401 || thrown?.networkError?.statusCode == 401) {
58
+ return { ok: false, kind: 'signed-out', said };
59
+ }
60
+ return { ok: false, kind: 'failed', said };
61
+ }
62
+ /** The sentence a session failure carries. Matched loosely because the wording is not ours. */
63
+ function looksSignedOut(message) {
64
+ const text = String(message || '').toLowerCase();
65
+ return text.includes('no auth token') || text.includes('jwt expired') || text.includes('unauthenticated');
66
+ }
67
+ /** The i18n key for a failure, so every screen says the same thing about the same thing. */
68
+ export function outcomeKey(outcome) {
69
+ if (outcome.ok) {
70
+ return '';
71
+ }
72
+ return {
73
+ forbidden: 'text.not-allowed-to-do-this',
74
+ 'signed-out': 'text.signed-out-sign-in-again',
75
+ failed: 'text.it-did-not-go-through'
76
+ }[outcome.kind];
77
+ }
78
+ //# sourceMappingURL=mutation-outcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-outcome.js","sourceRoot":"","sources":["../../client/components/mutation-outcome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAYH,mCAAmC;AACnC,MAAM,UAAU,SAAS,CAAC,QAAa;IACrC,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,IAAI,EAAE,CAAA;IAErC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,QAAQ,EAAE,IAAI,EAAE,CAAC;QACrC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC1C,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,IAAI,WAAW,CAAC,CAAA;IAErF,IAAI,SAAS,EAAE,CAAC;QACd,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC;YACrC,QAAQ,EAAE,SAAS,CAAC,UAAU,EAAE,QAAQ;SACzC,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAA;IAE7E,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAA;IACjF,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC,EAAE,CAAA;AAC9E,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,cAAc,CAAC,MAAW;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,IAAI,MAAM,IAAI,EAAE,CAAC,CAAA;IAEpD;;;;;OAKG;IACH,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,UAAU,IAAI,GAAG,IAAI,MAAM,EAAE,YAAY,EAAE,UAAU,IAAI,GAAG,EAAE,CAAC;QACjG,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAA;IAChD,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AAC5C,CAAC;AAED,+FAA+F;AAC/F,SAAS,cAAc,CAAC,OAAY;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAEhD,OAAO,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAA;AAC3G,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,UAAU,CAAC,OAAwB;IACjD,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,EAAE,CAAA;IACX,CAAC;IAED,OAAO;QACL,SAAS,EAAE,6BAA6B;QACxC,YAAY,EAAE,+BAA+B;QAC7C,MAAM,EAAE,4BAA4B;KACrC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AACjB,CAAC","sourcesContent":["/**\n * What came back from a mutation, in the terms a screen has to speak.\n *\n * ── Two failures that arrive by different roads ───────────────────────────\n * A privilege refusal is a GraphQL error. The server marks it itself\n * ([읽었음] auth-base/server/service/privilege/privilege-rejection.ts:61) with\n * `extensions: { code: 'FORBIDDEN', requires }`, and `mutate` is configured with\n * `errorPolicy: 'all'` ([읽었음] operato/webcomponents/graphql/src/graphql-client.ts:18), so it\n * **resolves** with `errors` rather than throwing.\n *\n * A missing or expired session never reaches GraphQL at all:\n *\n * [확인함] curl -X POST http://localhost:4000/graphql -d '{\"query\":\"{ __typename }\"}'\n * HTTP 401 · body: No auth token\n *\n * That is an HTTP failure with a plain-text body, so it arrives as a thrown error, not as `errors`.\n * A handler that only looks at `errors` misses it, and the throw takes the rest of the handler with\n * it. Both roads are handled here so a caller does not have to remember there are two.\n *\n * ── Why they must not share a sentence ────────────────────────────────────\n * Telling someone whose session expired to ask an administrator for a privilege sends them to the\n * wrong place, and they will wait for something nobody is going to do. (2026-09-11, architect)\n *\n * Nothing is imported: the test runner strips types without reading decorators, and one heavy\n * import takes a whole test file down — that happened three times in one day.\n */\n\nexport type MutationOutcome =\n /** It went through. `data` is what the mutation answered, so the caller need not keep the response. */\n | { ok: true; data: any }\n /** The server said this user may not. `requires` is what it said is needed, when it said. */\n | { ok: false; kind: 'forbidden'; said: string; requires?: unknown }\n /** No session reached the server. Signing in again is the answer, not a privilege. */\n | { ok: false; kind: 'signed-out'; said: string }\n /** Something else. `said` is the server's own sentence — this side does not know the cause. */\n | { ok: false; kind: 'failed'; said: string }\n\n/** Reading a resolved response. */\nexport function outcomeOf(response: any): MutationOutcome {\n const errors = response?.errors || []\n\n if (!errors.length && response?.data) {\n return { ok: true, data: response.data }\n }\n\n const forbidden = errors.find((error: any) => error?.extensions?.code == 'FORBIDDEN')\n\n if (forbidden) {\n return {\n ok: false,\n kind: 'forbidden',\n said: String(forbidden.message || ''),\n requires: forbidden.extensions?.requires\n }\n }\n\n const signedOut = errors.find((error: any) => looksSignedOut(error?.message))\n\n if (signedOut) {\n return { ok: false, kind: 'signed-out', said: String(signedOut.message || '') }\n }\n\n return { ok: false, kind: 'failed', said: String(errors[0]?.message || '') }\n}\n\n/** Reading a thrown error — the road a 401 takes. */\nexport function outcomeOfThrow(thrown: any): MutationOutcome {\n const said = String(thrown?.message || thrown || '')\n\n /*\n * Apollo wraps an HTTP failure, so the 401's body travels inside the message. Matching the text\n * is not ideal; it is what the server gives, since the body is plain text rather than a GraphQL\n * error with a code. If the server ever answers 401 as a GraphQL error, outcomeOf catches it on\n * the other road and this one keeps working.\n */\n if (looksSignedOut(said) || thrown?.statusCode == 401 || thrown?.networkError?.statusCode == 401) {\n return { ok: false, kind: 'signed-out', said }\n }\n\n return { ok: false, kind: 'failed', said }\n}\n\n/** The sentence a session failure carries. Matched loosely because the wording is not ours. */\nfunction looksSignedOut(message: any): boolean {\n const text = String(message || '').toLowerCase()\n\n return text.includes('no auth token') || text.includes('jwt expired') || text.includes('unauthenticated')\n}\n\n/** The i18n key for a failure, so every screen says the same thing about the same thing. */\nexport function outcomeKey(outcome: MutationOutcome): string {\n if (outcome.ok) {\n return ''\n }\n\n return {\n forbidden: 'text.not-allowed-to-do-this',\n 'signed-out': 'text.signed-out-sign-in-again',\n failed: 'text.it-did-not-go-through'\n }[outcome.kind]\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { MutationOutcome } from './mutation-outcome.js';
2
+ /**
3
+ * Run a mutation and say so when it does not go through.
4
+ *
5
+ * ── Why a wrapper and not a check at each call ────────────────────────────
6
+ * A failure arrives by two roads — a privilege refusal resolves with `errors`, a missing session
7
+ * throws an HTTP 401 — and every call site would have to remember both. Six handlers in this package
8
+ * remembered neither: they read `errors` only to skip the success branch, with no `else`, so a
9
+ * failed save changed nothing on screen and said nothing. The worker presses the button again.
10
+ *
11
+ * The caller keeps its success path and stops carrying the failure one:
12
+ *
13
+ * const outcome = await ranMutation(() => client.mutate({ … }))
14
+ * if (!outcome.ok) return
15
+ * … what to do when it worked …
16
+ */
17
+ export declare function ranMutation(run: () => Promise<any>): Promise<MutationOutcome>;
18
+ /**
19
+ * One sentence per kind of failure.
20
+ *
21
+ * `said` is the server's own sentence and only the last kind prints it — for the other two this
22
+ * side knows what happened and can say what to do about it. Telling someone whose session expired
23
+ * to ask an administrator for a privilege would send them where nobody can help.
24
+ */
25
+ export declare function sayFailure(outcome: MutationOutcome): void;
@@ -0,0 +1,51 @@
1
+ import { i18next } from '@operato/i18n';
2
+ import { outcomeKey, outcomeOf, outcomeOfThrow } from './mutation-outcome.js';
3
+ /**
4
+ * Run a mutation and say so when it does not go through.
5
+ *
6
+ * ── Why a wrapper and not a check at each call ────────────────────────────
7
+ * A failure arrives by two roads — a privilege refusal resolves with `errors`, a missing session
8
+ * throws an HTTP 401 — and every call site would have to remember both. Six handlers in this package
9
+ * remembered neither: they read `errors` only to skip the success branch, with no `else`, so a
10
+ * failed save changed nothing on screen and said nothing. The worker presses the button again.
11
+ *
12
+ * The caller keeps its success path and stops carrying the failure one:
13
+ *
14
+ * const outcome = await ranMutation(() => client.mutate({ … }))
15
+ * if (!outcome.ok) return
16
+ * … what to do when it worked …
17
+ */
18
+ export async function ranMutation(run) {
19
+ let outcome;
20
+ try {
21
+ outcome = outcomeOf(await run());
22
+ }
23
+ catch (thrown) {
24
+ outcome = outcomeOfThrow(thrown);
25
+ }
26
+ if (!outcome.ok) {
27
+ sayFailure(outcome);
28
+ }
29
+ return outcome;
30
+ }
31
+ /**
32
+ * One sentence per kind of failure.
33
+ *
34
+ * `said` is the server's own sentence and only the last kind prints it — for the other two this
35
+ * side knows what happened and can say what to do about it. Telling someone whose session expired
36
+ * to ask an administrator for a privilege would send them where nobody can help.
37
+ */
38
+ export function sayFailure(outcome) {
39
+ if (outcome.ok) {
40
+ return;
41
+ }
42
+ const key = outcomeKey(outcome);
43
+ document.dispatchEvent(new CustomEvent('notify', {
44
+ detail: {
45
+ level: 'error',
46
+ message: i18next.t(key, { said: outcome.said || '' })
47
+ }
48
+ }));
49
+ console.error(`[worklist] ${outcome.kind} — ${outcome.said}`);
50
+ }
51
+ //# sourceMappingURL=say-outcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"say-outcome.js","sourceRoot":"","sources":["../../client/components/say-outcome.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAmB,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE9F;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAuB;IACvD,IAAI,OAAwB,CAAA;IAE5B,IAAI,CAAC;QACH,OAAO,GAAG,SAAS,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAClC,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,UAAU,CAAC,OAAO,CAAC,CAAA;IACrB,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,OAAwB;IACjD,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,OAAM;IACR,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,CAAA;IAE/B,QAAQ,CAAC,aAAa,CACpB,IAAI,WAAW,CAAC,QAAQ,EAAE;QACxB,MAAM,EAAE;YACN,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;SACtD;KACF,CAAC,CACH,CAAA;IAED,OAAO,CAAC,KAAK,CAAC,cAAc,OAAO,CAAC,IAAI,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;AAC/D,CAAC","sourcesContent":["import { i18next } from '@operato/i18n'\n\nimport { MutationOutcome, outcomeKey, outcomeOf, outcomeOfThrow } from './mutation-outcome.js'\n\n/**\n * Run a mutation and say so when it does not go through.\n *\n * ── Why a wrapper and not a check at each call ────────────────────────────\n * A failure arrives by two roads — a privilege refusal resolves with `errors`, a missing session\n * throws an HTTP 401 — and every call site would have to remember both. Six handlers in this package\n * remembered neither: they read `errors` only to skip the success branch, with no `else`, so a\n * failed save changed nothing on screen and said nothing. The worker presses the button again.\n *\n * The caller keeps its success path and stops carrying the failure one:\n *\n * const outcome = await ranMutation(() => client.mutate({ … }))\n * if (!outcome.ok) return\n * … what to do when it worked …\n */\nexport async function ranMutation(run: () => Promise<any>): Promise<MutationOutcome> {\n let outcome: MutationOutcome\n\n try {\n outcome = outcomeOf(await run())\n } catch (thrown) {\n outcome = outcomeOfThrow(thrown)\n }\n\n if (!outcome.ok) {\n sayFailure(outcome)\n }\n\n return outcome\n}\n\n/**\n * One sentence per kind of failure.\n *\n * `said` is the server's own sentence and only the last kind prints it — for the other two this\n * side knows what happened and can say what to do about it. Telling someone whose session expired\n * to ask an administrator for a privilege would send them where nobody can help.\n */\nexport function sayFailure(outcome: MutationOutcome) {\n if (outcome.ok) {\n return\n }\n\n const key = outcomeKey(outcome)\n\n document.dispatchEvent(\n new CustomEvent('notify', {\n detail: {\n level: 'error',\n message: i18next.t(key, { said: outcome.said || '' })\n }\n })\n )\n\n console.error(`[worklist] ${outcome.kind} — ${outcome.said}`)\n}\n"]}