@things-factory/worklist 10.1.267 → 10.1.269
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/activity-cells.d.ts +4 -0
- package/dist-client/activity-cells.js +11 -0
- package/dist-client/activity-cells.js.map +1 -1
- package/dist-client/components/decision-content.js +22 -1
- 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 +6 -0
- 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.js +4 -0
- 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/todo/approval-pending-list-page.js +2 -6
- package/dist-client/pages/todo/approval-pending-list-page.js.map +1 -1
- package/dist-client/pages/todo/done-list-calendar-page.js +7 -2
- package/dist-client/pages/todo/done-list-calendar-page.js.map +1 -1
- package/dist-client/pages/todo/todo-list-page.js +2 -12
- package/dist-client/pages/todo/todo-list-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/activity-approval/activity-approval-query.d.ts +5 -0
- package/dist-server/service/activity-approval/activity-approval-query.js +5 -0
- package/dist-server/service/activity-approval/activity-approval-query.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/tests/open-doors.test.ts +1 -1
|
@@ -19,3 +19,7 @@ export declare function approvalMarks(record: any): unknown[];
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function dueCell(isActive: () => boolean): (value: any, column: any, record: any, rowIndex: number, field: any) => import("lit-html").TemplateResult<1>;
|
|
21
21
|
export declare function priorityCell(): (value: any) => import("lit-html").TemplateResult<1>;
|
|
22
|
+
export declare function threadStateOptions(): {
|
|
23
|
+
display: string;
|
|
24
|
+
value: string;
|
|
25
|
+
}[];
|
|
@@ -114,4 +114,15 @@ export function priorityCell() {
|
|
|
114
114
|
>`;
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* The thread states, each with its label — every value of `ActivityThreadStatus`.
|
|
119
|
+
*
|
|
120
|
+
* Each list page wrote its own copy, and 내 결재함's had three of the eight: its 「완료」 tab lists
|
|
121
|
+
* withdrawn cases too, and a `select` column without the value prints it as it is, so a withdrawn
|
|
122
|
+
* case read `aborted` beside 완료됨. One list, so a page cannot leave a state out.
|
|
123
|
+
*/
|
|
124
|
+
const THREAD_STATES = ['unassigned', 'assigned', 'started', 'submitted', 'escalated', 'rejected', 'ended', 'aborted'];
|
|
125
|
+
export function threadStateOptions() {
|
|
126
|
+
return THREAD_STATES.map(value => ({ display: i18next.t(`label.activity-state-${value}`), value }));
|
|
127
|
+
}
|
|
117
128
|
//# sourceMappingURL=activity-cells.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-cells.js","sourceRoot":"","sources":["../client/activity-cells.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit'\nimport { asyncReplace } from 'lit/directives/async-replace.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { dueTimer, isOverdue } from './due.js'\n\n/*\n * 업무 목록의 칸을 그리는 방식 — **한 곳에서만 정한다.**\n *\n * ── 왜 모았나 (2026-09-01) ─────────────────────────────────────────────────\n * 업무 목록 화면이 다섯이고(할 일 · 완료 · 작성 중 · 결재 대기 · 결재 완료) 컬럼 목록은 서로 다르다.\n * 그런데 **칸을 그리는 방식은 같은 것을 복사해 쓰고 있었다.** 우선순위의 별 렌더러가 네 곳에 같은\n * 코드로 있었고, 기한 계산 함수는 세 곳에 있었는데 그중 하나가 틀려 있었다(due.ts 머리말 참조).\n *\n * 복사본이 여럿이면 한 곳을 고쳐도 나머지는 그대로다. 그래서 화면마다 컬럼 목록은 그대로 두고\n * **그리는 방식만** 여기로 모았다.\n */\n\n/**\n * 안건 · 업무 이름 — 이름과 설명을 한 칸에 두 줄로.\n *\n * 둘은 함께 읽는 것인데 따로 두면 그 사이를 우선순위 · 차수 같은 칸이 가로막는다. 그리고 표식(메모 ·\n * 재상신)은 이름과 함께 보여야 뜻이 선다.\n *\n * `marks` 는 화면이 정한다 — 결재함은 메모와 재상신을 보이고, 작성 중 목록은 보일 것이 없다.\n */\nexport function subjectCell(marks?: (record: any) => unknown[]) {\n return (value: any, column: any, record: any, rowIndex: number, field: any) => {\n field?.setAttribute?.('twoline', '')\n\n const m = marks?.(record) ?? []\n\n /*\n * 값을 span 으로 감싼다. 텍스트 노드로 두면 CSS 가 표식과의 사이를 띄울 방법이 없다 — 형제\n * 선택자는 텍스트 노드를 보지 못한다. `.line` 이 간격과 높이를 잡는다(data-grid-field.ts).\n */\n return html`<span class=\"line\"><span>${value ?? ''}</span>${m}</span\n ><span class=\"sub\">${record?.description ?? ''}</span>`\n }\n}\n\n/** 결재함이 쓰는 표식 — 「메모 있음」과 「N차 재상신」. */\nexport function approvalMarks(record: any): unknown[] {\n const marks: unknown[] = []\n\n /* 승인자가 보다가 메모를 남기고 미뤄 둔 것 — 내가 검토하던 것이 무엇인지 한눈에 보여야 한다. */\n if (record?.comment) marks.push(html`<span class=\"pill info\">${i18next.t('label.approval-noted')}</span>`)\n\n /* 재상신은 예외라 표식으로 낸다. 거의 모든 줄이 1차인 목록에서 차수 칸은 아무것도 알려 주지 않는다. */\n if (record?.round > 1)\n marks.push(html`<span class=\"pill warn\">${i18next.t('label.approval-round-n', { n: record.round })}</span>`)\n\n return marks\n}\n\n/**\n * 기한 한 칸 — 남은 시간이 위, 절대 시각이 아래다.\n *\n * 전에는 「만기 시간」과 「남은 시간」이 각각 180px 을 쓰는 두 칸이었다. 사람이 묻는 것은 「얼마나\n * 남았나」 하나이고, 절대 시각은 그것을 확인할 때만 본다.\n *\n * 기한이 지난 것은 남은 시간에 강조를 준다.\n */\nexport function dueCell(isActive: () => boolean) {\n return (value: any, column: any, record: any, rowIndex: number, field: any) => {\n const dueAt = record?.dueAt\n\n /* 값이 없으면 빈 칸으로 두지 않는다 — 빈 칸은 값이 없는 것인지 화면이 못 낸 것인지 구별되지 않는다. */\n if (dueAt === null || dueAt === undefined || dueAt === '') {\n return html`<span class=\"none\">${i18next.t('text.no-due')}</span>`\n }\n\n field?.setAttribute?.('twoline', '')\n\n return html`<span class=${isOverdue(dueAt) ? 'crit' : ''}>${asyncReplace(dueTimer(dueAt, isActive))}</span\n ><span class=\"sub\"\n >${new Date(Date.parse(String(dueAt))).toLocaleString(i18next.language || undefined, {\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit'\n })}</span\n >`\n }\n}\n\n/**\n * 우선순위 — **별의 개수가 크기를 말한다.**\n *\n * 별을 숫자로 바꿔 봤다가 되돌렸다. 숫자만 적으면 **큰 쪽이 급한지 작은 쪽이 급한지 알 수 없다.**\n * 별은 개수가 곧 크기라서 표를 외우지 않아도 읽힌다. 그것이 이 칸에서 가장 중요한 것이다.\n *\n * 고친 것은 색이다. 전에는 값마다 색이 달랐다.\n *\n * 1 #98FB98 흰 바탕에서 대비 1.4 — 보이지 않았다\n * 2 #87CEEB\n * 3 #FF6B6B\n * 4 #8B0000\n *\n * 개수가 이미 크기를 말하는데 색까지 값마다 다르면 같은 것을 두 번 말하는 것이고, 네 색을 외운 사람만\n * 정확히 읽을 수 있다. 그리고 첫 색은 아예 안 보였다.\n *\n * 그래서 색은 **두 단계**만 쓴다 — 보통은 흐리게, 「중요」 「중요」 필터가 잡는 경계(priority >= 2)부터\n * 강조한다. 개수가 크기를 말하고 색이 「이 줄은 선을 넘었다」를 말한다. 서로 다른 것을 말하므로\n * 겹치지 않는다.\n *\n * 값이 없으면 — 다. 0 개의 별로 그리면 「보통」과 「값 없음」이 같아 보인다.\n */\nconst PRIORITY_STAR_CAP = 5\n\nexport function priorityCell() {\n return (value: any) => {\n if (value === null || value === undefined || value === '') return html`<span class=\"none\">—</span>`\n\n const n = Number(value)\n if (!Number.isFinite(n) || n <= 0) return html`<span class=\"none\">—</span>`\n\n /*\n * 열린 숫자 축이라(표준 Priority) 현장이 9 를 쓸 수도 있다. 별 아홉 개는 칸을 넘고 세어 읽을 수도\n * 없으므로, 다섯을 넘으면 별 다섯과 숫자를 함께 적는다 — 값을 잃지 않으면서 자리를 지킨다.\n */\n const stars = Math.min(Math.round(n), PRIORITY_STAR_CAP)\n const emphasis = n >= 2 ? 'crit' : 'quiet'\n\n return html`<span class=\"line ${emphasis}\"\n >${Array.from({ length: stars }, () => html`<md-icon class=\"star\">star</md-icon>`)}${n > PRIORITY_STAR_CAP\n ? html`<span class=\"sub\">${n}</span>`\n : ''}</span\n >`\n }\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"activity-cells.js","sourceRoot":"","sources":["../client/activity-cells.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit'\nimport { asyncReplace } from 'lit/directives/async-replace.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { dueTimer, isOverdue } from './due.js'\n\n/*\n * 업무 목록의 칸을 그리는 방식 — **한 곳에서만 정한다.**\n *\n * ── 왜 모았나 (2026-09-01) ─────────────────────────────────────────────────\n * 업무 목록 화면이 다섯이고(할 일 · 완료 · 작성 중 · 결재 대기 · 결재 완료) 컬럼 목록은 서로 다르다.\n * 그런데 **칸을 그리는 방식은 같은 것을 복사해 쓰고 있었다.** 우선순위의 별 렌더러가 네 곳에 같은\n * 코드로 있었고, 기한 계산 함수는 세 곳에 있었는데 그중 하나가 틀려 있었다(due.ts 머리말 참조).\n *\n * 복사본이 여럿이면 한 곳을 고쳐도 나머지는 그대로다. 그래서 화면마다 컬럼 목록은 그대로 두고\n * **그리는 방식만** 여기로 모았다.\n */\n\n/**\n * 안건 · 업무 이름 — 이름과 설명을 한 칸에 두 줄로.\n *\n * 둘은 함께 읽는 것인데 따로 두면 그 사이를 우선순위 · 차수 같은 칸이 가로막는다. 그리고 표식(메모 ·\n * 재상신)은 이름과 함께 보여야 뜻이 선다.\n *\n * `marks` 는 화면이 정한다 — 결재함은 메모와 재상신을 보이고, 작성 중 목록은 보일 것이 없다.\n */\nexport function subjectCell(marks?: (record: any) => unknown[]) {\n return (value: any, column: any, record: any, rowIndex: number, field: any) => {\n field?.setAttribute?.('twoline', '')\n\n const m = marks?.(record) ?? []\n\n /*\n * 값을 span 으로 감싼다. 텍스트 노드로 두면 CSS 가 표식과의 사이를 띄울 방법이 없다 — 형제\n * 선택자는 텍스트 노드를 보지 못한다. `.line` 이 간격과 높이를 잡는다(data-grid-field.ts).\n */\n return html`<span class=\"line\"><span>${value ?? ''}</span>${m}</span\n ><span class=\"sub\">${record?.description ?? ''}</span>`\n }\n}\n\n/** 결재함이 쓰는 표식 — 「메모 있음」과 「N차 재상신」. */\nexport function approvalMarks(record: any): unknown[] {\n const marks: unknown[] = []\n\n /* 승인자가 보다가 메모를 남기고 미뤄 둔 것 — 내가 검토하던 것이 무엇인지 한눈에 보여야 한다. */\n if (record?.comment) marks.push(html`<span class=\"pill info\">${i18next.t('label.approval-noted')}</span>`)\n\n /* 재상신은 예외라 표식으로 낸다. 거의 모든 줄이 1차인 목록에서 차수 칸은 아무것도 알려 주지 않는다. */\n if (record?.round > 1)\n marks.push(html`<span class=\"pill warn\">${i18next.t('label.approval-round-n', { n: record.round })}</span>`)\n\n return marks\n}\n\n/**\n * 기한 한 칸 — 남은 시간이 위, 절대 시각이 아래다.\n *\n * 전에는 「만기 시간」과 「남은 시간」이 각각 180px 을 쓰는 두 칸이었다. 사람이 묻는 것은 「얼마나\n * 남았나」 하나이고, 절대 시각은 그것을 확인할 때만 본다.\n *\n * 기한이 지난 것은 남은 시간에 강조를 준다.\n */\nexport function dueCell(isActive: () => boolean) {\n return (value: any, column: any, record: any, rowIndex: number, field: any) => {\n const dueAt = record?.dueAt\n\n /* 값이 없으면 빈 칸으로 두지 않는다 — 빈 칸은 값이 없는 것인지 화면이 못 낸 것인지 구별되지 않는다. */\n if (dueAt === null || dueAt === undefined || dueAt === '') {\n return html`<span class=\"none\">${i18next.t('text.no-due')}</span>`\n }\n\n field?.setAttribute?.('twoline', '')\n\n return html`<span class=${isOverdue(dueAt) ? 'crit' : ''}>${asyncReplace(dueTimer(dueAt, isActive))}</span\n ><span class=\"sub\"\n >${new Date(Date.parse(String(dueAt))).toLocaleString(i18next.language || undefined, {\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit'\n })}</span\n >`\n }\n}\n\n/**\n * 우선순위 — **별의 개수가 크기를 말한다.**\n *\n * 별을 숫자로 바꿔 봤다가 되돌렸다. 숫자만 적으면 **큰 쪽이 급한지 작은 쪽이 급한지 알 수 없다.**\n * 별은 개수가 곧 크기라서 표를 외우지 않아도 읽힌다. 그것이 이 칸에서 가장 중요한 것이다.\n *\n * 고친 것은 색이다. 전에는 값마다 색이 달랐다.\n *\n * 1 #98FB98 흰 바탕에서 대비 1.4 — 보이지 않았다\n * 2 #87CEEB\n * 3 #FF6B6B\n * 4 #8B0000\n *\n * 개수가 이미 크기를 말하는데 색까지 값마다 다르면 같은 것을 두 번 말하는 것이고, 네 색을 외운 사람만\n * 정확히 읽을 수 있다. 그리고 첫 색은 아예 안 보였다.\n *\n * 그래서 색은 **두 단계**만 쓴다 — 보통은 흐리게, 「중요」 「중요」 필터가 잡는 경계(priority >= 2)부터\n * 강조한다. 개수가 크기를 말하고 색이 「이 줄은 선을 넘었다」를 말한다. 서로 다른 것을 말하므로\n * 겹치지 않는다.\n *\n * 값이 없으면 — 다. 0 개의 별로 그리면 「보통」과 「값 없음」이 같아 보인다.\n */\nconst PRIORITY_STAR_CAP = 5\n\nexport function priorityCell() {\n return (value: any) => {\n if (value === null || value === undefined || value === '') return html`<span class=\"none\">—</span>`\n\n const n = Number(value)\n if (!Number.isFinite(n) || n <= 0) return html`<span class=\"none\">—</span>`\n\n /*\n * 열린 숫자 축이라(표준 Priority) 현장이 9 를 쓸 수도 있다. 별 아홉 개는 칸을 넘고 세어 읽을 수도\n * 없으므로, 다섯을 넘으면 별 다섯과 숫자를 함께 적는다 — 값을 잃지 않으면서 자리를 지킨다.\n */\n const stars = Math.min(Math.round(n), PRIORITY_STAR_CAP)\n const emphasis = n >= 2 ? 'crit' : 'quiet'\n\n return html`<span class=\"line ${emphasis}\"\n >${Array.from({ length: stars }, () => html`<md-icon class=\"star\">star</md-icon>`)}${n > PRIORITY_STAR_CAP\n ? html`<span class=\"sub\">${n}</span>`\n : ''}</span\n >`\n }\n}\n\n\n/**\n * The thread states, each with its label — every value of `ActivityThreadStatus`.\n *\n * Each list page wrote its own copy, and 내 결재함's had three of the eight: its 「완료」 tab lists\n * withdrawn cases too, and a `select` column without the value prints it as it is, so a withdrawn\n * case read `aborted` beside 완료됨. One list, so a page cannot leave a state out.\n */\nconst THREAD_STATES = ['unassigned', 'assigned', 'started', 'submitted', 'escalated', 'rejected', 'ended', 'aborted']\n\nexport function threadStateOptions(): { display: string; value: string }[] {\n return THREAD_STATES.map(value => ({ display: i18next.t(`label.activity-state-${value}`), value }))\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import { css, html, nothing } from 'lit';
|
|
|
3
3
|
import { i18next } from '@operato/i18n';
|
|
4
4
|
import { entity360KindOf, entity360Name } from '@operato/layout';
|
|
5
5
|
import { declaredLabel, readValue } from './activity-fields.js';
|
|
6
|
-
import { COMPENSABLE, decisionDrawable, decisionSlots, reversibilityIsMalformed, reversibilityOf, slotOfRole, slotsOfRole } from './decision-slots.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
|
*
|
|
@@ -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
|
|
@@ -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, readValue } 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 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 /*\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] .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":"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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"]}
|
|
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"]}
|
|
@@ -12,3 +12,9 @@ import { nothing } from 'lit';
|
|
|
12
12
|
export declare function renderSourceLink(input: {
|
|
13
13
|
sourceHref?: unknown;
|
|
14
14
|
} | null | undefined): import("lit-html").TemplateResult<1> | typeof nothing;
|
|
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);
|