@things-factory/worklist 10.1.266 → 10.1.268
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-fields.d.ts +5 -0
- package/dist-client/components/activity-fields.js +19 -6
- package/dist-client/components/activity-fields.js.map +1 -1
- package/dist-client/components/activity-thread-timeline.d.ts +2 -2
- package/dist-client/components/decision-content.js +25 -4
- package/dist-client/components/decision-content.js.map +1 -1
- package/dist-client/components/decision-slots.d.ts +11 -0
- package/dist-client/components/decision-slots.js +11 -0
- package/dist-client/components/decision-slots.js.map +1 -1
- package/dist-client/components/source-link.d.ts +7 -1
- package/dist-client/components/source-link.js +27 -2
- package/dist-client/components/source-link.js.map +1 -1
- package/dist-client/pages/activity-approval/activity-approval-page.js +2 -1
- package/dist-client/pages/activity-approval/activity-approval-page.js.map +1 -1
- package/dist-client/pages/activity-instance/activity-instance-view.d.ts +17 -2
- package/dist-client/pages/activity-instance/activity-instance-view.js +112 -61
- package/dist-client/pages/activity-instance/activity-instance-view.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-page.js +2 -1
- package/dist-client/pages/activity-thread/activity-thread-page.js.map +1 -1
- package/dist-client/pages/activity-thread/activity-thread-view.d.ts +1 -1
- package/dist-client/pages/approval-progress/approval-progress-page.js +2 -1
- package/dist-client/pages/approval-progress/approval-progress-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/templates/activity-approval-context-template.js +6 -2
- package/dist-client/templates/activity-approval-context-template.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/translations/en.json +3 -1
- package/translations/es.json +3 -1
- package/translations/ja.json +3 -1
- package/translations/ko.json +3 -1
- package/translations/ms.json +3 -1
- package/translations/zh.json +3 -1
|
@@ -68,6 +68,11 @@ export declare function declaredLabel(item: any): string;
|
|
|
68
68
|
* 값이 없으면 빈 칸으로 두지 않는다 — 빈 칸은 값이 없는 것인지 화면이 못 낸 것인지 모른다.
|
|
69
69
|
*/
|
|
70
70
|
export declare function fieldValue(value: any, depth?: number): unknown;
|
|
71
|
+
/**
|
|
72
|
+
* The same laying-out, without the copy button - for values a person reads rather than takes
|
|
73
|
+
* elsewhere: a decision's reason, a sentence list. The button is for payloads (2026-11-10).
|
|
74
|
+
*/
|
|
75
|
+
export declare function readValue(value: any): unknown;
|
|
71
76
|
/**
|
|
72
77
|
* A person as a value - their name opens their 360 (ADR-0067 decision 1), and no one says so as
|
|
73
78
|
* `fieldValue` would. `entity360Name` draws plain text until the person kind is registered.
|
|
@@ -99,9 +99,12 @@ export const ActivityFieldsStyles = css `
|
|
|
99
99
|
* 준다. 붙여 넣는 쪽이 기대하는 것은 원문이다.
|
|
100
100
|
*
|
|
101
101
|
* 값 위에 얹지 않고 오른쪽 위 구석에 둔다 — 값이 한 줄일 때도 두 단 깊을 때도 자리가 같다.
|
|
102
|
+
* 자리의 기준은 값 자신이다(.payload). 기준을 dl.given dd 에만 두었더니 결재 내용처럼 dd 밖에서
|
|
103
|
+
* 그린 값은 단추가 팝업 오른쪽 위 모서리로 빠져 닫기 단추와 겹쳤다(2026-11-10).
|
|
102
104
|
*/
|
|
103
|
-
|
|
105
|
+
.payload {
|
|
104
106
|
position: relative;
|
|
107
|
+
display: block;
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
.copy {
|
|
@@ -317,6 +320,16 @@ const MAX_DEPTH = 4;
|
|
|
317
320
|
* 값이 없으면 빈 칸으로 두지 않는다 — 빈 칸은 값이 없는 것인지 화면이 못 낸 것인지 모른다.
|
|
318
321
|
*/
|
|
319
322
|
export function fieldValue(value, depth = 0) {
|
|
323
|
+
return laidValue(value, depth, true);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* The same laying-out, without the copy button - for values a person reads rather than takes
|
|
327
|
+
* elsewhere: a decision's reason, a sentence list. The button is for payloads (2026-11-10).
|
|
328
|
+
*/
|
|
329
|
+
export function readValue(value) {
|
|
330
|
+
return laidValue(value, 0, false);
|
|
331
|
+
}
|
|
332
|
+
function laidValue(value, depth, copyable) {
|
|
320
333
|
if (value === null || value === undefined || value === '') {
|
|
321
334
|
return html `<span class="none">${i18next.t('text.no-value')}</span>`;
|
|
322
335
|
}
|
|
@@ -330,14 +343,14 @@ export function fieldValue(value, depth = 0) {
|
|
|
330
343
|
if (depth >= MAX_DEPTH) {
|
|
331
344
|
return html `<code>${JSON.stringify(value)}</code>`;
|
|
332
345
|
}
|
|
333
|
-
const laid = layOut(value, depth);
|
|
346
|
+
const laid = layOut(value, depth, copyable);
|
|
334
347
|
/*
|
|
335
348
|
* 맨 바깥 값에만 복사를 붙인다.
|
|
336
349
|
*
|
|
337
350
|
* 안쪽 단마다 달면 네 단짜리 페이로드에 단추가 열 개 넘게 붙어, 정작 읽으라고 편 값이 단추에
|
|
338
351
|
* 묻힌다. 사람이 가져가려는 것은 「이 항목」이다.
|
|
339
352
|
*/
|
|
340
|
-
return depth === 0 ? html `<span class="payload">${copyButton(value)}${laid}</span>` : laid;
|
|
353
|
+
return depth === 0 && copyable ? html `<span class="payload">${copyButton(value)}${laid}</span>` : laid;
|
|
341
354
|
}
|
|
342
355
|
/**
|
|
343
356
|
* 이 값을 원문 그대로 가져간다.
|
|
@@ -377,7 +390,7 @@ function copyButton(value) {
|
|
|
377
390
|
`;
|
|
378
391
|
}
|
|
379
392
|
/** 값을 이름·값 두 칸으로 편다. 복사 단추는 여기 끼지 않는다 — 편 결과만 만든다. */
|
|
380
|
-
function layOut(value, depth) {
|
|
393
|
+
function layOut(value, depth, copyable) {
|
|
381
394
|
if (Array.isArray(value)) {
|
|
382
395
|
/* 빈 배열은 「값 없음」이 아니다 — 「비어 있다」는 그 자체로 사실이다. */
|
|
383
396
|
if (value.length === 0) {
|
|
@@ -393,7 +406,7 @@ function layOut(value, depth) {
|
|
|
393
406
|
*/
|
|
394
407
|
return html `
|
|
395
408
|
<ol class="nested">
|
|
396
|
-
${value.map(item => html `<li>${
|
|
409
|
+
${value.map(item => html `<li>${laidValue(item, depth, copyable)}</li>`)}
|
|
397
410
|
</ol>
|
|
398
411
|
`;
|
|
399
412
|
}
|
|
@@ -409,7 +422,7 @@ function layOut(value, depth) {
|
|
|
409
422
|
<dl class="given nested">
|
|
410
423
|
${keys.map(key => html `
|
|
411
424
|
<dt>${key}</dt>
|
|
412
|
-
<dd>${
|
|
425
|
+
<dd>${laidValue(value[key], depth + 1, copyable)}</dd>
|
|
413
426
|
`)}
|
|
414
427
|
</dl>
|
|
415
428
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-fields.js","sourceRoot":"","sources":["../../client/components/activity-fields.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html } from 'lit'\n\nimport { i18next } from '@operato/i18n'\nimport { entity360Name } from '@operato/layout'\n\n/**\n * 업무 항목을 화면에 내는 방식 — **두 화면이 한 벌을 쓴다.**\n *\n * 업무 상세(activity-thread-page)와 결재 상세(activity-approval-page)는 거의 복사본이다. 항목을\n * 고르는 함수, 화면 종류에 따라 그리는 분기, 절을 그리는 부분이 두 파일에 같은 코드로 있었다.\n * 그래서 한쪽만 고치면 나머지가 그대로 남는다 — 실제로 그랬다(2026-09-06, 상세 화면의\n * Input · Output 영문 legend 를 고쳤는데 결재 화면에는 그대로 있었다).\n *\n * 화면마다 다른 것은 남긴다 — 상세는 결과를 적고, 결재는 읽기만 한다.\n */\n\n/** 절과 값 표의 모양. 카드 값은 SurfaceStyles 의 .ui-card 와 같다. */\nexport const ActivityFieldsStyles = css`\n section {\n margin: var(--spacing-large, 16px) var(--spacing-large, 16px) 0;\n padding: 14px;\n background: var(--ui-panel, #ffffff);\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: var(--ui-r, 10px);\n }\n\n section > h2 {\n margin: 0 0 10px;\n font-size: var(--ui-t-title, 15px);\n font-weight: 600;\n color: var(--ui-ink, #17242c);\n }\n\n /* 읽기만 하는 절 — 값을 바꿀 수 없다는 것이 보여야 한다. */\n section[readonly] > h2 {\n color: var(--ui-muted, #57676f);\n }\n\n /* 받은 값 — 이름과 값을 두 칸으로. 이름은 오른쪽 정렬이라 값의 왼쪽 끝이 한 줄로 선다. */\n dl.given {\n display: grid;\n grid-template-columns: minmax(80px, max-content) 1fr;\n gap: 7px var(--spacing-large, 16px);\n margin: 0;\n }\n\n dl.given dt {\n text-align: right;\n color: var(--ui-muted, #57676f);\n word-break: keep-all;\n }\n\n dl.given dd {\n margin: 0;\n color: var(--ui-ink, #17242c);\n word-break: break-word;\n }\n\n dl.given .none {\n color: var(--ui-faint, #8a959c);\n }\n\n dl.given code {\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n /*\n * 안쪽으로 펼친 오브젝트 · 배열.\n *\n * 바깥과 같은 두 칸 구조를 쓰되 **한 단 들여쓰고 왼쪽에 선을 긋는다.** 선이 없으면 세 단이 겹칠 때\n * 어느 이름이 어느 값에 딸린 것인지 눈으로 못 가른다.\n *\n * 안쪽 이름은 왼쪽 정렬이다. 바깥은 오른쪽 정렬이라 값의 왼쪽 끝이 한 줄로 서는데(dl.given dt),\n * 안쪽까지 그러면 단이 깊어질수록 이름이 값 쪽으로 밀려 붙는다.\n */\n dl.given .nested {\n margin: 2px 0 0;\n padding-left: 10px;\n border-left: 1px solid var(--ui-line, #d5dce0);\n }\n\n dl.given dl.nested {\n display: grid;\n grid-template-columns: minmax(60px, max-content) 1fr;\n gap: 4px var(--spacing-medium, 8px);\n }\n\n dl.given dl.nested dt {\n text-align: left;\n color: var(--ui-faint, #8a959c);\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n /*\n * 페이로드를 그대로 가져가는 자리.\n *\n * 화면은 값을 읽기 좋게 펴 놓지만, 이 값을 **다른 도구에 넣어 볼 일**이 있다 — 트윈 명령 하나가\n * 네 단짜리 오브젝트이고, 그것을 손으로 옮겨 적을 수는 없다. 화면이 편 글자가 아니라 원문 JSON 을\n * 준다. 붙여 넣는 쪽이 기대하는 것은 원문이다.\n *\n * 값 위에 얹지 않고 오른쪽 위 구석에 둔다 — 값이 한 줄일 때도 두 단 깊을 때도 자리가 같다.\n */\n dl.given dd:has(> .payload) {\n position: relative;\n }\n\n .copy {\n position: absolute;\n top: 0;\n right: 0;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 1px 5px;\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: 999px;\n background: var(--ui-panel, #ffffff);\n color: var(--ui-muted, #57676f);\n font-size: var(--ui-t-micro, 11px);\n line-height: 16px;\n cursor: pointer;\n }\n\n .copy md-icon {\n font-size: 13px;\n width: 13px;\n height: 13px;\n }\n\n .copy:hover {\n color: var(--ui-ink, #17242c);\n border-color: var(--ui-muted, #57676f);\n }\n\n .copy:focus-visible {\n outline: 2px solid var(--ui-accent, #00727e);\n outline-offset: 1px;\n }\n\n /* 복사한 직후 — 눌린 것이 보여야 한다. 소리도 움직임도 없는 조작이라 이 표시가 유일한 대답이다. */\n .copy[done] {\n border-color: var(--ui-good, #297d39);\n color: var(--ui-good, #297d39);\n }\n\n /*\n * 못 했을 때도 **같은 크기로** 말한다.\n *\n * 처음에는 실패를 title 로만 적었다. 그러면 누른 사람 눈에는 아무 일도 안 일어난 것과 같고,\n * 이유를 알려면 그 자리에 마우스를 올려 두어야 한다 — 손가락으로 쓰는 화면에는 그 길이 없다.\n * 클립보드는 브라우저가 막을 수 있는 자리라(보안 컨텍스트가 아니거나 권한이 없으면) 실제로 나는 일이다.\n */\n .copy[failed] {\n border-color: var(--ui-crit, #d14946);\n color: var(--ui-crit, #d14946);\n }\n\n /* 배열 — 번호로 몇 번째인지 말한다. 순서가 뜻을 갖는 값이 온다(주의 목록 · 권장 조치). */\n dl.given ol.nested {\n list-style: decimal;\n padding-left: 24px;\n }\n\n dl.given ol.nested li {\n margin: 2px 0;\n }\n\n dl.given ol.nested li::marker {\n color: var(--ui-faint, #8a959c);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n /*\n * 빈 화면은 가장 중요한 자리다. 없다고만 하지 않고 왜 비어 있는지 적는다.\n *\n * 두 가지가 있다. 화면 전체가 비는 것(건을 못 읽었다)은 세로로 늘려 가운데에 두고, 절 자리가\n * 비는 것(항목이 정의되지 않았다)은 **다른 절과 같은 카드**로 둔다. 절 하나가 없다고 해서\n * 화면 높이를 다 쓰면 그 아래의 타임라인이 화면 밖으로 밀린다.\n */\n section[empty] {\n display: flex;\n flex: none;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 28px 14px;\n text-align: center;\n color: var(--ui-muted, #57676f);\n }\n\n div[empty] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: var(--spacing-large, 16px);\n text-align: center;\n color: var(--ui-muted, #57676f);\n }\n\n [empty] .title {\n font-size: var(--ui-t-title, 15px);\n font-weight: 600;\n color: var(--ui-ink, #17242c);\n }\n\n [empty] .why {\n max-width: 40em;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-faint, #8a959c);\n word-break: keep-all;\n }\n`\n\n/**\n * 화면에 낼 항목을 고른다.\n *\n * hidden 을 지킨다. 전에는 이 선언을 보지 않았다 — 액티비티가 「이 항목은 보이지 않는다」고 적어\n * 두어도 화면에 나왔다. 편집기가 없는 타입(object 등)이라 조용히 빠졌을 뿐이고, 편집기가 있는\n * 타입이면 그대로 보였다. 값을 표로 그리기 시작하면서 실제로 드러났다 — 처분 승인 화면에\n * disposition 객체의 JSON 원문이 나왔다.\n */\nfunction specsOf(model: any[] | null | undefined, inouts: string[]) {\n return (model || [])\n .filter(item => !item.hidden)\n .filter(item => inouts.includes(item.inout))\n .map(item => ({\n ...item,\n name: item.tag,\n label: item.tKey || item.name,\n property: item.options\n }))\n}\n\n/** 받은 값 — 업무를 시작할 때 함께 온 것. */\nexport function inputSpecOf(model: any[] | null | undefined) {\n return specsOf(model, ['in', 'inout'])\n}\n\n/** 적어 낼 값 — 수행자가 채우는 것. */\nexport function outputSpecOf(model: any[] | null | undefined) {\n return specsOf(model, ['out', 'inout'])\n}\n\n/**\n * 값을 **표로** 보인다. 입력칸으로 그리지 않는다.\n *\n * 전에는 읽기만 하는 절도 ox-properties-dynamic-view 로 그렸다. 그러면 값마다 흰 입력 상자가\n * 생기고 사용자는 고쳐 쓸 수 있는 것으로 읽는다. 그런데 그 절에는 변경을 받는 자리가 없다 —\n * 결재 화면은 아예 output 을 mutation 에 싣지 않는다(approveActivityApproval 은 id 와 comment 만\n * 보낸다). 「적을 수 있게 보이는데 적히지 않는 칸」이라 가장 나쁜 쪽이다.\n */\nexport function renderGiven(specs: any[], values: any) {\n return html`\n <dl class=\"given\">\n ${specs.map(\n spec => html`\n <dt>${fieldLabel(spec)}</dt>\n <dd>${fieldValue(values?.[spec.name])}</dd>\n `\n )}\n </dl>\n `\n}\n\n/**\n * 항목 이름 — ox-property-editor 와 같은 규칙으로 찾는다(그쪽은 label. 접두사를 스스로 붙인다).\n *\n * 번역이 없으면 **키를 그대로 보이지 않는다.** 전에는 label.because 가 없어서 화면에\n * Label.Because 가 나왔다. 사용자에게 뜻이 없는 글자다.\n *\n * description 으로 물러나지도 않는다 — 액티비티의 description 은 개발자가 영어로 적어 둔 설명이다\n * (예: \"The disposition waiting for approval\"). 한국어 화면에 영어 문장이 나오는 것보다 항목\n * 이름이 낫다.\n */\nexport function fieldLabel(spec: any): string {\n const key = String(spec?.label ?? spec?.name ?? '')\n const full = key.indexOf('label.') >= 0 ? key : 'label.' + key\n\n return i18next.t(full, { defaultValue: spec?.name || key })\n}\n\n/**\n * 선언이 준 이름 — `tKey` 로 찾되, **없으면 키를 보이지 않는다.**\n *\n * 찾는 축(`searchKeys`)과 판단 자리(`decisionKeys`) 둘 다 이것을 쓴다. 두 선언이 같은 모양으로\n * 이름을 나른다 — `tKey` 가 있으면 그 키로, 없으면 `name` 으로.\n *\n * `fieldLabel` 과 규칙이 하나 다르다. 그쪽은 `label.` 을 스스로 붙이지만 여기는 안 붙인다 — 이 두\n * 선언의 `tKey` 는 선언한 모듈이 온전한 키로 적는다(plant 는 `field.lot`, twin 은 `twin.instance`).\n * 접두사를 붙이면 맞게 적은 키가 오히려 안 찾아진다.\n *\n * ── 왜 물러날 자리가 필요한가 (2026-09-16) ────────────────────────────────\n * i18next 는 없는 키를 **키 그대로** 돌려준다. 선언된 tKey 를 세어 보니 plant 는 서른 중 열아홉이,\n * twin 은 셋 다 다섯 언어 어디에도 없었다. 그래서 그 표가 「어느 로트」 자리에 `lotName` 을, 「누가\n * 냈나」 자리에 `twin.command-origin` 을 그리고 있다. 사용자에게 뜻이 없는 글자다.\n *\n * 없는 번역을 이 자리에서 지어낼 수는 없다. 대신 선언이 이미 준 `name` 으로 물러난다 — 개발자가 적은\n * 영어지만 점과 낙타등이 섞인 키보다는 읽힌다. 같은 규율이 `fieldLabel` 에 이미 있었고, 이 규칙이\n * 다섯 자리에 흩어져 있어서 한 자리만 고쳐서는 나머지 넷이 그대로 남았다.\n */\nexport function declaredLabel(item: any): string {\n const name = String(item?.name ?? '')\n\n return item?.tKey ? i18next.t(item.tKey, { defaultValue: name || item.tKey }) : name\n}\n\n/**\n * 얼마나 깊이 펼치나.\n *\n * 이 화면들이 받는 값은 액티비티가 정하므로 얼마나 깊을지 알 수 없다. 트윈의 작동 명령은 네 단이다\n * (twinNow → attentions[0] → params → 값). 끝없이 펼치면 어떤 페이로드에서 화면이 통째로 페이로드가\n * 되므로, 이 깊이를 넘으면 원문을 그대로 보인다 — 접는 것이 아니라 **다른 방식으로 보이는 것**이다.\n */\nconst MAX_DEPTH = 4\n\n/**\n * 값 한 칸.\n *\n * ── 오브젝트를 한 줄 JSON 으로 내지 않는다 (2026-09-07) ─────────────────────\n * `JSON.stringify` 한 줄이었다. 트윈의 작동 명령을 열면 그 한 줄이 700자가 넘어서, 「업무 내용」이\n * 읽을 수 없는 글자 덩어리 하나가 됐다. 값은 다 있는데 아무것도 읽히지 않는다.\n *\n * 화면은 이미 이름과 값을 두 칸으로 보이는 말을 갖고 있다(dl.given). 오브젝트도 그 말로 편다 —\n * 새 표현을 만들지 않고, 안쪽이든 바깥쪽이든 같은 모양으로 읽힌다.\n *\n * 값이 없으면 빈 칸으로 두지 않는다 — 빈 칸은 값이 없는 것인지 화면이 못 낸 것인지 모른다.\n */\nexport function fieldValue(value: any, depth = 0): unknown {\n if (value === null || value === undefined || value === '') {\n return html`<span class=\"none\">${i18next.t('text.no-value')}</span>`\n }\n\n if (typeof value === 'boolean') {\n return i18next.t(value ? 'label.yes' : 'label.no', { defaultValue: String(value) })\n }\n\n if (typeof value !== 'object') {\n return String(value)\n }\n\n /* 너무 깊으면 펼치기를 그만두고 원문을 보인다 — 없는 척하지 않는다. */\n if (depth >= MAX_DEPTH) {\n return html`<code>${JSON.stringify(value)}</code>`\n }\n\n const laid = layOut(value, depth)\n\n /*\n * 맨 바깥 값에만 복사를 붙인다.\n *\n * 안쪽 단마다 달면 네 단짜리 페이로드에 단추가 열 개 넘게 붙어, 정작 읽으라고 편 값이 단추에\n * 묻힌다. 사람이 가져가려는 것은 「이 항목」이다.\n */\n return depth === 0 ? html`<span class=\"payload\">${copyButton(value)}${laid}</span>` : laid\n}\n\n/**\n * 이 값을 원문 그대로 가져간다.\n *\n * 화면이 편 글자가 아니라 JSON 을 준다 — 붙여 넣는 쪽(질의 · 로그 검색 · 문의 글)이 기대하는 것은\n * 원문이다. 들여쓰기를 넣어 그대로 읽을 수 있게 한다.\n *\n * 눌러도 화면이 안 바뀌면 눌린 줄을 모른다. 그래서 잠깐 표시를 남긴다 — 이 조작에는 그것 말고\n * 대답이 없다.\n */\nfunction copyButton(value: any) {\n const onCopy = async (e: Event) => {\n const button = e.currentTarget as HTMLElement\n\n const mark = (attr: 'done' | 'failed') => {\n button.toggleAttribute(attr, true)\n setTimeout(() => button.toggleAttribute(attr, false), 1500)\n }\n\n try {\n await navigator.clipboard.writeText(JSON.stringify(value, null, 2))\n } catch {\n /*\n * 클립보드는 브라우저가 막을 수 있다(보안 컨텍스트가 아니거나 권한이 없으면). 안 된 것을 된\n * 것처럼 말하지 않고, 성공과 같은 크기로 보인다 — title 만 바꾸면 누른 사람 눈에는 아무 일도\n * 안 일어난 것과 같다.\n */\n button.setAttribute('title', String(i18next.t('text.copy-failed')))\n mark('failed')\n return\n }\n\n mark('done')\n }\n\n return html`\n <button class=\"copy\" type=\"button\" title=${String(i18next.t('text.copy-payload'))} @click=${onCopy}>\n <md-icon>content_copy</md-icon>${i18next.t('button.copy')}\n </button>\n `\n}\n\n/** 값을 이름·값 두 칸으로 편다. 복사 단추는 여기 끼지 않는다 — 편 결과만 만든다. */\nfunction layOut(value: any, depth: number): unknown {\n if (Array.isArray(value)) {\n /* 빈 배열은 「값 없음」이 아니다 — 「비어 있다」는 그 자체로 사실이다. */\n if (value.length === 0) {\n return html`<span class=\"none\">${i18next.t('text.empty-list')}</span>`\n }\n\n /*\n * 배열은 깊이를 먹지 않는다 — **같은 것이 여러 개**이지 새로운 단이 아니다.\n *\n * 배열도 한 단으로 세었더니 트윈 작동 명령의 마지막 줄이 잘렸다: twinNow → attentions →\n * attentions[0] → recommendedActions → { code } 에서 recommendedActions 의 항목이 다섯 번째가\n * 되어, 정작 가장 단순한 { code: 'advice.raise-priority' } 하나가 JSON 원문으로 남았다.\n * 사람이 세는 단은 넷이다.\n */\n return html`\n <ol class=\"nested\">\n ${value.map(item => html`<li>${fieldValue(item, depth)}</li>`)}\n </ol>\n `\n }\n\n const keys = Object.keys(value)\n\n if (keys.length === 0) {\n return html`<span class=\"none\">${i18next.t('text.empty-object')}</span>`\n }\n\n /*\n * 안쪽 이름은 번역하지 않는다. 액티비티가 정한 키라 번역이 있을 리 없고, 없는 번역을 찾으면\n * 「Label.MaterialDefinitionId」 같은 글자가 나온다 — 사용자에게 뜻이 없는 글자다.\n */\n return html`\n <dl class=\"given nested\">\n ${keys.map(\n key => html`\n <dt>${key}</dt>\n <dd>${fieldValue(value[key], depth + 1)}</dd>\n `\n )}\n </dl>\n `\n}\n\n/**\n * A person as a value - their name opens their 360 (ADR-0067 decision 1), and no one says so as\n * `fieldValue` would. `entity360Name` draws plain text until the person kind is registered.\n */\nexport function personValue(person?: { name?: string } | null): unknown {\n return person?.name ? entity360Name(person) : fieldValue(undefined)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"activity-fields.js","sourceRoot":"","sources":["../../client/components/activity-fields.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html } from 'lit'\n\nimport { i18next } from '@operato/i18n'\nimport { entity360Name } from '@operato/layout'\n\n/**\n * 업무 항목을 화면에 내는 방식 — **두 화면이 한 벌을 쓴다.**\n *\n * 업무 상세(activity-thread-page)와 결재 상세(activity-approval-page)는 거의 복사본이다. 항목을\n * 고르는 함수, 화면 종류에 따라 그리는 분기, 절을 그리는 부분이 두 파일에 같은 코드로 있었다.\n * 그래서 한쪽만 고치면 나머지가 그대로 남는다 — 실제로 그랬다(2026-09-06, 상세 화면의\n * Input · Output 영문 legend 를 고쳤는데 결재 화면에는 그대로 있었다).\n *\n * 화면마다 다른 것은 남긴다 — 상세는 결과를 적고, 결재는 읽기만 한다.\n */\n\n/** 절과 값 표의 모양. 카드 값은 SurfaceStyles 의 .ui-card 와 같다. */\nexport const ActivityFieldsStyles = css`\n section {\n margin: var(--spacing-large, 16px) var(--spacing-large, 16px) 0;\n padding: 14px;\n background: var(--ui-panel, #ffffff);\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: var(--ui-r, 10px);\n }\n\n section > h2 {\n margin: 0 0 10px;\n font-size: var(--ui-t-title, 15px);\n font-weight: 600;\n color: var(--ui-ink, #17242c);\n }\n\n /* 읽기만 하는 절 — 값을 바꿀 수 없다는 것이 보여야 한다. */\n section[readonly] > h2 {\n color: var(--ui-muted, #57676f);\n }\n\n /* 받은 값 — 이름과 값을 두 칸으로. 이름은 오른쪽 정렬이라 값의 왼쪽 끝이 한 줄로 선다. */\n dl.given {\n display: grid;\n grid-template-columns: minmax(80px, max-content) 1fr;\n gap: 7px var(--spacing-large, 16px);\n margin: 0;\n }\n\n dl.given dt {\n text-align: right;\n color: var(--ui-muted, #57676f);\n word-break: keep-all;\n }\n\n dl.given dd {\n margin: 0;\n color: var(--ui-ink, #17242c);\n word-break: break-word;\n }\n\n dl.given .none {\n color: var(--ui-faint, #8a959c);\n }\n\n dl.given code {\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n /*\n * 안쪽으로 펼친 오브젝트 · 배열.\n *\n * 바깥과 같은 두 칸 구조를 쓰되 **한 단 들여쓰고 왼쪽에 선을 긋는다.** 선이 없으면 세 단이 겹칠 때\n * 어느 이름이 어느 값에 딸린 것인지 눈으로 못 가른다.\n *\n * 안쪽 이름은 왼쪽 정렬이다. 바깥은 오른쪽 정렬이라 값의 왼쪽 끝이 한 줄로 서는데(dl.given dt),\n * 안쪽까지 그러면 단이 깊어질수록 이름이 값 쪽으로 밀려 붙는다.\n */\n dl.given .nested {\n margin: 2px 0 0;\n padding-left: 10px;\n border-left: 1px solid var(--ui-line, #d5dce0);\n }\n\n dl.given dl.nested {\n display: grid;\n grid-template-columns: minmax(60px, max-content) 1fr;\n gap: 4px var(--spacing-medium, 8px);\n }\n\n dl.given dl.nested dt {\n text-align: left;\n color: var(--ui-faint, #8a959c);\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n /*\n * 페이로드를 그대로 가져가는 자리.\n *\n * 화면은 값을 읽기 좋게 펴 놓지만, 이 값을 **다른 도구에 넣어 볼 일**이 있다 — 트윈 명령 하나가\n * 네 단짜리 오브젝트이고, 그것을 손으로 옮겨 적을 수는 없다. 화면이 편 글자가 아니라 원문 JSON 을\n * 준다. 붙여 넣는 쪽이 기대하는 것은 원문이다.\n *\n * 값 위에 얹지 않고 오른쪽 위 구석에 둔다 — 값이 한 줄일 때도 두 단 깊을 때도 자리가 같다.\n * 자리의 기준은 값 자신이다(.payload). 기준을 dl.given dd 에만 두었더니 결재 내용처럼 dd 밖에서\n * 그린 값은 단추가 팝업 오른쪽 위 모서리로 빠져 닫기 단추와 겹쳤다(2026-11-10).\n */\n .payload {\n position: relative;\n display: block;\n }\n\n .copy {\n position: absolute;\n top: 0;\n right: 0;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 1px 5px;\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: 999px;\n background: var(--ui-panel, #ffffff);\n color: var(--ui-muted, #57676f);\n font-size: var(--ui-t-micro, 11px);\n line-height: 16px;\n cursor: pointer;\n }\n\n .copy md-icon {\n font-size: 13px;\n width: 13px;\n height: 13px;\n }\n\n .copy:hover {\n color: var(--ui-ink, #17242c);\n border-color: var(--ui-muted, #57676f);\n }\n\n .copy:focus-visible {\n outline: 2px solid var(--ui-accent, #00727e);\n outline-offset: 1px;\n }\n\n /* 복사한 직후 — 눌린 것이 보여야 한다. 소리도 움직임도 없는 조작이라 이 표시가 유일한 대답이다. */\n .copy[done] {\n border-color: var(--ui-good, #297d39);\n color: var(--ui-good, #297d39);\n }\n\n /*\n * 못 했을 때도 **같은 크기로** 말한다.\n *\n * 처음에는 실패를 title 로만 적었다. 그러면 누른 사람 눈에는 아무 일도 안 일어난 것과 같고,\n * 이유를 알려면 그 자리에 마우스를 올려 두어야 한다 — 손가락으로 쓰는 화면에는 그 길이 없다.\n * 클립보드는 브라우저가 막을 수 있는 자리라(보안 컨텍스트가 아니거나 권한이 없으면) 실제로 나는 일이다.\n */\n .copy[failed] {\n border-color: var(--ui-crit, #d14946);\n color: var(--ui-crit, #d14946);\n }\n\n /* 배열 — 번호로 몇 번째인지 말한다. 순서가 뜻을 갖는 값이 온다(주의 목록 · 권장 조치). */\n dl.given ol.nested {\n list-style: decimal;\n padding-left: 24px;\n }\n\n dl.given ol.nested li {\n margin: 2px 0;\n }\n\n dl.given ol.nested li::marker {\n color: var(--ui-faint, #8a959c);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n /*\n * 빈 화면은 가장 중요한 자리다. 없다고만 하지 않고 왜 비어 있는지 적는다.\n *\n * 두 가지가 있다. 화면 전체가 비는 것(건을 못 읽었다)은 세로로 늘려 가운데에 두고, 절 자리가\n * 비는 것(항목이 정의되지 않았다)은 **다른 절과 같은 카드**로 둔다. 절 하나가 없다고 해서\n * 화면 높이를 다 쓰면 그 아래의 타임라인이 화면 밖으로 밀린다.\n */\n section[empty] {\n display: flex;\n flex: none;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 28px 14px;\n text-align: center;\n color: var(--ui-muted, #57676f);\n }\n\n div[empty] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: var(--spacing-large, 16px);\n text-align: center;\n color: var(--ui-muted, #57676f);\n }\n\n [empty] .title {\n font-size: var(--ui-t-title, 15px);\n font-weight: 600;\n color: var(--ui-ink, #17242c);\n }\n\n [empty] .why {\n max-width: 40em;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-faint, #8a959c);\n word-break: keep-all;\n }\n`\n\n/**\n * 화면에 낼 항목을 고른다.\n *\n * hidden 을 지킨다. 전에는 이 선언을 보지 않았다 — 액티비티가 「이 항목은 보이지 않는다」고 적어\n * 두어도 화면에 나왔다. 편집기가 없는 타입(object 등)이라 조용히 빠졌을 뿐이고, 편집기가 있는\n * 타입이면 그대로 보였다. 값을 표로 그리기 시작하면서 실제로 드러났다 — 처분 승인 화면에\n * disposition 객체의 JSON 원문이 나왔다.\n */\nfunction specsOf(model: any[] | null | undefined, inouts: string[]) {\n return (model || [])\n .filter(item => !item.hidden)\n .filter(item => inouts.includes(item.inout))\n .map(item => ({\n ...item,\n name: item.tag,\n label: item.tKey || item.name,\n property: item.options\n }))\n}\n\n/** 받은 값 — 업무를 시작할 때 함께 온 것. */\nexport function inputSpecOf(model: any[] | null | undefined) {\n return specsOf(model, ['in', 'inout'])\n}\n\n/** 적어 낼 값 — 수행자가 채우는 것. */\nexport function outputSpecOf(model: any[] | null | undefined) {\n return specsOf(model, ['out', 'inout'])\n}\n\n/**\n * 값을 **표로** 보인다. 입력칸으로 그리지 않는다.\n *\n * 전에는 읽기만 하는 절도 ox-properties-dynamic-view 로 그렸다. 그러면 값마다 흰 입력 상자가\n * 생기고 사용자는 고쳐 쓸 수 있는 것으로 읽는다. 그런데 그 절에는 변경을 받는 자리가 없다 —\n * 결재 화면은 아예 output 을 mutation 에 싣지 않는다(approveActivityApproval 은 id 와 comment 만\n * 보낸다). 「적을 수 있게 보이는데 적히지 않는 칸」이라 가장 나쁜 쪽이다.\n */\nexport function renderGiven(specs: any[], values: any) {\n return html`\n <dl class=\"given\">\n ${specs.map(\n spec => html`\n <dt>${fieldLabel(spec)}</dt>\n <dd>${fieldValue(values?.[spec.name])}</dd>\n `\n )}\n </dl>\n `\n}\n\n/**\n * 항목 이름 — ox-property-editor 와 같은 규칙으로 찾는다(그쪽은 label. 접두사를 스스로 붙인다).\n *\n * 번역이 없으면 **키를 그대로 보이지 않는다.** 전에는 label.because 가 없어서 화면에\n * Label.Because 가 나왔다. 사용자에게 뜻이 없는 글자다.\n *\n * description 으로 물러나지도 않는다 — 액티비티의 description 은 개발자가 영어로 적어 둔 설명이다\n * (예: \"The disposition waiting for approval\"). 한국어 화면에 영어 문장이 나오는 것보다 항목\n * 이름이 낫다.\n */\nexport function fieldLabel(spec: any): string {\n const key = String(spec?.label ?? spec?.name ?? '')\n const full = key.indexOf('label.') >= 0 ? key : 'label.' + key\n\n return i18next.t(full, { defaultValue: spec?.name || key })\n}\n\n/**\n * 선언이 준 이름 — `tKey` 로 찾되, **없으면 키를 보이지 않는다.**\n *\n * 찾는 축(`searchKeys`)과 판단 자리(`decisionKeys`) 둘 다 이것을 쓴다. 두 선언이 같은 모양으로\n * 이름을 나른다 — `tKey` 가 있으면 그 키로, 없으면 `name` 으로.\n *\n * `fieldLabel` 과 규칙이 하나 다르다. 그쪽은 `label.` 을 스스로 붙이지만 여기는 안 붙인다 — 이 두\n * 선언의 `tKey` 는 선언한 모듈이 온전한 키로 적는다(plant 는 `field.lot`, twin 은 `twin.instance`).\n * 접두사를 붙이면 맞게 적은 키가 오히려 안 찾아진다.\n *\n * ── 왜 물러날 자리가 필요한가 (2026-09-16) ────────────────────────────────\n * i18next 는 없는 키를 **키 그대로** 돌려준다. 선언된 tKey 를 세어 보니 plant 는 서른 중 열아홉이,\n * twin 은 셋 다 다섯 언어 어디에도 없었다. 그래서 그 표가 「어느 로트」 자리에 `lotName` 을, 「누가\n * 냈나」 자리에 `twin.command-origin` 을 그리고 있다. 사용자에게 뜻이 없는 글자다.\n *\n * 없는 번역을 이 자리에서 지어낼 수는 없다. 대신 선언이 이미 준 `name` 으로 물러난다 — 개발자가 적은\n * 영어지만 점과 낙타등이 섞인 키보다는 읽힌다. 같은 규율이 `fieldLabel` 에 이미 있었고, 이 규칙이\n * 다섯 자리에 흩어져 있어서 한 자리만 고쳐서는 나머지 넷이 그대로 남았다.\n */\nexport function declaredLabel(item: any): string {\n const name = String(item?.name ?? '')\n\n return item?.tKey ? i18next.t(item.tKey, { defaultValue: name || item.tKey }) : name\n}\n\n/**\n * 얼마나 깊이 펼치나.\n *\n * 이 화면들이 받는 값은 액티비티가 정하므로 얼마나 깊을지 알 수 없다. 트윈의 작동 명령은 네 단이다\n * (twinNow → attentions[0] → params → 값). 끝없이 펼치면 어떤 페이로드에서 화면이 통째로 페이로드가\n * 되므로, 이 깊이를 넘으면 원문을 그대로 보인다 — 접는 것이 아니라 **다른 방식으로 보이는 것**이다.\n */\nconst MAX_DEPTH = 4\n\n/**\n * 값 한 칸.\n *\n * ── 오브젝트를 한 줄 JSON 으로 내지 않는다 (2026-09-07) ─────────────────────\n * `JSON.stringify` 한 줄이었다. 트윈의 작동 명령을 열면 그 한 줄이 700자가 넘어서, 「업무 내용」이\n * 읽을 수 없는 글자 덩어리 하나가 됐다. 값은 다 있는데 아무것도 읽히지 않는다.\n *\n * 화면은 이미 이름과 값을 두 칸으로 보이는 말을 갖고 있다(dl.given). 오브젝트도 그 말로 편다 —\n * 새 표현을 만들지 않고, 안쪽이든 바깥쪽이든 같은 모양으로 읽힌다.\n *\n * 값이 없으면 빈 칸으로 두지 않는다 — 빈 칸은 값이 없는 것인지 화면이 못 낸 것인지 모른다.\n */\nexport function fieldValue(value: any, depth = 0): unknown {\n return laidValue(value, depth, true)\n}\n\n/**\n * The same laying-out, without the copy button - for values a person reads rather than takes\n * elsewhere: a decision's reason, a sentence list. The button is for payloads (2026-11-10).\n */\nexport function readValue(value: any): unknown {\n return laidValue(value, 0, false)\n}\n\nfunction laidValue(value: any, depth: number, copyable: boolean): unknown {\n if (value === null || value === undefined || value === '') {\n return html`<span class=\"none\">${i18next.t('text.no-value')}</span>`\n }\n\n if (typeof value === 'boolean') {\n return i18next.t(value ? 'label.yes' : 'label.no', { defaultValue: String(value) })\n }\n\n if (typeof value !== 'object') {\n return String(value)\n }\n\n /* 너무 깊으면 펼치기를 그만두고 원문을 보인다 — 없는 척하지 않는다. */\n if (depth >= MAX_DEPTH) {\n return html`<code>${JSON.stringify(value)}</code>`\n }\n\n const laid = layOut(value, depth, copyable)\n\n /*\n * 맨 바깥 값에만 복사를 붙인다.\n *\n * 안쪽 단마다 달면 네 단짜리 페이로드에 단추가 열 개 넘게 붙어, 정작 읽으라고 편 값이 단추에\n * 묻힌다. 사람이 가져가려는 것은 「이 항목」이다.\n */\n return depth === 0 && copyable ? html`<span class=\"payload\">${copyButton(value)}${laid}</span>` : laid\n}\n\n/**\n * 이 값을 원문 그대로 가져간다.\n *\n * 화면이 편 글자가 아니라 JSON 을 준다 — 붙여 넣는 쪽(질의 · 로그 검색 · 문의 글)이 기대하는 것은\n * 원문이다. 들여쓰기를 넣어 그대로 읽을 수 있게 한다.\n *\n * 눌러도 화면이 안 바뀌면 눌린 줄을 모른다. 그래서 잠깐 표시를 남긴다 — 이 조작에는 그것 말고\n * 대답이 없다.\n */\nfunction copyButton(value: any) {\n const onCopy = async (e: Event) => {\n const button = e.currentTarget as HTMLElement\n\n const mark = (attr: 'done' | 'failed') => {\n button.toggleAttribute(attr, true)\n setTimeout(() => button.toggleAttribute(attr, false), 1500)\n }\n\n try {\n await navigator.clipboard.writeText(JSON.stringify(value, null, 2))\n } catch {\n /*\n * 클립보드는 브라우저가 막을 수 있다(보안 컨텍스트가 아니거나 권한이 없으면). 안 된 것을 된\n * 것처럼 말하지 않고, 성공과 같은 크기로 보인다 — title 만 바꾸면 누른 사람 눈에는 아무 일도\n * 안 일어난 것과 같다.\n */\n button.setAttribute('title', String(i18next.t('text.copy-failed')))\n mark('failed')\n return\n }\n\n mark('done')\n }\n\n return html`\n <button class=\"copy\" type=\"button\" title=${String(i18next.t('text.copy-payload'))} @click=${onCopy}>\n <md-icon>content_copy</md-icon>${i18next.t('button.copy')}\n </button>\n `\n}\n\n/** 값을 이름·값 두 칸으로 편다. 복사 단추는 여기 끼지 않는다 — 편 결과만 만든다. */\nfunction layOut(value: any, depth: number, copyable: boolean): unknown {\n if (Array.isArray(value)) {\n /* 빈 배열은 「값 없음」이 아니다 — 「비어 있다」는 그 자체로 사실이다. */\n if (value.length === 0) {\n return html`<span class=\"none\">${i18next.t('text.empty-list')}</span>`\n }\n\n /*\n * 배열은 깊이를 먹지 않는다 — **같은 것이 여러 개**이지 새로운 단이 아니다.\n *\n * 배열도 한 단으로 세었더니 트윈 작동 명령의 마지막 줄이 잘렸다: twinNow → attentions →\n * attentions[0] → recommendedActions → { code } 에서 recommendedActions 의 항목이 다섯 번째가\n * 되어, 정작 가장 단순한 { code: 'advice.raise-priority' } 하나가 JSON 원문으로 남았다.\n * 사람이 세는 단은 넷이다.\n */\n return html`\n <ol class=\"nested\">\n ${value.map(item => html`<li>${laidValue(item, depth, copyable)}</li>`)}\n </ol>\n `\n }\n\n const keys = Object.keys(value)\n\n if (keys.length === 0) {\n return html`<span class=\"none\">${i18next.t('text.empty-object')}</span>`\n }\n\n /*\n * 안쪽 이름은 번역하지 않는다. 액티비티가 정한 키라 번역이 있을 리 없고, 없는 번역을 찾으면\n * 「Label.MaterialDefinitionId」 같은 글자가 나온다 — 사용자에게 뜻이 없는 글자다.\n */\n return html`\n <dl class=\"given nested\">\n ${keys.map(\n key => html`\n <dt>${key}</dt>\n <dd>${laidValue(value[key], depth + 1, copyable)}</dd>\n `\n )}\n </dl>\n `\n}\n\n/**\n * A person as a value - their name opens their 360 (ADR-0067 decision 1), and no one says so as\n * `fieldValue` would. `entity360Name` draws plain text until the person kind is registered.\n */\nexport function personValue(person?: { name?: string } | null): unknown {\n return person?.name ? entity360Name(person) : fieldValue(undefined)\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
|
}
|
|
@@ -2,8 +2,8 @@ import '@material/web/icon/icon.js';
|
|
|
2
2
|
import { css, html, nothing } from 'lit';
|
|
3
3
|
import { i18next } from '@operato/i18n';
|
|
4
4
|
import { entity360KindOf, entity360Name } from '@operato/layout';
|
|
5
|
-
import { declaredLabel,
|
|
6
|
-
import { COMPENSABLE, decisionDrawable, decisionSlots, reversibilityIsMalformed, reversibilityOf, slotOfRole, slotsOfRole } from './decision-slots.js';
|
|
5
|
+
import { declaredLabel, readValue } from './activity-fields.js';
|
|
6
|
+
import { attachedFiles, COMPENSABLE, decisionDrawable, decisionSlots, reversibilityIsMalformed, reversibilityOf, slotOfRole, slotsOfRole } from './decision-slots.js';
|
|
7
7
|
/**
|
|
8
8
|
* The decision an activity declared, drawn the one way every screen draws it.
|
|
9
9
|
*
|
|
@@ -163,7 +163,7 @@ function renderReversibility(slot) {
|
|
|
163
163
|
* One slot's value as something to read.
|
|
164
164
|
*
|
|
165
165
|
* A scalar prints with its unit when one was declared. Anything shaped - an object, a list - goes
|
|
166
|
-
* through
|
|
166
|
+
* through readValue, the same laying-out (without the copy button - a decision is read) the model sections use, so a payload reads the same way
|
|
167
167
|
* wherever it appears and a four-level object does not become one long line.
|
|
168
168
|
*
|
|
169
169
|
* ── The sentence is built here, not frozen when the request was raised ──
|
|
@@ -182,6 +182,13 @@ function slotText(slot) {
|
|
|
182
182
|
if (said) {
|
|
183
183
|
return said;
|
|
184
184
|
}
|
|
185
|
+
/* The paper a decision is judged on - an attachment or a list of them - is drawn as the files to open, each in its own tab. */
|
|
186
|
+
const files = attachedFiles(slot.value);
|
|
187
|
+
if (files) {
|
|
188
|
+
return html `<ul class="files">
|
|
189
|
+
${files.map(file => html `<li><a href=${file.href} target="_blank" rel="noopener">${file.name}</a></li>`)}
|
|
190
|
+
</ul>`;
|
|
191
|
+
}
|
|
185
192
|
/*
|
|
186
193
|
* A value that names what it is - `{ __typename, id, name }` - is drawn as its name, a link to its
|
|
187
194
|
* 360 (ADR-0067 decision 1: an entity's name opens its 360). The counterparty an expenditure pays
|
|
@@ -194,7 +201,7 @@ function slotText(slot) {
|
|
|
194
201
|
if (scalar && slot.unit) {
|
|
195
202
|
return `${slot.value} ${slot.unit}`;
|
|
196
203
|
}
|
|
197
|
-
return scalar ? String(slot.value) :
|
|
204
|
+
return scalar ? String(slot.value) : readValue(slot.value);
|
|
198
205
|
}
|
|
199
206
|
/** The sentence this slot declares, when it declares one and the locale has it. */
|
|
200
207
|
function sentenceOf(slot) {
|
|
@@ -305,6 +312,20 @@ export const DecisionContentStyles = css `
|
|
|
305
312
|
border-left: 2px solid var(--ui-line, #d5dce0);
|
|
306
313
|
}
|
|
307
314
|
|
|
315
|
+
section[decision] ul.files {
|
|
316
|
+
margin: 0;
|
|
317
|
+
padding-left: 1.2em;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* A file reads as a link the way a name that opens its 360 does (entity360Name). */
|
|
321
|
+
section[decision] ul.files a {
|
|
322
|
+
color: inherit;
|
|
323
|
+
text-decoration: underline;
|
|
324
|
+
text-decoration-thickness: 1px;
|
|
325
|
+
text-underline-offset: 3px;
|
|
326
|
+
text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
|
|
327
|
+
}
|
|
328
|
+
|
|
308
329
|
section[decision] .evidence > .label {
|
|
309
330
|
margin-bottom: 2px;
|
|
310
331
|
font-size: var(--ui-t-small, 11.5px);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-content.js","sourceRoot":"","sources":["../../client/components/decision-content.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, nothing, TemplateResult } from 'lit'\nimport { i18next } from '@operato/i18n'\nimport { entity360KindOf, entity360Name } from '@operato/layout'\n\nimport { declaredLabel, fieldValue } from './activity-fields.js'\nimport {\n COMPENSABLE,\n DecisionSlot,\n decisionDrawable,\n decisionSlots,\n reversibilityIsMalformed,\n reversibilityOf,\n slotOfRole,\n slotsOfRole\n} from './decision-slots.js'\n\n/**\n * The decision an activity declared, drawn the one way every screen draws it.\n *\n * The approval screen drew it and the task screen did not. An activity whose model is all hidden -\n * the expenditure request carries its payee and lines only for its decisionKeys - then showed the\n * approver what was asked and showed the person who asked an empty box. Both read the same\n * declaration and the same input, so one drawing serves both.\n */\n\n/**\n * The fields a page asks for to draw the decision - every field renderDecision reads.\n *\n * `name` is the label when the translation `tKey` names is missing (decisionLabel), and `valueTKey` with `params`\n * builds the value sentence at reading time (slotText). Leaving them out drew the sentence frozen\n * at issue, in the language of whoever raised it, with nothing to say a field was missing.\n */\nexport const DecisionKeysFields = `decisionKeys {\n role\n inputPath\n name\n tKey\n valueTKey\n params\n unit\n}`\n\n/**\n * The decision, or `nothing` when the activity declared none or none of it is in this input.\n *\n * Returning nothing is how a page knows to fall back to the model. An activity with no\n * decisionKeys draws exactly what it drew before.\n *\n * A slot whose path led nowhere is skipped rather than drawn empty. Older instances of an activity\n * predate a field the declaration names and an empty bar on the screen would read as \"there is no\n * reason\" when the truth is that this record has no such value. What cannot be shown is left out;\n * what is shown is only what the payload actually carries.\n *\n * `reason` is the exception. Missing and blank mean the same thing to whoever is reading - nobody\n * wrote one - so both draw the grey line rather than disappearing. A reason that silently vanishes\n * would let an approver assume it was never asked for.\n */\nexport function renderDecision(decisionKeys: any, input: any): TemplateResult | typeof nothing {\n const slots = decisionSlots(decisionKeys, input)\n\n if (!decisionDrawable(slots)) {\n return nothing\n }\n\n const headline = slotOfRole(slots, 'headline')\n const subject = slotOfRole(slots, 'subject')\n const reason = slotOfRole(slots, 'reason')\n const consequences = slotsOfRole(slots, 'consequence').filter(slot => slot.found)\n const evidence = slotsOfRole(slots, 'evidence').filter(slot => slot.found)\n const currentState = slotOfRole(slots, 'currentState')\n const reversibility = slotOfRole(slots, 'reversibility')\n\n return html`\n <section decision>\n ${headline?.found ? html`<div class=\"headline\">${slotText(headline)}</div>` : ''}\n ${subject?.found ? html`<div class=\"subject\">${slotText(subject)}</div>` : ''}\n ${\n reason\n ? html`\n <div class=\"block\">\n <div class=\"label\">${i18next.t('field.reason')}</div>\n <div class=\"said ${hasWords(reason) ? '' : 'none'}\">\n ${hasWords(reason) ? slotText(reason) : i18next.t('text.no-reason-given')}\n </div>\n </div>\n `\n : ''\n }\n ${renderReversibility(reversibility)}\n ${\n evidence.length\n ? html`\n <div class=\"block\">\n <div class=\"label\">${i18next.t('label.what-it-was-judged-from')}</div>\n ${evidence.map(\n slot => html`\n <div class=\"evidence\">\n ${decisionLabel(slot) ? html`<div class=\"label\">${decisionLabel(slot)}</div>` : ''}\n <div>${slotText(slot)}</div>\n </div>\n `\n )}\n </div>\n `\n : ''\n }\n ${\n currentState?.found\n ? html`\n <div class=\"current-state\">\n <div class=\"label\">${decisionLabel(currentState) || i18next.t('label.current-state')}</div>\n <div>${slotText(currentState)}</div>\n </div>\n `\n : ''\n }\n ${\n consequences.length\n ? html`\n <div class=\"block\">\n <div class=\"label\">${i18next.t('label.what-changes')}</div>\n ${consequences.map(\n slot => html`\n <div class=\"consequence\">\n ${decisionLabel(slot) ? html`<div class=\"label\">${decisionLabel(slot)}</div>` : ''}\n <div>${slotText(slot)}</div>\n </div>\n `\n )}\n </div>\n `\n : ''\n }\n </section>\n `\n}\n\n/**\n * A slot's own label - only where the declaration names one with `tKey`.\n *\n * A decision key's `name` is the developer's note (\"Never shown on a screen\", ActivityDecisionKeyItem),\n * and without `tKey` the screen shows no label. declaredLabel falls back to `name` whenever `tKey` is\n * absent - right for a search key, whose name is its column, and wrong here: asking for `name` put\n * \"what approving does\" under 「승인하면 달라지는 것」 on a Korean screen (2026-10-16). The fallback\n * stays for a declared key whose translation is missing.\n */\nfunction decisionLabel(slot: DecisionSlot): string {\n return slot.tKey ? declaredLabel(slot) : ''\n}\n\n/**\n * Whether approving can be undone, drawn as the frame around the decision.\n *\n * Only an explicit true or false draws. Anything else - the path led nowhere, or the value is not\n * a boolean - draws nothing, because saying \"this can be undone\" on a screen that does not know is\n * the one mistake this line must never make.\n *\n * This is not rejectionIsFinal. That one says whether **rejecting** ends the case for good; this\n * says whether **approving** can be taken back. The twin actuation carries both, which is exactly\n * why they must not share a sentence.\n */\nfunction renderReversibility(slot?: DecisionSlot) {\n const said = reversibilityOf(slot)\n\n if (!said) {\n if (reversibilityIsMalformed(slot)) {\n /*\n * Declared, and the value is there, but not the shape this role takes - a bare sentence\n * where { reversible, caveat } belongs. Nothing is drawn, because this side cannot tell\n * which way it goes; the log is so a developer does not have to find it by hand, which is\n * how it was found the first time.\n */\n console.error(`[worklist] reversibility at \"${slot?.inputPath}\" is not { reversible, caveat } — nothing drawn`)\n }\n\n return ''\n }\n\n /*\n * The module's own sentence when it wrote one. 「버린 자재는 돌아오지 않습니다」 says why, which\n * the plain wording cannot; the boolean beside it is what decides how heavily this is drawn.\n */\n return html`\n <div class=\"reversibility\" ?irreversible=${!said.reversible}>\n <md-icon>${said.kind === COMPENSABLE ? 'history' : said.reversible ? 'undo' : 'lock'}</md-icon>\n <span\n >${\n said.caveat || (said.kind === COMPENSABLE\n ? i18next.t('text.approval-is-compensable', { defaultValue: 'The original decision remains. A subsequent corrective action is required.' })\n : i18next.t(said.reversible ? 'text.approval-is-reversible' : 'text.approval-is-irreversible'))\n }</span\n >\n </div>\n `\n}\n\n/**\n * One slot's value as something to read.\n *\n * A scalar prints with its unit when one was declared. Anything shaped - an object, a list - goes\n * through fieldValue, the same laying-out the model sections use, so a payload reads the same way\n * wherever it appears and a four-level object does not become one long line.\n *\n * ── The sentence is built here, not frozen when the request was raised ──\n * Eighteen doors write a Korean sentence at issue and the issue snapshots it, so it stands in the\n * language of whoever raised it and an approver reading another language never catches up. Where\n * a door declares `valueTKey`, the sentence is made at reading time instead, out of the values\n * the request did freeze.\n *\n * **A door that has not moved yet draws exactly what it drew before.** That is what lets plant\n * move them one at a time instead of eighteen at once, and it covers a door that moved into a\n * locale nobody has written yet: an empty `defaultValue` means a missing key falls through to the\n * frozen sentence rather than printing `approval.…` on the card.\n */\nfunction slotText(slot: DecisionSlot): unknown {\n const said = sentenceOf(slot)\n\n if (said) {\n return said\n }\n\n /*\n * A value that names what it is - `{ __typename, id, name }` - is drawn as its name, a link to its\n * 360 (ADR-0067 decision 1: an entity's name opens its 360). The counterparty an expenditure pays\n * opens the counterparty 360; a person opens the person 360. A kind nobody registered stays text.\n */\n if (slot.value && typeof slot.value === 'object' && !Array.isArray(slot.value) && entity360KindOf(slot.value)) {\n return entity360Name(slot.value)\n }\n\n const scalar = slot.value !== null && slot.value !== undefined && typeof slot.value !== 'object'\n\n if (scalar && slot.unit) {\n return `${slot.value} ${slot.unit}`\n }\n\n return scalar ? String(slot.value) : fieldValue(slot.value)\n}\n\n/** The sentence this slot declares, when it declares one and the locale has it. */\nfunction sentenceOf(slot: DecisionSlot): string {\n if (!slot.valueTKey) {\n return ''\n }\n\n return String(i18next.t(slot.valueTKey, { ...(slot.params || {}), defaultValue: '' }) || '')\n}\n\n/** Did a person actually write something here. Whitespace is not words. */\nfunction hasWords(slot: DecisionSlot): boolean {\n return typeof slot.value === 'string' ? slot.value.trim().length > 0 : slot.found && slot.value != null\n}\n\n/*\n * The decision, drawn from the activity's declaration.\n *\n * The order is the declaration's and the weight descends with it: what is being asked reads\n * first at title size, then what it is about, then the words a person wrote, then what changes\n * on approval, and last why it came up at all.\n */\nexport const DecisionContentStyles = css`\n section[decision] .headline {\n font-size: var(--ui-t-headline, 19px);\n font-weight: 600;\n line-height: 1.35;\n color: var(--ui-ink, #17242c);\n word-break: keep-all;\n }\n\n section[decision] .subject {\n margin-top: 3px;\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-muted, #57676f);\n }\n\n section[decision] .block {\n margin-top: 14px;\n }\n\n section[decision] .block > .label {\n margin-bottom: 4px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n }\n\n /* What a person wrote is set apart, because it is the only part nobody computed. */\n section[decision] .said {\n padding: 9px 11px;\n border-left: 3px solid var(--ui-line, #d5dce0);\n background: var(--ui-line-soft, #f2f5f7);\n color: var(--ui-ink, #17242c);\n word-break: break-word;\n }\n\n section[decision] .none {\n color: var(--ui-faint, #8a959c);\n }\n\n section[decision] .consequence {\n margin-top: 8px;\n padding: 9px 11px;\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: var(--ui-r-small, 6px);\n }\n\n section[decision] .consequence > .label {\n margin-bottom: 3px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n }\n\n /*\n * Whether approving can be undone reads as the frame around the decision, not as one of its\n * consequences. Among them it carries the same weight as \"four orders get pushed back\", and\n * on an approval that moves equipment it is the heaviest line on the screen.\n */\n section[decision] .reversibility {\n display: flex;\n align-items: center;\n gap: 6px;\n\n margin-top: 12px;\n padding: 8px 11px;\n border-radius: var(--ui-r-small, 6px);\n\n background: var(--ui-line-soft, #f2f5f7);\n color: var(--ui-muted, #57676f);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n section[decision] .reversibility[irreversible] {\n background: var(--md-sys-color-error-container, #ffdad6);\n color: var(--md-sys-color-on-error-container, #410002);\n font-weight: 600;\n }\n\n section[decision] .reversibility md-icon {\n font-size: 1.15em;\n }\n\n /*\n * What the decision was made from. Set flatter than the consequences - it is the record the\n * approver checks, not something the approval brings about.\n */\n section[decision] .evidence {\n margin-top: 8px;\n padding: 7px 0 7px 11px;\n border-left: 2px solid var(--ui-line, #d5dce0);\n }\n\n section[decision] .evidence > .label {\n margin-bottom: 2px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n }\n\n /*\n * What is true right now sits apart from what the approval would change. Sharing the box the\n * consequences use would say the approval causes these readings.\n */\n section[decision] .current-state {\n margin-top: 14px;\n padding: 9px 11px;\n border: 1px dashed var(--ui-line, #d5dce0);\n border-radius: var(--ui-r-small, 6px);\n }\n\n section[decision] .current-state > .label {\n margin-bottom: 3px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n }\n`\n"]}
|
|
1
|
+
{"version":3,"file":"decision-content.js","sourceRoot":"","sources":["../../client/components/decision-content.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, nothing, TemplateResult } from 'lit'\nimport { i18next } from '@operato/i18n'\nimport { entity360KindOf, entity360Name } from '@operato/layout'\n\nimport { declaredLabel, readValue } from './activity-fields.js'\nimport {\n attachedFiles,\n COMPENSABLE,\n DecisionSlot,\n decisionDrawable,\n decisionSlots,\n reversibilityIsMalformed,\n reversibilityOf,\n slotOfRole,\n slotsOfRole\n} from './decision-slots.js'\n\n/**\n * The decision an activity declared, drawn the one way every screen draws it.\n *\n * The approval screen drew it and the task screen did not. An activity whose model is all hidden -\n * the expenditure request carries its payee and lines only for its decisionKeys - then showed the\n * approver what was asked and showed the person who asked an empty box. Both read the same\n * declaration and the same input, so one drawing serves both.\n */\n\n/**\n * The fields a page asks for to draw the decision - every field renderDecision reads.\n *\n * `name` is the label when the translation `tKey` names is missing (decisionLabel), and `valueTKey` with `params`\n * builds the value sentence at reading time (slotText). Leaving them out drew the sentence frozen\n * at issue, in the language of whoever raised it, with nothing to say a field was missing.\n */\nexport const DecisionKeysFields = `decisionKeys {\n role\n inputPath\n name\n tKey\n valueTKey\n params\n unit\n}`\n\n/**\n * The decision, or `nothing` when the activity declared none or none of it is in this input.\n *\n * Returning nothing is how a page knows to fall back to the model. An activity with no\n * decisionKeys draws exactly what it drew before.\n *\n * A slot whose path led nowhere is skipped rather than drawn empty. Older instances of an activity\n * predate a field the declaration names and an empty bar on the screen would read as \"there is no\n * reason\" when the truth is that this record has no such value. What cannot be shown is left out;\n * what is shown is only what the payload actually carries.\n *\n * `reason` is the exception. Missing and blank mean the same thing to whoever is reading - nobody\n * wrote one - so both draw the grey line rather than disappearing. A reason that silently vanishes\n * would let an approver assume it was never asked for.\n */\nexport function renderDecision(decisionKeys: any, input: any): TemplateResult | typeof nothing {\n const slots = decisionSlots(decisionKeys, input)\n\n if (!decisionDrawable(slots)) {\n return nothing\n }\n\n const headline = slotOfRole(slots, 'headline')\n const subject = slotOfRole(slots, 'subject')\n const reason = slotOfRole(slots, 'reason')\n const consequences = slotsOfRole(slots, 'consequence').filter(slot => slot.found)\n const evidence = slotsOfRole(slots, 'evidence').filter(slot => slot.found)\n const currentState = slotOfRole(slots, 'currentState')\n const reversibility = slotOfRole(slots, 'reversibility')\n\n return html`\n <section decision>\n ${headline?.found ? html`<div class=\"headline\">${slotText(headline)}</div>` : ''}\n ${subject?.found ? html`<div class=\"subject\">${slotText(subject)}</div>` : ''}\n ${\n reason\n ? html`\n <div class=\"block\">\n <div class=\"label\">${i18next.t('field.reason')}</div>\n <div class=\"said ${hasWords(reason) ? '' : 'none'}\">\n ${hasWords(reason) ? slotText(reason) : i18next.t('text.no-reason-given')}\n </div>\n </div>\n `\n : ''\n }\n ${renderReversibility(reversibility)}\n ${\n evidence.length\n ? html`\n <div class=\"block\">\n <div class=\"label\">${i18next.t('label.what-it-was-judged-from')}</div>\n ${evidence.map(\n slot => html`\n <div class=\"evidence\">\n ${decisionLabel(slot) ? html`<div class=\"label\">${decisionLabel(slot)}</div>` : ''}\n <div>${slotText(slot)}</div>\n </div>\n `\n )}\n </div>\n `\n : ''\n }\n ${\n currentState?.found\n ? html`\n <div class=\"current-state\">\n <div class=\"label\">${decisionLabel(currentState) || i18next.t('label.current-state')}</div>\n <div>${slotText(currentState)}</div>\n </div>\n `\n : ''\n }\n ${\n consequences.length\n ? html`\n <div class=\"block\">\n <div class=\"label\">${i18next.t('label.what-changes')}</div>\n ${consequences.map(\n slot => html`\n <div class=\"consequence\">\n ${decisionLabel(slot) ? html`<div class=\"label\">${decisionLabel(slot)}</div>` : ''}\n <div>${slotText(slot)}</div>\n </div>\n `\n )}\n </div>\n `\n : ''\n }\n </section>\n `\n}\n\n/**\n * A slot's own label - only where the declaration names one with `tKey`.\n *\n * A decision key's `name` is the developer's note (\"Never shown on a screen\", ActivityDecisionKeyItem),\n * and without `tKey` the screen shows no label. declaredLabel falls back to `name` whenever `tKey` is\n * absent - right for a search key, whose name is its column, and wrong here: asking for `name` put\n * \"what approving does\" under 「승인하면 달라지는 것」 on a Korean screen (2026-10-16). The fallback\n * stays for a declared key whose translation is missing.\n */\nfunction decisionLabel(slot: DecisionSlot): string {\n return slot.tKey ? declaredLabel(slot) : ''\n}\n\n/**\n * Whether approving can be undone, drawn as the frame around the decision.\n *\n * Only an explicit true or false draws. Anything else - the path led nowhere, or the value is not\n * a boolean - draws nothing, because saying \"this can be undone\" on a screen that does not know is\n * the one mistake this line must never make.\n *\n * This is not rejectionIsFinal. That one says whether **rejecting** ends the case for good; this\n * says whether **approving** can be taken back. The twin actuation carries both, which is exactly\n * why they must not share a sentence.\n */\nfunction renderReversibility(slot?: DecisionSlot) {\n const said = reversibilityOf(slot)\n\n if (!said) {\n if (reversibilityIsMalformed(slot)) {\n /*\n * Declared, and the value is there, but not the shape this role takes - a bare sentence\n * where { reversible, caveat } belongs. Nothing is drawn, because this side cannot tell\n * which way it goes; the log is so a developer does not have to find it by hand, which is\n * how it was found the first time.\n */\n console.error(`[worklist] reversibility at \"${slot?.inputPath}\" is not { reversible, caveat } — nothing drawn`)\n }\n\n return ''\n }\n\n /*\n * The module's own sentence when it wrote one. 「버린 자재는 돌아오지 않습니다」 says why, which\n * the plain wording cannot; the boolean beside it is what decides how heavily this is drawn.\n */\n return html`\n <div class=\"reversibility\" ?irreversible=${!said.reversible}>\n <md-icon>${said.kind === COMPENSABLE ? 'history' : said.reversible ? 'undo' : 'lock'}</md-icon>\n <span\n >${\n said.caveat || (said.kind === COMPENSABLE\n ? i18next.t('text.approval-is-compensable', { defaultValue: 'The original decision remains. A subsequent corrective action is required.' })\n : i18next.t(said.reversible ? 'text.approval-is-reversible' : 'text.approval-is-irreversible'))\n }</span\n >\n </div>\n `\n}\n\n/**\n * One slot's value as something to read.\n *\n * A scalar prints with its unit when one was declared. Anything shaped - an object, a list - goes\n * through readValue, the same laying-out (without the copy button - a decision is read) the model sections use, so a payload reads the same way\n * wherever it appears and a four-level object does not become one long line.\n *\n * ── The sentence is built here, not frozen when the request was raised ──\n * Eighteen doors write a Korean sentence at issue and the issue snapshots it, so it stands in the\n * language of whoever raised it and an approver reading another language never catches up. Where\n * a door declares `valueTKey`, the sentence is made at reading time instead, out of the values\n * the request did freeze.\n *\n * **A door that has not moved yet draws exactly what it drew before.** That is what lets plant\n * move them one at a time instead of eighteen at once, and it covers a door that moved into a\n * locale nobody has written yet: an empty `defaultValue` means a missing key falls through to the\n * frozen sentence rather than printing `approval.…` on the card.\n */\nfunction slotText(slot: DecisionSlot): unknown {\n const said = sentenceOf(slot)\n\n if (said) {\n return said\n }\n\n /* The paper a decision is judged on - an attachment or a list of them - is drawn as the files to open, each in its own tab. */\n const files = attachedFiles(slot.value)\n if (files) {\n return html`<ul class=\"files\">\n ${files.map(file => html`<li><a href=${file.href} target=\"_blank\" rel=\"noopener\">${file.name}</a></li>`)}\n </ul>`\n }\n\n /*\n * A value that names what it is - `{ __typename, id, name }` - is drawn as its name, a link to its\n * 360 (ADR-0067 decision 1: an entity's name opens its 360). The counterparty an expenditure pays\n * opens the counterparty 360; a person opens the person 360. A kind nobody registered stays text.\n */\n if (slot.value && typeof slot.value === 'object' && !Array.isArray(slot.value) && entity360KindOf(slot.value)) {\n return entity360Name(slot.value)\n }\n\n const scalar = slot.value !== null && slot.value !== undefined && typeof slot.value !== 'object'\n\n if (scalar && slot.unit) {\n return `${slot.value} ${slot.unit}`\n }\n\n return scalar ? String(slot.value) : readValue(slot.value)\n}\n\n/** The sentence this slot declares, when it declares one and the locale has it. */\nfunction sentenceOf(slot: DecisionSlot): string {\n if (!slot.valueTKey) {\n return ''\n }\n\n return String(i18next.t(slot.valueTKey, { ...(slot.params || {}), defaultValue: '' }) || '')\n}\n\n/** Did a person actually write something here. Whitespace is not words. */\nfunction hasWords(slot: DecisionSlot): boolean {\n return typeof slot.value === 'string' ? slot.value.trim().length > 0 : slot.found && slot.value != null\n}\n\n/*\n * The decision, drawn from the activity's declaration.\n *\n * The order is the declaration's and the weight descends with it: what is being asked reads\n * first at title size, then what it is about, then the words a person wrote, then what changes\n * on approval, and last why it came up at all.\n */\nexport const DecisionContentStyles = css`\n section[decision] .headline {\n font-size: var(--ui-t-headline, 19px);\n font-weight: 600;\n line-height: 1.35;\n color: var(--ui-ink, #17242c);\n word-break: keep-all;\n }\n\n section[decision] .subject {\n margin-top: 3px;\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-muted, #57676f);\n }\n\n section[decision] .block {\n margin-top: 14px;\n }\n\n section[decision] .block > .label {\n margin-bottom: 4px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n }\n\n /* What a person wrote is set apart, because it is the only part nobody computed. */\n section[decision] .said {\n padding: 9px 11px;\n border-left: 3px solid var(--ui-line, #d5dce0);\n background: var(--ui-line-soft, #f2f5f7);\n color: var(--ui-ink, #17242c);\n word-break: break-word;\n }\n\n section[decision] .none {\n color: var(--ui-faint, #8a959c);\n }\n\n section[decision] .consequence {\n margin-top: 8px;\n padding: 9px 11px;\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: var(--ui-r-small, 6px);\n }\n\n section[decision] .consequence > .label {\n margin-bottom: 3px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n }\n\n /*\n * Whether approving can be undone reads as the frame around the decision, not as one of its\n * consequences. Among them it carries the same weight as \"four orders get pushed back\", and\n * on an approval that moves equipment it is the heaviest line on the screen.\n */\n section[decision] .reversibility {\n display: flex;\n align-items: center;\n gap: 6px;\n\n margin-top: 12px;\n padding: 8px 11px;\n border-radius: var(--ui-r-small, 6px);\n\n background: var(--ui-line-soft, #f2f5f7);\n color: var(--ui-muted, #57676f);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n section[decision] .reversibility[irreversible] {\n background: var(--md-sys-color-error-container, #ffdad6);\n color: var(--md-sys-color-on-error-container, #410002);\n font-weight: 600;\n }\n\n section[decision] .reversibility md-icon {\n font-size: 1.15em;\n }\n\n /*\n * What the decision was made from. Set flatter than the consequences - it is the record the\n * approver checks, not something the approval brings about.\n */\n section[decision] .evidence {\n margin-top: 8px;\n padding: 7px 0 7px 11px;\n border-left: 2px solid var(--ui-line, #d5dce0);\n }\n\n section[decision] ul.files {\n margin: 0;\n padding-left: 1.2em;\n }\n\n /* A file reads as a link the way a name that opens its 360 does (entity360Name). */\n section[decision] ul.files a {\n color: inherit;\n text-decoration: underline;\n text-decoration-thickness: 1px;\n text-underline-offset: 3px;\n text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);\n }\n\n section[decision] .evidence > .label {\n margin-bottom: 2px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n }\n\n /*\n * What is true right now sits apart from what the approval would change. Sharing the box the\n * consequences use would say the approval causes these readings.\n */\n section[decision] .current-state {\n margin-top: 14px;\n padding: 9px 11px;\n border: 1px dashed var(--ui-line, #d5dce0);\n border-radius: var(--ui-r-small, 6px);\n }\n\n section[decision] .current-state > .label {\n margin-bottom: 3px;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n }\n`\n"]}
|
|
@@ -151,3 +151,14 @@ export declare function reversibilityIsMalformed(slot?: DecisionSlot): boolean;
|
|
|
151
151
|
* draws even when missing, as the grey "nobody wrote one" line, so declaring it is enough.
|
|
152
152
|
*/
|
|
153
153
|
export declare function decisionDrawable(slots: DecisionSlot[]): boolean;
|
|
154
|
+
/** A file a decision carries, to open from the card. */
|
|
155
|
+
export type AttachedFile = {
|
|
156
|
+
name: string;
|
|
157
|
+
href: string;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* The files a value is, when it is an attachment or a list of them - `{ __typename: 'Attachment', name, fullpath }`,
|
|
161
|
+
* as a module freezes the paper a decision is judged on (a seal use's document, 2026-11-10). Undefined for anything
|
|
162
|
+
* else, which is read as before; a list that mixes in anything but attachments is not files.
|
|
163
|
+
*/
|
|
164
|
+
export declare function attachedFiles(value: unknown): AttachedFile[] | undefined;
|
|
@@ -149,4 +149,15 @@ export function decisionDrawable(slots) {
|
|
|
149
149
|
found('evidence') ||
|
|
150
150
|
reversibilityOf(slotOfRole(slots, 'reversibility')) !== undefined);
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* The files a value is, when it is an attachment or a list of them - `{ __typename: 'Attachment', name, fullpath }`,
|
|
154
|
+
* as a module freezes the paper a decision is judged on (a seal use's document, 2026-11-10). Undefined for anything
|
|
155
|
+
* else, which is read as before; a list that mixes in anything but attachments is not files.
|
|
156
|
+
*/
|
|
157
|
+
export function attachedFiles(value) {
|
|
158
|
+
const one = (item) => item && typeof item === 'object' && item.__typename === 'Attachment' && typeof item.fullpath === 'string' ? { name: String(item.name ?? item.fullpath), href: item.fullpath } : undefined;
|
|
159
|
+
const list = Array.isArray(value) ? value : [value];
|
|
160
|
+
const files = list.map(one);
|
|
161
|
+
return list.length && files.every(Boolean) ? files : undefined;
|
|
162
|
+
}
|
|
152
163
|
//# sourceMappingURL=decision-slots.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-slots.js","sourceRoot":"","sources":["../../client/components/decision-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAA;AAE5E,6GAA6G;AAC7G,OAAO,EAAE,WAAW,EAAE,CAAA;AAEtB,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;CACL,CAAA;AAmDV;;;;;;;;;;;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,IAAI,EAAE,GAAG,EAAE,IAAI;YACf,SAAS,EAAE,GAAG,EAAE,SAAS;YACzB,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC;YACtC,KAAK;YACL,KAAK;SACN,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAGD,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;;;;;;;;;;;;;;;;;;;GAmBG;AACH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,CAAU,CAAA;AAGpG,+GAA+G;AAC/G,MAAM,CAAC,MAAM,WAAW,GAAsB,aAAa,CAAA;AAE3D,qFAAqF;AACrF,MAAM,CAAC,MAAM,UAAU,GAAsB,YAAY,CAAA;AAIzD;;;;;;;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;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAE,mBAAyC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,KAAK,KAAK,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAA;IAExK,OAAO;QACL,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,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;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAqB;IACpD,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAEjF,OAAO,CACL,KAAK,CAAC,UAAU,CAAC;QACjB,KAAK,CAAC,SAAS,CAAC;QAChB,KAAK,CAAC,cAAc,CAAC;QACrB,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC;QAC7B,KAAK,CAAC,aAAa,CAAC;QACpB,KAAK,CAAC,UAAU,CAAC;QACjB,eAAe,CAAC,UAAU,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,KAAK,SAAS,CAClE,CAAA;AACH,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 heavy is imported ─────────────────────────────────────────────\n * No lit, no i18next — only the pure reading of the input in `shared/decision-sentences`, which the\n * server freezes sentences with. 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\nimport { paramsFrom, valueAtPath } from '../../shared/decision-sentences.js'\n\n/* The blanks are read the same way the server freezes them — one reading, in `shared/decision-sentences`. */\nexport { valueAtPath }\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] as const\n\nexport type DecisionKeyDeclaration = {\n role?: string\n inputPath?: string\n name?: string\n tKey?: string\n unit?: string\n /** i18n key for the value **sentence** — `approval.<door>.<inputPath>`, computed by the server. */\n valueTKey?: string\n /** What the sentence puts in its blanks: name in the sentence → inputPath to read it from. */\n params?: { [name: string]: string }\n}\n\nexport type DecisionSlot = {\n role: string\n inputPath: string\n /**\n * 번역이 없을 때 화면이 보일 수 있는 단 하나의 글.\n *\n * 선언은 늘 이것을 주는데 슬롯이 버리고 있었다. 그래서 `tKey` 가 다섯 언어 어디에도 없으면 화면에\n * 남는 것이 점 섞인 키뿐이었다 — 보일 것이 없는 게 아니라 **가진 것을 안 나른 것**이다.\n */\n name?: string\n tKey?: string\n unit?: string\n /** i18n key for the value sentence. Absent means the frozen `value` is what this slot has. */\n valueTKey?: string\n /**\n * The sentence's blanks, **already read out of the input.**\n *\n * The declaration says where each value lives; this says what was there. Resolving here rather\n * than on the screen keeps the path language in one place — the same `valueAtPath` the slot's own\n * value goes through, so a params path that leads nowhere behaves the way an inputPath does\n * instead of inventing a second rule.\n */\n params?: { [name: string]: unknown }\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/**\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 name: key?.name,\n tKey: key?.tKey,\n unit: key?.unit,\n valueTKey: key?.valueTKey,\n params: paramsFrom(key?.params, input),\n value,\n found\n }\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 */\n/**\n * The kinds of an effect, as shell names them (shell server/reversibility, ADR-0064 decision 1).\n *\n * A copy, and the only one on this side: shell's client entry carries side effects and a client\n * importing server code moves the build, so the shell file itself says screens do not import it.\n * test/reversibility-kinds.test.ts holds this list to shell's, word for word.\n */\nexport const REVERSIBILITY_KINDS = ['irreversible', 'compensable', 'reversible', 'unknown'] as const\nexport type ReversibilityKind = (typeof REVERSIBILITY_KINDS)[number]\n\n/** The one kind that is not undone and still not destructive: the original stays, a new record corrects it. */\nexport const COMPENSABLE: ReversibilityKind = 'compensable'\n\n/** Undone by a paired operation. The only kind that agrees with reversible: true. */\nexport const REVERSIBLE: ReversibilityKind = 'reversible'\n\nexport type Reversibility = { reversible: boolean; kind?: ReversibilityKind; 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 if (value.kind !== undefined && (!(REVERSIBILITY_KINDS as readonly string[]).includes(value.kind) || (value.kind === REVERSIBLE) !== value.reversible)) return undefined\n\n return {\n reversible: value.reversible,\n ...(value.kind ? { kind: value.kind } : {}),\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\n/**\n * Whether these slots draw anything at all.\n *\n * Every declared slot can miss a payload - an older instance predates the field a declaration\n * names. Then a screen falls back to the model rather than drawing an empty decision box. `reason`\n * draws even when missing, as the grey \"nobody wrote one\" line, so declaring it is enough.\n */\nexport function decisionDrawable(slots: DecisionSlot[]): boolean {\n const found = (role: string) => slotsOfRole(slots, role).some(slot => slot.found)\n\n return (\n found('headline') ||\n found('subject') ||\n found('currentState') ||\n !!slotOfRole(slots, 'reason') ||\n found('consequence') ||\n found('evidence') ||\n reversibilityOf(slotOfRole(slots, 'reversibility')) !== undefined\n )\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 heavy is imported ─────────────────────────────────────────────\n * No lit, no i18next — only the pure reading of the input in `shared/decision-sentences`, which the\n * server freezes sentences with. 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\nimport { paramsFrom, valueAtPath } from '../../shared/decision-sentences.js'\n\n/* The blanks are read the same way the server freezes them — one reading, in `shared/decision-sentences`. */\nexport { valueAtPath }\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] as const\n\nexport type DecisionKeyDeclaration = {\n role?: string\n inputPath?: string\n name?: string\n tKey?: string\n unit?: string\n /** i18n key for the value **sentence** — `approval.<door>.<inputPath>`, computed by the server. */\n valueTKey?: string\n /** What the sentence puts in its blanks: name in the sentence → inputPath to read it from. */\n params?: { [name: string]: string }\n}\n\nexport type DecisionSlot = {\n role: string\n inputPath: string\n /**\n * 번역이 없을 때 화면이 보일 수 있는 단 하나의 글.\n *\n * 선언은 늘 이것을 주는데 슬롯이 버리고 있었다. 그래서 `tKey` 가 다섯 언어 어디에도 없으면 화면에\n * 남는 것이 점 섞인 키뿐이었다 — 보일 것이 없는 게 아니라 **가진 것을 안 나른 것**이다.\n */\n name?: string\n tKey?: string\n unit?: string\n /** i18n key for the value sentence. Absent means the frozen `value` is what this slot has. */\n valueTKey?: string\n /**\n * The sentence's blanks, **already read out of the input.**\n *\n * The declaration says where each value lives; this says what was there. Resolving here rather\n * than on the screen keeps the path language in one place — the same `valueAtPath` the slot's own\n * value goes through, so a params path that leads nowhere behaves the way an inputPath does\n * instead of inventing a second rule.\n */\n params?: { [name: string]: unknown }\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/**\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 name: key?.name,\n tKey: key?.tKey,\n unit: key?.unit,\n valueTKey: key?.valueTKey,\n params: paramsFrom(key?.params, input),\n value,\n found\n }\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 */\n/**\n * The kinds of an effect, as shell names them (shell server/reversibility, ADR-0064 decision 1).\n *\n * A copy, and the only one on this side: shell's client entry carries side effects and a client\n * importing server code moves the build, so the shell file itself says screens do not import it.\n * test/reversibility-kinds.test.ts holds this list to shell's, word for word.\n */\nexport const REVERSIBILITY_KINDS = ['irreversible', 'compensable', 'reversible', 'unknown'] as const\nexport type ReversibilityKind = (typeof REVERSIBILITY_KINDS)[number]\n\n/** The one kind that is not undone and still not destructive: the original stays, a new record corrects it. */\nexport const COMPENSABLE: ReversibilityKind = 'compensable'\n\n/** Undone by a paired operation. The only kind that agrees with reversible: true. */\nexport const REVERSIBLE: ReversibilityKind = 'reversible'\n\nexport type Reversibility = { reversible: boolean; kind?: ReversibilityKind; 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 if (value.kind !== undefined && (!(REVERSIBILITY_KINDS as readonly string[]).includes(value.kind) || (value.kind === REVERSIBLE) !== value.reversible)) return undefined\n\n return {\n reversible: value.reversible,\n ...(value.kind ? { kind: value.kind } : {}),\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\n/**\n * Whether these slots draw anything at all.\n *\n * Every declared slot can miss a payload - an older instance predates the field a declaration\n * names. Then a screen falls back to the model rather than drawing an empty decision box. `reason`\n * draws even when missing, as the grey \"nobody wrote one\" line, so declaring it is enough.\n */\nexport function decisionDrawable(slots: DecisionSlot[]): boolean {\n const found = (role: string) => slotsOfRole(slots, role).some(slot => slot.found)\n\n return (\n found('headline') ||\n found('subject') ||\n found('currentState') ||\n !!slotOfRole(slots, 'reason') ||\n found('consequence') ||\n found('evidence') ||\n reversibilityOf(slotOfRole(slots, 'reversibility')) !== undefined\n )\n}\n\n/** A file a decision carries, to open from the card. */\nexport type AttachedFile = { name: string; href: string }\n\n/**\n * The files a value is, when it is an attachment or a list of them - `{ __typename: 'Attachment', name, fullpath }`,\n * as a module freezes the paper a decision is judged on (a seal use's document, 2026-11-10). Undefined for anything\n * else, which is read as before; a list that mixes in anything but attachments is not files.\n */\nexport function attachedFiles(value: unknown): AttachedFile[] | undefined {\n const one = (item: any): AttachedFile | undefined =>\n item && typeof item === 'object' && item.__typename === 'Attachment' && typeof item.fullpath === 'string' ? { name: String(item.name ?? item.fullpath), href: item.fullpath } : undefined\n const list = Array.isArray(value) ? value : [value]\n const files = list.map(one)\n return list.length && files.every(Boolean) ? (files as AttachedFile[]) : undefined\n}\n"]}
|
|
@@ -11,4 +11,10 @@ import { nothing } from 'lit';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function renderSourceLink(input: {
|
|
13
13
|
sourceHref?: unknown;
|
|
14
|
-
} | null | undefined): import("lit-html").TemplateResult<1
|
|
14
|
+
} | null | undefined): typeof nothing | import("lit-html").TemplateResult<1>;
|
|
15
|
+
/**
|
|
16
|
+
* How the link looks, wherever it is drawn - the way a name that opens its 360 looks (`entity360Name`): the text's own
|
|
17
|
+
* colour, a light underline that darkens under the pointer. The approval screen drew it as plain text and the case popup
|
|
18
|
+
* in the browser's blue (2026-11-10).
|
|
19
|
+
*/
|
|
20
|
+
export declare const SourceLinkStyles: import("lit").CSSResult;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { html, nothing } from 'lit';
|
|
1
|
+
import { css, html, nothing } from 'lit';
|
|
2
2
|
import { i18next } from '@operato/i18n';
|
|
3
3
|
import { navigate } from '@operato/shell';
|
|
4
4
|
import { sourceAddress } from '../../shared/task-followup.js';
|
|
@@ -23,10 +23,35 @@ export function renderSourceLink(input) {
|
|
|
23
23
|
if (event.button || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
|
|
24
24
|
return;
|
|
25
25
|
event.preventDefault();
|
|
26
|
-
|
|
26
|
+
/* Named as the source: inside a popup (결재 현황, 상신함) the address takes a move only from that popup. */
|
|
27
|
+
navigate(address, false, event.currentTarget);
|
|
27
28
|
}}
|
|
28
29
|
>${i18next.t('button.task-open-source')}</a
|
|
29
30
|
>
|
|
30
31
|
</p>`;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* How the link looks, wherever it is drawn - the way a name that opens its 360 looks (`entity360Name`): the text's own
|
|
35
|
+
* colour, a light underline that darkens under the pointer. The approval screen drew it as plain text and the case popup
|
|
36
|
+
* in the browser's blue (2026-11-10).
|
|
37
|
+
*/
|
|
38
|
+
export const SourceLinkStyles = css `
|
|
39
|
+
p[source-link] {
|
|
40
|
+
margin: 0 0 var(--spacing-medium, 8px);
|
|
41
|
+
font-size: var(--ui-t-body, 13px);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
p[source-link] a {
|
|
45
|
+
color: inherit;
|
|
46
|
+
text-decoration: underline;
|
|
47
|
+
text-decoration-thickness: 1px;
|
|
48
|
+
text-underline-offset: 3px;
|
|
49
|
+
text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
|
|
50
|
+
transition: text-decoration-color 0.12s;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
p[source-link] a:hover {
|
|
54
|
+
text-decoration-color: color-mix(in srgb, currentColor 65%, transparent);
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
32
57
|
//# sourceMappingURL=source-link.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-link.js","sourceRoot":"","sources":["../../client/components/source-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"source-link.js","sourceRoot":"","sources":["../../client/components/source-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAE7D;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAkD;IACjF,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;IAChD,IAAI,CAAC,OAAO;QAAE,OAAO,OAAO,CAAA;IAE5B,OAAO,IAAI,CAAA;;aAEA,OAAO;eACL,CAAC,KAAiB,EAAE,EAAE;QAC7B,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM;YAAE,OAAM;QAC5F,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,qGAAqG;QACrG,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,aAAwB,CAAC,CAAA;IAC1D,CAAC;SACE,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC;;OAEtC,CAAA;AACP,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;CAkBlC,CAAA","sourcesContent":["import { css, html, nothing } from 'lit'\nimport { i18next } from '@operato/i18n'\nimport { navigate } from '@operato/shell'\n\nimport { sourceAddress } from '../../shared/task-followup.js'\n\n/**\n * The link to the record a task is about - the page its module named as `input.sourceHref`.\n *\n * Both readers of a task need it: the one who carries it out (the task screen) and the one who decides it (the approval\n * screen). An approval card holds a few lines of the record; the approver judges the record itself, so the link to it\n * stands on both screens, drawn here once.\n *\n * Only an application-relative address is drawn (`sourceAddress`); a plain click stays in the app, a modified click is\n * left to the browser so the record can open in another tab.\n */\nexport function renderSourceLink(input: { sourceHref?: unknown } | null | undefined) {\n const address = sourceAddress(input?.sourceHref)\n if (!address) return nothing\n\n return html`<p source-link>\n <a\n href=${address}\n @click=${(event: MouseEvent) => {\n if (event.button || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return\n event.preventDefault()\n /* Named as the source: inside a popup (결재 현황, 상신함) the address takes a move only from that popup. */\n navigate(address, false, event.currentTarget as Element)\n }}\n >${i18next.t('button.task-open-source')}</a\n >\n </p>`\n}\n\n/**\n * How the link looks, wherever it is drawn - the way a name that opens its 360 looks (`entity360Name`): the text's own\n * colour, a light underline that darkens under the pointer. The approval screen drew it as plain text and the case popup\n * in the browser's blue (2026-11-10).\n */\nexport const SourceLinkStyles = css`\n p[source-link] {\n margin: 0 0 var(--spacing-medium, 8px);\n font-size: var(--ui-t-body, 13px);\n }\n\n p[source-link] a {\n color: inherit;\n text-decoration: underline;\n text-decoration-thickness: 1px;\n text-underline-offset: 3px;\n text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);\n transition: text-decoration-color 0.12s;\n }\n\n p[source-link] a:hover {\n text-decoration-color: color-mix(in srgb, currentColor 65%, transparent);\n }\n`\n"]}
|
|
@@ -23,7 +23,7 @@ import { isReferenceStep } from '@things-factory/organization/dist-client';
|
|
|
23
23
|
import { ActivityFieldsStyles, inputSpecOf, outputSpecOf, renderGiven } from '../../components/activity-fields.js';
|
|
24
24
|
import { DecisionContentStyles, DecisionKeysFields, renderDecision } from '../../components/decision-content.js';
|
|
25
25
|
import { decisionSlots, slotOfRole } from '../../components/decision-slots.js';
|
|
26
|
-
import { renderSourceLink } from '../../components/source-link.js';
|
|
26
|
+
import { renderSourceLink, SourceLinkStyles } from '../../components/source-link.js';
|
|
27
27
|
const ActivityApprovalFetchResult = `\
|
|
28
28
|
{ id
|
|
29
29
|
judgment
|
|
@@ -170,6 +170,7 @@ let ActivityApprovalPage = class ActivityApprovalPage extends localize(i18next)(
|
|
|
170
170
|
ScrollbarStyles,
|
|
171
171
|
ActivityFieldsStyles,
|
|
172
172
|
DecisionContentStyles,
|
|
173
|
+
SourceLinkStyles,
|
|
173
174
|
css `
|
|
174
175
|
:host {
|
|
175
176
|
color: var(--ui-ink, #17242c);
|