@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.
- package/dist-client/components/activity-instance-preview.js +5 -4
- package/dist-client/components/activity-instance-preview.js.map +1 -1
- package/dist-client/components/activity-starter-form.js +14 -10
- package/dist-client/components/activity-starter-form.js.map +1 -1
- package/dist-client/components/activity-thread-timeline.d.ts +2 -2
- package/dist-client/components/decision-slots.d.ts +38 -9
- package/dist-client/components/decision-slots.js +24 -10
- package/dist-client/components/decision-slots.js.map +1 -1
- package/dist-client/components/mutation-outcome.d.ts +57 -0
- package/dist-client/components/mutation-outcome.js +78 -0
- package/dist-client/components/mutation-outcome.js.map +1 -0
- package/dist-client/components/say-outcome.d.ts +25 -0
- package/dist-client/components/say-outcome.js +51 -0
- package/dist-client/components/say-outcome.js.map +1 -0
- package/dist-client/pages/activity/activity-list-page.js +28 -12
- package/dist-client/pages/activity/activity-list-page.js.map +1 -1
- package/dist-client/pages/activity/activity-model-item-list.js +4 -3
- package/dist-client/pages/activity/activity-model-item-list.js.map +1 -1
- package/dist-client/pages/activity/activity-page.d.ts +2 -2
- package/dist-client/pages/activity/activity-page.js +25 -21
- package/dist-client/pages/activity/activity-page.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-importer.js +4 -3
- package/dist-client/pages/activity-approval/activity-approval-importer.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-list-page.js +8 -9
- package/dist-client/pages/activity-approval/activity-approval-list-page.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-page.d.ts +3 -1
- package/dist-client/pages/activity-approval/activity-approval-page.js +76 -28
- package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-start-page.js +24 -18
- package/dist-client/pages/activity-instance/activity-instance-start-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-view.js +1 -0
- package/dist-client/pages/activity-instance/activity-instance-view.js.map +1 -1
- package/dist-client/pages/activity-stats/activity-stats-importer.js +4 -3
- package/dist-client/pages/activity-stats/activity-stats-importer.js.map +1 -1
- package/dist-client/pages/activity-stats/activity-stats-list-page.js +8 -9
- package/dist-client/pages/activity-stats/activity-stats-list-page.js.map +1 -1
- package/dist-client/pages/activity-template/activity-template-importer.js +4 -3
- package/dist-client/pages/activity-template/activity-template-importer.js.map +1 -1
- package/dist-client/pages/activity-template/activity-template-list-page.js +8 -9
- package/dist-client/pages/activity-template/activity-template-list-page.js.map +1 -1
- package/dist-client/pages/activity-template/activity-template-model-item-list.js +4 -3
- package/dist-client/pages/activity-template/activity-template-model-item-list.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-importer.js +4 -3
- package/dist-client/pages/activity-thread/activity-thread-importer.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-list-page.js +8 -9
- package/dist-client/pages/activity-thread/activity-thread-list-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-page.d.ts +1 -1
- package/dist-client/pages/activity-thread/activity-thread-page.js +22 -20
- package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view-page.d.ts +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view-page.js +1 -0
- package/dist-client/pages/activity-thread/activity-thread-view-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view.d.ts +1 -1
- package/dist-client/pages/todo/draft-list-page.js +1 -0
- package/dist-client/pages/todo/draft-list-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/activity-installation/installable-to-activity.d.ts +14 -1
- package/dist-server/controllers/activity-installation/installable-to-activity.js +14 -1
- package/dist-server/controllers/activity-installation/installable-to-activity.js.map +1 -1
- package/dist-server/controllers/activity-instance/draft.js +3 -0
- package/dist-server/controllers/activity-instance/draft.js.map +1 -1
- package/dist-server/controllers/activity-instance/entitlement.d.ts +25 -0
- package/dist-server/controllers/activity-instance/entitlement.js +108 -0
- package/dist-server/controllers/activity-instance/entitlement.js.map +1 -0
- package/dist-server/controllers/activity-instance/issue.js +10 -0
- package/dist-server/controllers/activity-instance/issue.js.map +1 -1
- package/dist-server/controllers/activity-instance/pick.js +8 -0
- package/dist-server/controllers/activity-instance/pick.js.map +1 -1
- package/dist-server/service/activity/index.d.ts +1 -1
- package/dist-server/service/activity-instance/activity-instance-mutation.js +20 -9
- package/dist-server/service/activity-instance/activity-instance-mutation.js.map +1 -1
- package/dist-server/service/activity-instance/activity-instance-query.js +25 -2
- package/dist-server/service/activity-instance/activity-instance-query.js.map +1 -1
- package/dist-server/service/activity-instance/index.d.ts +1 -1
- package/dist-server/service/activity-template/index.d.ts +1 -1
- package/dist-server/service/activity-thread/index.d.ts +1 -1
- package/dist-server/service/index.d.ts +3 -3
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/translations/en.json +5 -0
- package/translations/ja.json +5 -0
- package/translations/ko.json +5 -0
- package/translations/ms.json +5 -0
- 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
|
|
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 (
|
|
157
|
+
}));
|
|
158
|
+
if (outcome.ok) {
|
|
158
159
|
this.picked = true;
|
|
159
|
-
this.activityThreads = [
|
|
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":";AAAA,OAAO,yCAAyC,CAAA;AAChD,OAAO,wDAAwD,CAAA;AAE/D,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGrE,IAAM,uBAAuB,GAA7B,MAAM,uBAAwB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;IAAnE;;QAgC8B,qBAAgB,GAAQ,EAAE,CAAA;QAG7C,WAAM,GAAY,KAAK,CAAA;IA4IlC,CAAC;aA9KQ,WAAM,GAAG;QACd,kBAAkB;QAClB,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;KAwBF;KACF,AA5BY,CA4BZ;IAQD,MAAM;QACJ,MAAM,EACJ,EAAE,EACF,IAAI,EACJ,WAAW,EACX,KAAK,EACL,KAAK,EACL,UAAU,GAAG,CAAC,EACd,UAAU,GAAG,CAAC,EACd,QAAQ,EACT,GAAG,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAA;QAC/B,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,QAAQ,IAAI,EAAE,CAAA;QAE3C,MAAM,SAAS,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;aAC5B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,CAAC;aAC7D,GAAG,CAAC,IAAI,CAAC,EAAE;YACV,OAAO;gBACL,GAAG,IAAI;gBACP,KAAK,EAAE,IAAI,CAAC,IAAI;aACjB,CAAA;QACH,CAAC,CAAC,CAAA;QAEJ,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,UAAU,CAAA;QAE1E,OAAO,IAAI,CAAA;;;;wBAIS,IAAI;iBACX,WAAW;0BACF,UAAU,MAAM,UAAU;qBAC/B,SAAS;;;;UAIpB,CAAC,KAAK;YACN,CAAC,CAAC,IAAI,CAAA,EAAE;YACR,CAAC,CAAC,IAAI,CAAA;;yBAES,SAAS;yBACT,KAAK;mCACK,CAAC,CAAC,EAAE;gBACrB,IAAI,CAAC,gBAAgB,CAAC,KAAK,GAAG,CAAC,CAAC,aAAa,CAAC,KAAK,CAAA;gBACnD,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAA;YAC/D,CAAC;;aAEJ;;;;;yBAKY,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,4BAA4B,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;UACvF,QAAQ;YACR,CAAC,CAAC,IAAI,CAAA,kBAAkB,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC;uCAC/B,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;sBACzC;YACZ,CAAC,CAAC,IAAI,CAAA,EAAE;;KAEb,CAAA;IACH,CAAC;IAED,OAAO,CAAC,OAAO;QACb,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAE;QACf,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;kCAEkB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4FA6BwD,EAAE;;;;;;;OAOvF;SACF,CAAC,CAAA;QAEF,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAA;QACtD,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAA;IAC5D,CAAC;IAED,KAAK,CAAC,oBAAoB;QACxB,MAAM,
|
|
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
|
|
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 (!
|
|
232
|
-
|
|
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
|
|
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
|
|
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
|
-
*
|
|
78
|
+
* What the declaration says about undoing this approval.
|
|
79
79
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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
|
|
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
|
-
*
|
|
97
|
+
* What the slot says, or undefined when it does not say.
|
|
98
98
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
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
|
-
|
|
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":"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;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,OAAO,OAAO,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAClE,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 * 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":"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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 * 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"]}
|