@things-factory/ai-assistant 10.1.26 → 10.1.28
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/client/components/assistant-chat.ts +653 -59
- package/client/components/assistant-mode.test.ts +86 -55
- package/client/components/assistant-mode.ts +124 -82
- package/client/components/chat-echo-dedup.test.ts +23 -0
- package/client/components/chat-echo-dedup.ts +29 -3
- package/client/components/chat-input-builder.test.ts +20 -20
- package/client/components/chat-input-builder.ts +16 -14
- package/client/components/chat-proposal-survival.test.ts +73 -0
- package/client/components/mention-popup.ts +31 -8
- package/client/components/panel-aria.test.ts +34 -0
- package/client/components/proposal-outcome-line.ts +33 -0
- package/client/utils/assistant-session-controller.test.ts +75 -0
- package/client/utils/assistant-session-controller.ts +23 -0
- package/client/utils/assistant-session-transport.ts +11 -3
- package/dist-client/components/assistant-chat.d.ts +128 -13
- package/dist-client/components/assistant-chat.js +613 -67
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-mode.d.ts +80 -68
- package/dist-client/components/assistant-mode.js +63 -19
- package/dist-client/components/assistant-mode.js.map +1 -1
- package/dist-client/components/assistant-mode.test.js +74 -53
- package/dist-client/components/assistant-mode.test.js.map +1 -1
- package/dist-client/components/chat-echo-dedup.d.ts +2 -0
- package/dist-client/components/chat-echo-dedup.js +27 -4
- package/dist-client/components/chat-echo-dedup.js.map +1 -1
- package/dist-client/components/chat-echo-dedup.test.js +20 -0
- package/dist-client/components/chat-echo-dedup.test.js.map +1 -1
- package/dist-client/components/chat-input-builder.d.ts +12 -7
- package/dist-client/components/chat-input-builder.js +8 -10
- package/dist-client/components/chat-input-builder.js.map +1 -1
- package/dist-client/components/chat-input-builder.test.js +17 -18
- package/dist-client/components/chat-input-builder.test.js.map +1 -1
- package/dist-client/components/chat-proposal-survival.test.d.ts +1 -0
- package/dist-client/components/chat-proposal-survival.test.js +61 -0
- package/dist-client/components/chat-proposal-survival.test.js.map +1 -0
- package/dist-client/components/mention-popup.js +28 -8
- package/dist-client/components/mention-popup.js.map +1 -1
- package/dist-client/components/panel-aria.test.d.ts +1 -0
- package/dist-client/components/panel-aria.test.js +31 -0
- package/dist-client/components/panel-aria.test.js.map +1 -0
- package/dist-client/components/proposal-outcome-line.d.ts +9 -0
- package/dist-client/components/proposal-outcome-line.js +15 -0
- package/dist-client/components/proposal-outcome-line.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-session-controller.d.ts +10 -0
- package/dist-client/utils/assistant-session-controller.js +16 -0
- package/dist-client/utils/assistant-session-controller.js.map +1 -1
- package/dist-client/utils/assistant-session-controller.test.d.ts +1 -0
- package/dist-client/utils/assistant-session-controller.test.js +56 -0
- package/dist-client/utils/assistant-session-controller.test.js.map +1 -0
- package/dist-client/utils/assistant-session-transport.js +11 -3
- package/dist-client/utils/assistant-session-transport.js.map +1 -1
- package/dist-server/service/assistant-capability-resolver.d.ts +17 -0
- package/dist-server/service/assistant-capability-resolver.js +88 -0
- package/dist-server/service/assistant-capability-resolver.js.map +1 -0
- package/dist-server/service/assistant-chat-resolver.d.ts +6 -6
- package/dist-server/service/assistant-chat-resolver.js +166 -44
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +25 -0
- package/dist-server/service/assistant-mode/assistant-mode-resolver.js +140 -0
- package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -0
- package/dist-server/service/assistant-mode/index.d.ts +9 -0
- package/dist-server/service/assistant-mode/index.js +19 -0
- package/dist-server/service/assistant-mode/index.js.map +1 -0
- package/dist-server/service/assistant-mode/registry.d.ts +18 -0
- package/dist-server/service/assistant-mode/registry.js +136 -0
- package/dist-server/service/assistant-mode/registry.js.map +1 -0
- package/dist-server/service/assistant-mode/resolve.d.ts +33 -0
- package/dist-server/service/assistant-mode/resolve.js +60 -0
- package/dist-server/service/assistant-mode/resolve.js.map +1 -0
- package/dist-server/service/assistant-mode/types.d.ts +99 -0
- package/dist-server/service/assistant-mode/types.js +12 -0
- package/dist-server/service/assistant-mode/types.js.map +1 -0
- package/dist-server/service/chat-message/carried-turns.d.ts +48 -0
- package/dist-server/service/chat-message/carried-turns.js +80 -0
- package/dist-server/service/chat-message/carried-turns.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-subscription.d.ts +1 -1
- package/dist-server/service/chat-message/chat-message-subscription.js +24 -6
- package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -1
- package/dist-server/service/chat-message/chat-message.d.ts +32 -0
- package/dist-server/service/chat-message/chat-message.js +17 -0
- package/dist-server/service/chat-message/chat-message.js.map +1 -1
- package/dist-server/service/chat-message/door-access.d.ts +15 -0
- package/dist-server/service/chat-message/door-access.js +26 -0
- package/dist-server/service/chat-message/door-access.js.map +1 -0
- package/dist-server/service/chat-message/fold-session-history.d.ts +30 -0
- package/dist-server/service/chat-message/fold-session-history.js +133 -0
- package/dist-server/service/chat-message/fold-session-history.js.map +1 -0
- package/dist-server/service/chat-message/llm-history.d.ts +7 -0
- package/dist-server/service/chat-message/llm-history.js +23 -11
- package/dist-server/service/chat-message/llm-history.js.map +1 -1
- package/dist-server/service/chat-message/message-doors.d.ts +29 -0
- package/dist-server/service/chat-message/message-doors.js +107 -0
- package/dist-server/service/chat-message/message-doors.js.map +1 -0
- package/dist-server/service/chat-message/proposal-outcome.d.ts +18 -0
- package/dist-server/service/chat-message/proposal-outcome.js +31 -0
- package/dist-server/service/chat-message/proposal-outcome.js.map +1 -0
- package/dist-server/service/chat-message/reader-gate.d.ts +3 -0
- package/dist-server/service/chat-message/reader-gate.js +37 -0
- package/dist-server/service/chat-message/reader-gate.js.map +1 -0
- package/dist-server/service/chat-message/room-message.d.ts +4 -0
- package/dist-server/service/chat-message/room-message.js +25 -0
- package/dist-server/service/chat-message/room-message.js.map +1 -0
- package/dist-server/service/chat-session/chat-session.d.ts +24 -0
- package/dist-server/service/chat-session/chat-session.js +26 -2
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-inbox.d.ts +4 -1
- package/dist-server/service/chat-session/session-inbox.js +3 -2
- package/dist-server/service/chat-session/session-inbox.js.map +1 -1
- package/dist-server/service/chat-session/session-visibility.d.ts +15 -0
- package/dist-server/service/chat-session/session-visibility.js +28 -0
- package/dist-server/service/chat-session/session-visibility.js.map +1 -0
- package/dist-server/service/chat-session/station-session-guard.d.ts +1 -5
- package/dist-server/service/chat-session/station-session-guard.js +7 -2
- package/dist-server/service/chat-session/station-session-guard.js.map +1 -1
- package/dist-server/service/chat-session-participant/mention-invite.d.ts +29 -0
- package/dist-server/service/chat-session-participant/mention-invite.js +103 -0
- package/dist-server/service/chat-session-participant/mention-invite.js.map +1 -0
- package/dist-server/service/chat-session-resolver.d.ts +15 -0
- package/dist-server/service/chat-session-resolver.js +267 -15
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/grounding-sources.d.ts +14 -0
- package/dist-server/service/grounding-sources.js +26 -0
- package/dist-server/service/grounding-sources.js.map +1 -0
- package/dist-server/service/index.d.ts +8 -1
- package/dist-server/service/index.js +19 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/request-translator.d.ts +2 -0
- package/dist-server/service/request-translator.js +20 -0
- package/dist-server/service/request-translator.js.map +1 -0
- package/dist-server/service/testing/conversation-script.d.ts +123 -0
- package/dist-server/service/testing/conversation-script.js +202 -0
- package/dist-server/service/testing/conversation-script.js.map +1 -0
- package/dist-server/service/testing/mode-conformance.d.ts +40 -0
- package/dist-server/service/testing/mode-conformance.js +90 -0
- package/dist-server/service/testing/mode-conformance.js.map +1 -0
- package/dist-server/tool-registry-boot-report.d.ts +3 -0
- package/dist-server/tool-registry-boot-report.js +25 -2
- package/dist-server/tool-registry-boot-report.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/explicit-graphql-types.test.ts +56 -0
- package/server/service/assistant-capability-resolver.ts +66 -0
- package/server/service/assistant-chat-resolver.ts +184 -42
- package/server/service/assistant-mode/assistant-mode-resolver.ts +102 -0
- package/server/service/assistant-mode/index.ts +16 -0
- package/server/service/assistant-mode/registry.test.ts +105 -0
- package/server/service/assistant-mode/registry.ts +147 -0
- package/server/service/assistant-mode/resolve.test.ts +78 -0
- package/server/service/assistant-mode/resolve.ts +96 -0
- package/server/service/assistant-mode/subject-defaults.test.ts +86 -0
- package/server/service/assistant-mode/types.ts +103 -0
- package/server/service/chat-message/carried-turns.test.ts +69 -0
- package/server/service/chat-message/carried-turns.ts +99 -0
- package/server/service/chat-message/chat-message-subscription.ts +20 -4
- package/server/service/chat-message/chat-message.ts +44 -0
- package/server/service/chat-message/door-access.ts +45 -0
- package/server/service/chat-message/fold-session-history.test.ts +98 -0
- package/server/service/chat-message/fold-session-history.ts +115 -0
- package/server/service/chat-message/llm-history.test.ts +58 -7
- package/server/service/chat-message/llm-history.ts +30 -11
- package/server/service/chat-message/message-doors.test.ts +87 -0
- package/server/service/chat-message/message-doors.ts +117 -0
- package/server/service/chat-message/proposal-outcome.test.ts +48 -0
- package/server/service/chat-message/proposal-outcome.ts +42 -0
- package/server/service/chat-message/reader-gate.ts +42 -0
- package/server/service/chat-message/room-message.test.ts +21 -0
- package/server/service/chat-message/room-message.ts +22 -0
- package/server/service/chat-session/chat-session.ts +31 -1
- package/server/service/chat-session/session-inbox.test.ts +18 -0
- package/server/service/chat-session/session-inbox.ts +9 -2
- package/server/service/chat-session/session-visibility.test.ts +36 -0
- package/server/service/chat-session/session-visibility.ts +26 -0
- package/server/service/chat-session/station-session-guard.ts +10 -2
- package/server/service/chat-session-participant/mention-invite.test.ts +125 -0
- package/server/service/chat-session-participant/mention-invite.ts +121 -0
- package/server/service/chat-session-resolver.ts +272 -15
- package/server/service/grounding-sources.test.ts +55 -0
- package/server/service/grounding-sources.ts +46 -0
- package/server/service/index.ts +19 -0
- package/server/service/request-translator.test.ts +45 -0
- package/server/service/request-translator.ts +33 -0
- package/server/service/station-session-guard.test.ts +12 -5
- package/server/service/testing/conversation-scenario.test.ts +163 -0
- package/server/service/testing/conversation-script.test.ts +131 -0
- package/server/service/testing/conversation-script.ts +288 -0
- package/server/service/testing/mode-conformance.ts +107 -0
- package/server/tool-registry-boot-report.ts +25 -2
- package/test/assistant-chat-call-sites.test.ts +82 -0
- package/translations/en.json +44 -2
- package/translations/ja.json +44 -2
- package/translations/ko.json +44 -2
- package/translations/ms.json +44 -2
- package/translations/zh.json +44 -2
|
@@ -12,10 +12,17 @@ import { __decorate, __metadata } from "tslib";
|
|
|
12
12
|
* `mention-popup-helpers.ts` 에 분리 — 단위 테스트 가능.
|
|
13
13
|
*/
|
|
14
14
|
import { LitElement, css, html } from 'lit';
|
|
15
|
+
import { i18next } from '@operato/i18n';
|
|
15
16
|
import { customElement, property } from 'lit/decorators.js';
|
|
16
17
|
export { buildMentionCandidates, buildCatalogCandidates, buildSlashCandidates, buildUserCandidates, extractMentionContext, filterMentionCandidates, applyMentionSelection, resolveMentionToken } from './mention-popup-helpers.js';
|
|
17
|
-
/**
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* 어느 칸에서 맞았는지를 말하는 배지, 그리고 후보의 종류 칩.
|
|
20
|
+
*
|
|
21
|
+
* 영어 낱말을 그대로 박아 두어서 한국어 화면에 `Name` · `template` 두 낱말만 영어로 서 있었다
|
|
22
|
+
* [Twin 레인이 화면에서 봄, 2026-09-18]. 화면에 보이는 말이므로 키로 내고 플랫폼이 옮긴다 —
|
|
23
|
+
* 기본값은 영어다(ADR-0049 ②).
|
|
24
|
+
*/
|
|
25
|
+
const MATCH_TYPE_DEFAULT = {
|
|
19
26
|
id: 'ID',
|
|
20
27
|
name: 'Name',
|
|
21
28
|
tag: 'Tag',
|
|
@@ -26,6 +33,9 @@ const MATCH_TYPE_LABEL = {
|
|
|
26
33
|
property: 'Prop',
|
|
27
34
|
description: 'Desc'
|
|
28
35
|
};
|
|
36
|
+
const matchTypeLabel = (matchType) => i18next.t(`ai-assistant.match.${matchType}`, { defaultValue: MATCH_TYPE_DEFAULT[matchType] ?? '' });
|
|
37
|
+
/** 후보의 종류 — `template`(슬래시 문장) · `user` · 호스트가 낸 카탈로그 종류. */
|
|
38
|
+
const candidateTypeLabel = (type) => i18next.t(`ai-assistant.candidate.${type}`, { defaultValue: type });
|
|
29
39
|
let OxMentionPopup = class OxMentionPopup extends LitElement {
|
|
30
40
|
constructor() {
|
|
31
41
|
super(...arguments);
|
|
@@ -133,7 +143,7 @@ let OxMentionPopup = class OxMentionPopup extends LitElement {
|
|
|
133
143
|
`; }
|
|
134
144
|
render() {
|
|
135
145
|
if (this.results.length === 0) {
|
|
136
|
-
return html `<div class="empty"
|
|
146
|
+
return html `<div class="empty">${i18next.t('ai-assistant.text.no-match', { defaultValue: 'no match' })}</div>`;
|
|
137
147
|
}
|
|
138
148
|
const totalShown = this.results.length;
|
|
139
149
|
const totalAll = totalShown + this.overflow;
|
|
@@ -145,7 +155,7 @@ let OxMentionPopup = class OxMentionPopup extends LitElement {
|
|
|
145
155
|
// - secondary hint = matchValue 가 label 과 다른 경우 (예: tag/text 매칭) 또는
|
|
146
156
|
// user 의 email 같은 부가 정보. 없으면 생략.
|
|
147
157
|
const matchType = r.matchType;
|
|
148
|
-
const badgeText = matchType ?
|
|
158
|
+
const badgeText = matchType ? matchTypeLabel(matchType) : '';
|
|
149
159
|
const label = r.candidate.label;
|
|
150
160
|
// matchValue 가 label 과 같으면 redundant 라 hint 안 함
|
|
151
161
|
const matchHint = r.matchValue && r.matchValue !== label ? r.matchValue : '';
|
|
@@ -153,7 +163,12 @@ let OxMentionPopup = class OxMentionPopup extends LitElement {
|
|
|
153
163
|
const fallbackHint = !matchHint && r.candidate.type === 'user' && r.candidate.email !== label
|
|
154
164
|
? r.candidate.email ?? ''
|
|
155
165
|
: '';
|
|
156
|
-
|
|
166
|
+
/*
|
|
167
|
+
* 설명이 있으면 그것을 곁들인다. 슬래시 문장은 이름만으로는 무엇을 하는지 알 수 없다 —
|
|
168
|
+
* `/bind` 는 「그린 것을 트윈 엔티티에 잇는다」이고, 그 문장이 서버에서 사용자의 언어로
|
|
169
|
+
* 내려오는데 화면이 그리지 않고 있었다.
|
|
170
|
+
*/
|
|
171
|
+
const hint = matchHint || fallbackHint || r.candidate.description || '';
|
|
157
172
|
return html `
|
|
158
173
|
<div
|
|
159
174
|
class="item ${i === this.activeIndex ? 'active' : ''}"
|
|
@@ -162,16 +177,21 @@ let OxMentionPopup = class OxMentionPopup extends LitElement {
|
|
|
162
177
|
<span class="match-badge ${badgeText ? '' : 'invisible'}"
|
|
163
178
|
>${badgeText || '·'}</span
|
|
164
179
|
>
|
|
165
|
-
<span class="type">${r.candidate.type}</span>
|
|
180
|
+
<span class="type">${candidateTypeLabel(r.candidate.type)}</span>
|
|
166
181
|
<span class="label">${label}</span>
|
|
167
182
|
${hint ? html `<span class="hint">${hint}</span>` : ''}
|
|
168
183
|
</div>
|
|
169
184
|
`;
|
|
170
185
|
})}
|
|
171
186
|
<div class="status">
|
|
172
|
-
<span>${totalShown} /
|
|
187
|
+
<span>${i18next.t('ai-assistant.text.match-count', { shown: totalShown, all: totalAll, defaultValue: '{shown} / {all} matches' })}</span>
|
|
173
188
|
${this.overflow > 0
|
|
174
|
-
? html `<span class="more"
|
|
189
|
+
? html `<span class="more"
|
|
190
|
+
>${i18next.t('ai-assistant.text.match-overflow', {
|
|
191
|
+
count: this.overflow,
|
|
192
|
+
defaultValue: '+{count} more — narrow the query'
|
|
193
|
+
})}</span
|
|
194
|
+
>`
|
|
175
195
|
: ''}
|
|
176
196
|
</div>
|
|
177
197
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mention-popup.js","sourceRoot":"","sources":["../../client/components/mention-popup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * <ox-mention-popup> — Mention 후보 리스트 popup overlay.\n *\n * board-ai-chat 의 textarea 위에 absolute 로 띄움. plain `<textarea>` + popup 분리 패턴\n * (한국어 IME 함정 회피 — contenteditable 사용 안 함).\n *\n * 키보드 네비게이션은 호스트 (board-ai-chat) 가 textarea keydown 으로 처리하고\n * activeIndex 만 동기화. 마우스 클릭은 popup 자체에서 처리.\n *\n * Pure helper (extractMentionContext / filterMentionCandidates 등) 는\n * `mention-popup-helpers.ts` 에 분리 — 단위 테스트 가능.\n */\nimport { LitElement, css, html } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport type { FilterResult, MentionCandidate } from './mention-popup-helpers.js'\n\nexport type {\n MentionCandidate,\n FilterResult\n} from './mention-popup-helpers.js'\nexport {\n buildMentionCandidates,\n buildCatalogCandidates,\n buildSlashCandidates,\n buildUserCandidates,\n extractMentionContext,\n filterMentionCandidates,\n applyMentionSelection,\n resolveMentionToken,\n type CatalogEntry,\n type SlashTemplate,\n type ResolveMentionTokenOptions\n} from './mention-popup-helpers.js'\n\n/** match type 라벨 — things-scene SearchOverlay 의 MATCH_TYPE_LABELS 와 동일. */\nconst MATCH_TYPE_LABEL: Record<string, string> = {\n id: 'ID',\n name: 'Name',\n tag: 'Tag',\n type: 'Type',\n class: 'Class',\n text: 'Text',\n data: 'Data',\n property: 'Prop',\n description: 'Desc'\n}\n\n@customElement('ox-mention-popup')\nexport class OxMentionPopup extends LitElement {\n @property({ type: Array }) results: FilterResult[] = []\n @property({ type: Number }) activeIndex = 0\n /** filter cap 후 표시 안 된 추가 매칭 수 (\"...and N more\" 표기용) */\n @property({ type: Number }) overflow = 0\n\n static styles = css`\n :host {\n display: block;\n position: absolute;\n z-index: 1000;\n background: var(--md-sys-color-surface, #ffffff);\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n border-radius: 6px;\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.10);\n max-height: 220px;\n overflow-y: auto;\n /* 폭은 호스트가 left/right 로 결정. */\n }\n .empty {\n padding: 8px 10px;\n color: var(--md-sys-color-outline, #94a3b8);\n font-size: 11px;\n }\n /* dense 한 단일 행 — scene Finder 톤. label / hint / type-chip inline. */\n .item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n cursor: pointer;\n font-size: 11.5px;\n line-height: 1.4;\n color: var(--md-sys-color-on-surface, #0f172a);\n border-bottom: 1px solid var(--md-sys-color-outline-variant, #f1f5f9);\n }\n .item:last-child {\n border-bottom: 0;\n }\n .item:hover {\n background: var(--md-sys-color-surface-container, #f8fafc);\n }\n .item.active {\n background: var(--md-sys-color-primary-container, rgba(37, 99, 235, 0.10));\n }\n .match-badge {\n flex-shrink: 0;\n background: var(--md-sys-color-primary-container, rgba(37, 99, 235, 0.15));\n color: var(--md-sys-color-on-primary-container, #1e40af);\n padding: 0 5px;\n border-radius: 3px;\n font-size: 9px;\n font-weight: 600;\n letter-spacing: 0.04em;\n min-width: 32px;\n text-align: center;\n }\n /* badge 가 비어있을 때 (matchType=label) — 자리는 reserve 해서 모든 행 정렬 일치 */\n .match-badge.invisible {\n visibility: hidden;\n }\n .label {\n flex: 1;\n min-width: 0;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--md-sys-color-on-surface, #0f172a);\n }\n .hint {\n font-size: 10px;\n color: var(--md-sys-color-outline, #94a3b8);\n flex-shrink: 0;\n white-space: nowrap;\n max-width: 45%;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .type {\n flex-shrink: 0;\n background: var(--md-sys-color-surface-container, #f1f5f9);\n padding: 0 5px;\n border-radius: 3px;\n font-size: 9.5px;\n color: var(--md-sys-color-outline, #94a3b8);\n letter-spacing: 0.02em;\n }\n /* 상태 footer — 'N matches' 카운터 + cap 초과 안내 */\n .status {\n padding: 3px 8px;\n font-size: 9.5px;\n color: var(--md-sys-color-outline, #94a3b8);\n border-top: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n background: var(--md-sys-color-surface-container-low, #fafbfc);\n display: flex;\n justify-content: space-between;\n gap: 8px;\n }\n .more {\n color: var(--md-sys-color-outline, #94a3b8);\n }\n `\n\n render() {\n if (this.results.length === 0) {\n return html`<div class=\"empty\">매칭 없음</div>`\n }\n const totalShown = this.results.length\n const totalAll = totalShown + this.overflow\n return html`\n ${this.results.map((r, i) => {\n // 행 layout:\n // [matchType-badge] [type-chip] [label] [secondary hint when 의미 있을 때]\n // - label = 항상 사람-친화적 1차 식별 (component=id, user=name, catalog=name 등)\n // - secondary hint = matchValue 가 label 과 다른 경우 (예: tag/text 매칭) 또는\n // user 의 email 같은 부가 정보. 없으면 생략.\n const matchType = r.matchType\n const badgeText = matchType ? MATCH_TYPE_LABEL[matchType] ?? '' : ''\n const label = r.candidate.label\n // matchValue 가 label 과 같으면 redundant 라 hint 안 함\n const matchHint = r.matchValue && r.matchValue !== label ? r.matchValue : ''\n // user 후보이고 hint 가 비어있으면 email 을 hint 로 (보조 식별)\n const fallbackHint =\n !matchHint && r.candidate.type === 'user' && r.candidate.email !== label\n ? r.candidate.email ?? ''\n : ''\n const hint = matchHint || fallbackHint\n return html`\n <div\n class=\"item ${i === this.activeIndex ? 'active' : ''}\"\n @mouseenter=${() => this._setActive(i)}\n @mousedown=${(e: MouseEvent) => this._select(e, r.candidate)}>\n <span class=\"match-badge ${badgeText ? '' : 'invisible'}\"\n >${badgeText || '·'}</span\n >\n <span class=\"type\">${r.candidate.type}</span>\n <span class=\"label\">${label}</span>\n ${hint ? html`<span class=\"hint\">${hint}</span>` : ''}\n </div>\n `\n })}\n <div class=\"status\">\n <span>${totalShown} / ${totalAll} matches</span>\n ${this.overflow > 0\n ? html`<span class=\"more\">+${this.overflow} more — narrow query</span>`\n : ''}\n </div>\n `\n }\n\n private _select(e: MouseEvent, candidate: MentionCandidate) {\n e.preventDefault() // textarea blur 방지\n this.dispatchEvent(\n new CustomEvent('mention-select', {\n detail: { candidate },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /** 마우스 hover 로 active 이동 — host 가 받아 keyboard nav 와 동일하게 처리. */\n private _setActive(index: number) {\n if (index === this.activeIndex) return\n this.dispatchEvent(\n new CustomEvent('mention-active-change', {\n detail: { index },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /** activeIndex 가 바뀌면 해당 항목을 view 로 스크롤 — popup 의 overflow-y:auto\n * 영역에서 키보드 네비 시 active 가 가려지지 않도록. */\n protected updated(changed: Map<string, unknown>) {\n if (changed.has('activeIndex') || changed.has('results')) {\n const items = this.renderRoot.querySelectorAll('.item')\n const active = items[this.activeIndex] as HTMLElement | undefined\n active?.scrollIntoView({ block: 'nearest' })\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ox-mention-popup': OxMentionPopup\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mention-popup.js","sourceRoot":"","sources":["../../client/components/mention-popup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * <ox-mention-popup> — Mention 후보 리스트 popup overlay.\n *\n * board-ai-chat 의 textarea 위에 absolute 로 띄움. plain `<textarea>` + popup 분리 패턴\n * (한국어 IME 함정 회피 — contenteditable 사용 안 함).\n *\n * 키보드 네비게이션은 호스트 (board-ai-chat) 가 textarea keydown 으로 처리하고\n * activeIndex 만 동기화. 마우스 클릭은 popup 자체에서 처리.\n *\n * Pure helper (extractMentionContext / filterMentionCandidates 등) 는\n * `mention-popup-helpers.ts` 에 분리 — 단위 테스트 가능.\n */\nimport { LitElement, css, html } from 'lit'\nimport { i18next } from '@operato/i18n'\nimport { customElement, property } from 'lit/decorators.js'\nimport type { FilterResult, MentionCandidate } from './mention-popup-helpers.js'\n\nexport type {\n MentionCandidate,\n FilterResult\n} from './mention-popup-helpers.js'\nexport {\n buildMentionCandidates,\n buildCatalogCandidates,\n buildSlashCandidates,\n buildUserCandidates,\n extractMentionContext,\n filterMentionCandidates,\n applyMentionSelection,\n resolveMentionToken,\n type CatalogEntry,\n type SlashTemplate,\n type ResolveMentionTokenOptions\n} from './mention-popup-helpers.js'\n\n/**\n * 어느 칸에서 맞았는지를 말하는 배지, 그리고 후보의 종류 칩.\n *\n * 영어 낱말을 그대로 박아 두어서 한국어 화면에 `Name` · `template` 두 낱말만 영어로 서 있었다\n * [Twin 레인이 화면에서 봄, 2026-09-18]. 화면에 보이는 말이므로 키로 내고 플랫폼이 옮긴다 —\n * 기본값은 영어다(ADR-0049 ②).\n */\nconst MATCH_TYPE_DEFAULT: Record<string, string> = {\n id: 'ID',\n name: 'Name',\n tag: 'Tag',\n type: 'Type',\n class: 'Class',\n text: 'Text',\n data: 'Data',\n property: 'Prop',\n description: 'Desc'\n}\n\nconst matchTypeLabel = (matchType: string) =>\n i18next.t(`ai-assistant.match.${matchType}`, { defaultValue: MATCH_TYPE_DEFAULT[matchType] ?? '' })\n\n/** 후보의 종류 — `template`(슬래시 문장) · `user` · 호스트가 낸 카탈로그 종류. */\nconst candidateTypeLabel = (type: string) => i18next.t(`ai-assistant.candidate.${type}`, { defaultValue: type })\n\n@customElement('ox-mention-popup')\nexport class OxMentionPopup extends LitElement {\n @property({ type: Array }) results: FilterResult[] = []\n @property({ type: Number }) activeIndex = 0\n /** filter cap 후 표시 안 된 추가 매칭 수 (\"...and N more\" 표기용) */\n @property({ type: Number }) overflow = 0\n\n static styles = css`\n :host {\n display: block;\n position: absolute;\n z-index: 1000;\n background: var(--md-sys-color-surface, #ffffff);\n border: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n border-radius: 6px;\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.10);\n max-height: 220px;\n overflow-y: auto;\n /* 폭은 호스트가 left/right 로 결정. */\n }\n .empty {\n padding: 8px 10px;\n color: var(--md-sys-color-outline, #94a3b8);\n font-size: 11px;\n }\n /* dense 한 단일 행 — scene Finder 톤. label / hint / type-chip inline. */\n .item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n cursor: pointer;\n font-size: 11.5px;\n line-height: 1.4;\n color: var(--md-sys-color-on-surface, #0f172a);\n border-bottom: 1px solid var(--md-sys-color-outline-variant, #f1f5f9);\n }\n .item:last-child {\n border-bottom: 0;\n }\n .item:hover {\n background: var(--md-sys-color-surface-container, #f8fafc);\n }\n .item.active {\n background: var(--md-sys-color-primary-container, rgba(37, 99, 235, 0.10));\n }\n .match-badge {\n flex-shrink: 0;\n background: var(--md-sys-color-primary-container, rgba(37, 99, 235, 0.15));\n color: var(--md-sys-color-on-primary-container, #1e40af);\n padding: 0 5px;\n border-radius: 3px;\n font-size: 9px;\n font-weight: 600;\n letter-spacing: 0.04em;\n min-width: 32px;\n text-align: center;\n }\n /* badge 가 비어있을 때 (matchType=label) — 자리는 reserve 해서 모든 행 정렬 일치 */\n .match-badge.invisible {\n visibility: hidden;\n }\n .label {\n flex: 1;\n min-width: 0;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--md-sys-color-on-surface, #0f172a);\n }\n .hint {\n font-size: 10px;\n color: var(--md-sys-color-outline, #94a3b8);\n flex-shrink: 0;\n white-space: nowrap;\n max-width: 45%;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .type {\n flex-shrink: 0;\n background: var(--md-sys-color-surface-container, #f1f5f9);\n padding: 0 5px;\n border-radius: 3px;\n font-size: 9.5px;\n color: var(--md-sys-color-outline, #94a3b8);\n letter-spacing: 0.02em;\n }\n /* 상태 footer — 'N matches' 카운터 + cap 초과 안내 */\n .status {\n padding: 3px 8px;\n font-size: 9.5px;\n color: var(--md-sys-color-outline, #94a3b8);\n border-top: 1px solid var(--md-sys-color-outline-variant, #e2e8f0);\n background: var(--md-sys-color-surface-container-low, #fafbfc);\n display: flex;\n justify-content: space-between;\n gap: 8px;\n }\n .more {\n color: var(--md-sys-color-outline, #94a3b8);\n }\n `\n\n render() {\n if (this.results.length === 0) {\n return html`<div class=\"empty\">${i18next.t('ai-assistant.text.no-match', { defaultValue: 'no match' })}</div>`\n }\n const totalShown = this.results.length\n const totalAll = totalShown + this.overflow\n return html`\n ${this.results.map((r, i) => {\n // 행 layout:\n // [matchType-badge] [type-chip] [label] [secondary hint when 의미 있을 때]\n // - label = 항상 사람-친화적 1차 식별 (component=id, user=name, catalog=name 등)\n // - secondary hint = matchValue 가 label 과 다른 경우 (예: tag/text 매칭) 또는\n // user 의 email 같은 부가 정보. 없으면 생략.\n const matchType = r.matchType\n const badgeText = matchType ? matchTypeLabel(matchType) : ''\n const label = r.candidate.label\n // matchValue 가 label 과 같으면 redundant 라 hint 안 함\n const matchHint = r.matchValue && r.matchValue !== label ? r.matchValue : ''\n // user 후보이고 hint 가 비어있으면 email 을 hint 로 (보조 식별)\n const fallbackHint =\n !matchHint && r.candidate.type === 'user' && r.candidate.email !== label\n ? r.candidate.email ?? ''\n : ''\n /*\n * 설명이 있으면 그것을 곁들인다. 슬래시 문장은 이름만으로는 무엇을 하는지 알 수 없다 —\n * `/bind` 는 「그린 것을 트윈 엔티티에 잇는다」이고, 그 문장이 서버에서 사용자의 언어로\n * 내려오는데 화면이 그리지 않고 있었다.\n */\n const hint = matchHint || fallbackHint || r.candidate.description || ''\n return html`\n <div\n class=\"item ${i === this.activeIndex ? 'active' : ''}\"\n @mouseenter=${() => this._setActive(i)}\n @mousedown=${(e: MouseEvent) => this._select(e, r.candidate)}>\n <span class=\"match-badge ${badgeText ? '' : 'invisible'}\"\n >${badgeText || '·'}</span\n >\n <span class=\"type\">${candidateTypeLabel(r.candidate.type)}</span>\n <span class=\"label\">${label}</span>\n ${hint ? html`<span class=\"hint\">${hint}</span>` : ''}\n </div>\n `\n })}\n <div class=\"status\">\n <span>${i18next.t('ai-assistant.text.match-count', { shown: totalShown, all: totalAll, defaultValue: '{shown} / {all} matches' })}</span>\n ${this.overflow > 0\n ? html`<span class=\"more\"\n >${i18next.t('ai-assistant.text.match-overflow', {\n count: this.overflow,\n defaultValue: '+{count} more — narrow the query'\n })}</span\n >`\n : ''}\n </div>\n `\n }\n\n private _select(e: MouseEvent, candidate: MentionCandidate) {\n e.preventDefault() // textarea blur 방지\n this.dispatchEvent(\n new CustomEvent('mention-select', {\n detail: { candidate },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /** 마우스 hover 로 active 이동 — host 가 받아 keyboard nav 와 동일하게 처리. */\n private _setActive(index: number) {\n if (index === this.activeIndex) return\n this.dispatchEvent(\n new CustomEvent('mention-active-change', {\n detail: { index },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /** activeIndex 가 바뀌면 해당 항목을 view 로 스크롤 — popup 의 overflow-y:auto\n * 영역에서 키보드 네비 시 active 가 가려지지 않도록. */\n protected updated(changed: Map<string, unknown>) {\n if (changed.has('activeIndex') || changed.has('results')) {\n const items = this.renderRoot.querySelectorAll('.item')\n const active = items[this.activeIndex] as HTMLElement | undefined\n active?.scrollIntoView({ block: 'nearest' })\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ox-mention-popup': OxMentionPopup\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 패널 안에 고르는 축이 둘일 때 — 둘 다 「탭」이라고 말하지 않는다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 이 시험이 있나 ──────────────────────────────────────────────────────
|
|
5
|
+
* 대화 고르개를 칩 줄로 만들면서 `role="tablist"` 를 달았다. 바로 아래가 세션 탭이고 그쪽도 tablist
|
|
6
|
+
* 라서, 화면 낭독기로 들으면 「탭 목록, 대화」 다음에 「탭 목록, Conversation history」 가 이어졌다.
|
|
7
|
+
* 고르는 축이 둘인데 둘 다 같은 이름으로 불린다. 눈으로 보면 멀쩡해서 화면에서는 안 잡힌다 —
|
|
8
|
+
* UX 레인이 소스를 읽고 잡았다(2026-09-18).
|
|
9
|
+
*
|
|
10
|
+
* 탭 축은 세션 toolbar(`assistant-session-toolbar.ts`)가 갖는다. 이 패널은 자기 탭 축을 세우지 않는다.
|
|
11
|
+
* ARIA 탭은 좌우 화살표 이동과 `tabpanel` 이 함께 있어야 성립하는데, 여기에는 둘 다 없다.
|
|
12
|
+
*
|
|
13
|
+
* 주석은 벗겨 낸다 — 위 문단에 적힌 `role="tablist"` 가 검사를 통과시키면 그 검사는 자기 산문을 센다.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
function bare(name) {
|
|
18
|
+
return readFileSync(join(__dirname, name), 'utf-8')
|
|
19
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
20
|
+
.replace(/^\s*\/\/.*$/gm, '');
|
|
21
|
+
}
|
|
22
|
+
describe('the panel does not raise a second tab axis', () => {
|
|
23
|
+
it('★ assistant-chat 는 tablist/tab 을 선언하지 않는다 — 그 축은 세션 toolbar의 것이다', () => {
|
|
24
|
+
const found = [...bare('assistant-chat.ts').matchAll(/role="(tablist|tab)"/g)].map(m => m[1]);
|
|
25
|
+
expect(found).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
it('세션 toolbar는 그 축을 계속 갖는다 — 위 시험이 축을 없애라는 뜻으로 읽히지 않도록', () => {
|
|
28
|
+
expect(bare('assistant-session-toolbar.ts')).toContain('role="tablist"');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=panel-aria.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panel-aria.test.js","sourceRoot":"","sources":["../../client/components/panel-aria.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC;SAChD,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAA;AACjC,CAAC;AAED,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAE7F,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC3B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAA;IAC1E,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["/*\n * 패널 안에 고르는 축이 둘일 때 — 둘 다 「탭」이라고 말하지 않는다.\n *\n * ── 왜 이 시험이 있나 ──────────────────────────────────────────────────────\n * 대화 고르개를 칩 줄로 만들면서 `role=\"tablist\"` 를 달았다. 바로 아래가 세션 탭이고 그쪽도 tablist\n * 라서, 화면 낭독기로 들으면 「탭 목록, 대화」 다음에 「탭 목록, Conversation history」 가 이어졌다.\n * 고르는 축이 둘인데 둘 다 같은 이름으로 불린다. 눈으로 보면 멀쩡해서 화면에서는 안 잡힌다 —\n * UX 레인이 소스를 읽고 잡았다(2026-09-18).\n *\n * 탭 축은 세션 toolbar(`assistant-session-toolbar.ts`)가 갖는다. 이 패널은 자기 탭 축을 세우지 않는다.\n * ARIA 탭은 좌우 화살표 이동과 `tabpanel` 이 함께 있어야 성립하는데, 여기에는 둘 다 없다.\n *\n * 주석은 벗겨 낸다 — 위 문단에 적힌 `role=\"tablist\"` 가 검사를 통과시키면 그 검사는 자기 산문을 센다.\n */\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nfunction bare(name: string): string {\n return readFileSync(join(__dirname, name), 'utf-8')\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/^\\s*\\/\\/.*$/gm, '')\n}\n\ndescribe('the panel does not raise a second tab axis', () => {\n it('★ assistant-chat 는 tablist/tab 을 선언하지 않는다 — 그 축은 세션 toolbar의 것이다', () => {\n const found = [...bare('assistant-chat.ts').matchAll(/role=\"(tablist|tab)\"/g)].map(m => m[1])\n\n expect(found).toEqual([])\n })\n\n it('세션 toolbar는 그 축을 계속 갖는다 — 위 시험이 축을 없애라는 뜻으로 읽히지 않도록', () => {\n expect(bare('assistant-session-toolbar.ts')).toContain('role=\"tablist\"')\n })\n})\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ParsedProposalOutcome {
|
|
2
|
+
key: string;
|
|
3
|
+
verdict: 'accepted' | 'refused' | 'unavailable' | 'declined';
|
|
4
|
+
errorCode?: string;
|
|
5
|
+
applied?: number;
|
|
6
|
+
total?: number;
|
|
7
|
+
}
|
|
8
|
+
/** Undefined when the row is any other system line — this must not guess. */
|
|
9
|
+
export declare function parseProposalOutcomeLine(content: string): ParsedProposalOutcome | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const LINE = /^proposal (.+): (accepted|refused|unavailable|declined)(?: \(([^)]*)\))?(?: (\d+)\/(\d+))?$/;
|
|
2
|
+
/** Undefined when the row is any other system line — this must not guess. */
|
|
3
|
+
export function parseProposalOutcomeLine(content) {
|
|
4
|
+
const matched = LINE.exec((content ?? '').trim());
|
|
5
|
+
if (!matched)
|
|
6
|
+
return undefined;
|
|
7
|
+
return {
|
|
8
|
+
key: matched[1],
|
|
9
|
+
verdict: matched[2],
|
|
10
|
+
errorCode: matched[3] || undefined,
|
|
11
|
+
applied: matched[4] === undefined ? undefined : Number(matched[4]),
|
|
12
|
+
total: matched[5] === undefined ? undefined : Number(matched[5])
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=proposal-outcome-line.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposal-outcome-line.js","sourceRoot":"","sources":["../../client/components/proposal-outcome-line.ts"],"names":[],"mappings":"AAkBA,MAAM,IAAI,GAAG,6FAA6F,CAAA;AAE1G,6EAA6E;AAC7E,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IACjD,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAA;IAE9B,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;QACf,OAAO,EAAE,OAAO,CAAC,CAAC,CAAqC;QACvD,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,SAAS;QAClC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAClE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;KACjE,CAAA;AACH,CAAC","sourcesContent":["/*\n * Reading back what the host did with a proposal (ADR-0070 A12-a).\n *\n * The outcome arrives as a `system` row in the conversation — broadcast while the panel is open,\n * and part of the history after a refresh. The panel reads the card's state from it, so a card\n * shows what happened rather than what was pressed.\n *\n * The shape is the server's (`server/service/chat-message/proposal-outcome.ts`). This parser is\n * tested against that formatter, so the two cannot drift apart without a test saying so.\n */\nexport interface ParsedProposalOutcome {\n key: string\n verdict: 'accepted' | 'refused' | 'unavailable' | 'declined'\n errorCode?: string\n applied?: number\n total?: number\n}\n\nconst LINE = /^proposal (.+): (accepted|refused|unavailable|declined)(?: \\(([^)]*)\\))?(?: (\\d+)\\/(\\d+))?$/\n\n/** Undefined when the row is any other system line — this must not guess. */\nexport function parseProposalOutcomeLine(content: string): ParsedProposalOutcome | undefined {\n const matched = LINE.exec((content ?? '').trim())\n if (!matched) return undefined\n\n return {\n key: matched[1],\n verdict: matched[2] as ParsedProposalOutcome['verdict'],\n errorCode: matched[3] || undefined,\n applied: matched[4] === undefined ? undefined : Number(matched[4]),\n total: matched[5] === undefined ? undefined : Number(matched[5])\n }\n}\n"]}
|