@prettier-ai/dsh-client-ui-user-questions 0.1.2-alpha.1

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/lib/client.js ADDED
@@ -0,0 +1,891 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@prettier-ai/dsh-client-ui-user-questions",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _prettier_ai_dsh_client_store = require("@prettier-ai/dsh-client-store");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ let react = require("react");
10
+ let _prettier_ai_dsh_client_ui_primitives = require("@prettier-ai/dsh-client-ui-primitives");
11
+ //#region lib/types/client/contract/slots.js
12
+ function settlePendingComposer(settle, failureMessage) {
13
+ try {
14
+ settle();
15
+ return Promise.resolve();
16
+ } catch (error) {
17
+ return Promise.reject(error instanceof Error ? error : new Error(failureMessage, { cause: error }));
18
+ }
19
+ }
20
+ /**
21
+ * Narrow a request to a renderable plan review, or return undefined to leave it
22
+ * to the generic question flow.
23
+ *
24
+ * The card is one decision over one plan, and it claims a request only when it
25
+ * can send every answer that request allows — an intent changes the layout,
26
+ * never which answers are reachable. So the batch must be a single question
27
+ * that declares the intent, carries the plan as its detail, offers the approve
28
+ * label the intent names, and is a binary single choice: at most one option
29
+ * besides approve, and not multi-select. A third option or a multi-select batch
30
+ * has answers two buttons cannot express, so the generic flow keeps it — as it
31
+ * keeps any request whose intent the asker's own service would have rejected,
32
+ * because the client sits downstream of a wire boundary and every request must
33
+ * stay answerable.
34
+ *
35
+ * @param questions - the request's whole question batch.
36
+ * @returns The narrowed review, or undefined when the generic flow owns it.
37
+ */
38
+ function planReviewOf(questions) {
39
+ if (questions.length !== 1) return void 0;
40
+ const question = questions[0];
41
+ const intent = question.intent;
42
+ if (intent?.kind !== "plan-review" || question.detail === void 0) return void 0;
43
+ if (question.multiSelect === true) return void 0;
44
+ const options = question.options ?? [];
45
+ if (options.length > 2) return void 0;
46
+ const approve = options.find((option) => option.label === intent.approve);
47
+ if (approve === void 0) return void 0;
48
+ const decline = options.find((option) => option.label !== intent.approve);
49
+ return {
50
+ id: question.id,
51
+ question: question.question,
52
+ plan: question.detail,
53
+ approve,
54
+ ...decline === void 0 ? {} : { decline }
55
+ };
56
+ }
57
+ let nextQuestionKey = 0;
58
+ /** Create a wire-preserved user-question rejection. */
59
+ function questionError(message, code) {
60
+ const error = new Error(message);
61
+ error.name = "UserQuestionError";
62
+ error.code = code;
63
+ return error;
64
+ }
65
+ /** One answerable Client presentation of a pending Host waterfall. */
66
+ var PendingQuestion = class {
67
+ sessionId;
68
+ /** Presentation discriminator used by Session pending-interaction consumers. */
69
+ kind;
70
+ /** Opaque render identity and request key for the Session-scoped draft store. */
71
+ key;
72
+ /** The request's question list. */
73
+ questions;
74
+ /** Result returned by the Remote Event listener to the Host waterfall. */
75
+ result;
76
+ #resolve;
77
+ #reject;
78
+ #signal;
79
+ #onAbort;
80
+ #delegated = Symbol("pending question delegated");
81
+ #settled = false;
82
+ /**
83
+ * @param sessionId - Agent/Session identity owning the scoped request.
84
+ * @param questions - complete question batch.
85
+ * @param signal - Host request and delivery lifetime.
86
+ */
87
+ constructor(sessionId, questions, signal) {
88
+ this.sessionId = sessionId;
89
+ nextQuestionKey += 1;
90
+ this.key = `question:${String(nextQuestionKey)}`;
91
+ this.questions = questions;
92
+ this.kind = planReviewOf(questions) === void 0 ? "question" : "plan-review";
93
+ const completion = Promise.withResolvers();
94
+ this.result = completion.promise;
95
+ this.#resolve = completion.resolve;
96
+ this.#reject = completion.reject;
97
+ this.#signal = signal;
98
+ if (signal === void 0) {
99
+ this.#onAbort = void 0;
100
+ return;
101
+ }
102
+ const onAbort = () => {
103
+ this.abort(questionError("ask_user_question was aborted before the user answered", "ASK_ABORTED"));
104
+ };
105
+ this.#onAbort = onAbort;
106
+ signal.addEventListener("abort", onAbort, { once: true });
107
+ if (signal.aborted) onAbort();
108
+ }
109
+ /**
110
+ * Resolve the Host waterfall with the whole answer batch.
111
+ * @param answer - complete structured answer batch.
112
+ */
113
+ answer(answer) {
114
+ return settlePendingComposer(() => {
115
+ this.finish(() => {
116
+ this.#resolve(answer);
117
+ });
118
+ }, "pending question settlement failed");
119
+ }
120
+ /** Delegate an unanswered request to the next waterfall listener. */
121
+ delegate() {
122
+ if (this.#settled) return;
123
+ this.finish(() => {
124
+ this.#reject(this.#delegated);
125
+ });
126
+ }
127
+ /**
128
+ * Test whether a rejection requests waterfall delegation.
129
+ * @param reason - rejection received from {@link PendingQuestion.result}.
130
+ * @returns whether {@link PendingQuestion.delegate} produced it.
131
+ */
132
+ isDelegation(reason) {
133
+ return reason === this.#delegated;
134
+ }
135
+ /** Reject the Host waterfall because the user closed the question. */
136
+ cancel() {
137
+ return settlePendingComposer(() => {
138
+ this.finish(() => {
139
+ this.#reject(questionError("the user cancelled ask_user_question", "ASK_CANCELLED"));
140
+ });
141
+ }, "pending question cancellation failed");
142
+ }
143
+ /**
144
+ * End an unanswered presentation when its transport, scope, or plugin lifetime ends.
145
+ * @param reason - rejection exposed to the waiting Remote Event listener.
146
+ */
147
+ abort(reason) {
148
+ if (this.#settled) return;
149
+ this.finish(() => {
150
+ this.#reject(reason);
151
+ });
152
+ }
153
+ finish(settle) {
154
+ if (this.#settled) throw new Error(`pending question ${this.key} is already settled`);
155
+ this.#settled = true;
156
+ if (this.#signal !== void 0 && this.#onAbort !== void 0) this.#signal.removeEventListener("abort", this.#onAbort);
157
+ settle();
158
+ }
159
+ };
160
+ //#endregion
161
+ //#region lib/types/client/draft-store.js
162
+ /**
163
+ * Session-scoped draft state for the generic question composer. The Slot
164
+ * registry owns store instances; this module exports only the factory so a
165
+ * plugin reload cannot reuse a module-global handle.
166
+ */
167
+ const emptyProgress = () => ({
168
+ index: 0,
169
+ drafts: []
170
+ });
171
+ /**
172
+ * Declare the question composer's transient Session store.
173
+ * @returns a non-persisted store handle whose instance is owned by the Slot registry.
174
+ */
175
+ function createQuestionDraftStore() {
176
+ return (0, _prettier_ai_dsh_client_store.defineStore)({
177
+ init: () => ({ progress: emptyProgress() }),
178
+ actions: {
179
+ replace: (draft, requestKey, progress) => {
180
+ draft.requestKey = requestKey;
181
+ draft.progress = progress;
182
+ },
183
+ clear: (draft, requestKey) => {
184
+ if (draft.requestKey !== requestKey) return;
185
+ delete draft.requestKey;
186
+ draft.progress = emptyProgress();
187
+ }
188
+ }
189
+ });
190
+ }
191
+ //#endregion
192
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
193
+ function r(e) {
194
+ var t, f, n = "";
195
+ if ("string" == typeof e || "number" == typeof e) n += e;
196
+ else if ("object" == typeof e) if (Array.isArray(e)) {
197
+ var o = e.length;
198
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
199
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
200
+ return n;
201
+ }
202
+ function clsx() {
203
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
204
+ return n;
205
+ }
206
+ //#endregion
207
+ //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-user-questions/src/client/PlanReviewPanel.module.css.mjs
208
+ const css$1 = ".bnoQSG_frame{padding:6px calc(var(--dsh-composer-side-clearance) + 16px) 10px;justify-content:center;display:flex}.bnoQSG_card{width:100%;max-width:var(--dsh-chat-content-width);border:1px solid var(--dsw-alias-state-warn-secondary);background:var(--dsw-specific-input-major);max-height:min(60vh,520px);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:20px;flex-direction:column;display:flex;overflow:hidden}.bnoQSG_card,.bnoQSG_card *{box-sizing:border-box}.bnoQSG_strip{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-primary);flex-shrink:0;align-items:center;gap:8px;padding:10px 16px;font-size:13px;line-height:18px;display:flex}.bnoQSG_dot{background:var(--dsw-alias-state-warn-primary);border-radius:50%;width:8px;height:8px}.bnoQSG_body{overscroll-behavior:contain;flex:auto;min-height:0;padding:12px 16px 4px;font-size:14px;line-height:22px;overflow-y:auto}.bnoQSG_footer{flex-shrink:0;justify-content:space-between;align-items:center;gap:12px;padding:8px 16px 12px;display:flex}.bnoQSG_feedback{min-height:16px;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px}.bnoQSG_actions{flex-shrink:0;align-items:center;gap:8px;display:flex}.bnoQSG_discuss{color:var(--dsw-alias-label-secondary);gap:6px}.bnoQSG_discuss:hover:not(:disabled){color:var(--dsw-alias-label-primary)}@media (width<=720px){.bnoQSG_card{border-radius:16px}.bnoQSG_body{padding:10px 12px 4px}.bnoQSG_footer{align-items:flex-end;padding:8px 12px 10px}}";
209
+ const tagId$1 = "@prettier-ai/dsh-client-ui-user-questions/PlanReviewPanel.module.css";
210
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
211
+ const tag = document.createElement("style");
212
+ tag.dataset.plugin = "@prettier-ai/dsh-client-ui-user-questions";
213
+ tag.dataset.pluginCss = tagId$1;
214
+ tag.textContent = css$1;
215
+ document.head.appendChild(tag);
216
+ }
217
+ var PlanReviewPanel_module_css_default = {
218
+ "actions": "bnoQSG_actions",
219
+ "body": "bnoQSG_body",
220
+ "card": "bnoQSG_card",
221
+ "discuss": "bnoQSG_discuss",
222
+ "dot": "bnoQSG_dot",
223
+ "feedback": "bnoQSG_feedback",
224
+ "footer": "bnoQSG_footer",
225
+ "frame": "bnoQSG_frame",
226
+ "strip": "bnoQSG_strip"
227
+ };
228
+ //#endregion
229
+ //#region lib/types/client/PlanReviewPanel.js
230
+ /**
231
+ * Optional-prop spread for a decision button's tooltip: `title` is optional on
232
+ * the DOM props, and exactOptionalPropertyTypes rejects an explicit undefined.
233
+ *
234
+ * @param description - the asker's option description, when it carries one.
235
+ * @returns The `title` prop to spread, or nothing.
236
+ */
237
+ function tooltip(description) {
238
+ return description === void 0 ? {} : { title: description };
239
+ }
240
+ /**
241
+ * Render a plan review as a decision card.
242
+ *
243
+ * @param props - the question domain face, the narrowed plan review, and `t`.
244
+ * @returns The plan-review takeover for this request.
245
+ */
246
+ function PlanReviewPanel({ pending, review, t }) {
247
+ const markdownLabels = (0, react.useMemo)(() => ({
248
+ code: {
249
+ copyLabel: t("copy"),
250
+ copiedLabel: t("copied")
251
+ },
252
+ footnotes: t("markdown.footnotes")
253
+ }), [t]);
254
+ const [busy, setBusy] = (0, react.useState)(false);
255
+ const [error, setError] = (0, react.useState)(null);
256
+ const settle = (send) => {
257
+ setBusy(true);
258
+ setError(null);
259
+ send().catch((cause) => {
260
+ setBusy(false);
261
+ setError(cause instanceof Error ? cause.message : String(cause));
262
+ });
263
+ };
264
+ const decide = (label) => {
265
+ settle(() => pending.answer({ answers: [{
266
+ id: review.id,
267
+ selected: [label]
268
+ }] }));
269
+ };
270
+ const decline = review.decline;
271
+ return (0, react_jsx_runtime.jsx)("div", {
272
+ className: PlanReviewPanel_module_css_default.frame,
273
+ "data-plan-review-key": pending.key,
274
+ children: (0, react_jsx_runtime.jsxs)("section", {
275
+ className: PlanReviewPanel_module_css_default.card,
276
+ "aria-label": review.question,
277
+ children: [
278
+ (0, react_jsx_runtime.jsxs)("div", {
279
+ className: PlanReviewPanel_module_css_default.strip,
280
+ children: [(0, react_jsx_runtime.jsx)("span", { className: PlanReviewPanel_module_css_default.dot }), t("plan.header")]
281
+ }),
282
+ (0, react_jsx_runtime.jsx)("div", {
283
+ className: PlanReviewPanel_module_css_default.body,
284
+ "data-plan-review-scroll": true,
285
+ children: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.MarkdownText, {
286
+ text: review.plan,
287
+ labels: markdownLabels
288
+ })
289
+ }),
290
+ (0, react_jsx_runtime.jsxs)("div", {
291
+ className: PlanReviewPanel_module_css_default.footer,
292
+ children: [(0, react_jsx_runtime.jsx)("div", {
293
+ className: PlanReviewPanel_module_css_default.feedback,
294
+ role: "status",
295
+ children: error
296
+ }), (0, react_jsx_runtime.jsxs)("div", {
297
+ className: PlanReviewPanel_module_css_default.actions,
298
+ children: [
299
+ (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.Button, {
300
+ variant: "ghost",
301
+ className: PlanReviewPanel_module_css_default.discuss,
302
+ icon: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
303
+ disabled: busy,
304
+ onClick: () => {
305
+ settle(() => pending.cancel());
306
+ },
307
+ children: t("plan.discuss")
308
+ }),
309
+ decline !== void 0 && (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.Button, {
310
+ variant: "outline",
311
+ ...tooltip(decline.description),
312
+ disabled: busy,
313
+ onClick: () => {
314
+ decide(decline.label);
315
+ },
316
+ children: t("plan.decline")
317
+ }),
318
+ (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.Button, {
319
+ variant: "primary",
320
+ ...tooltip(review.approve.description),
321
+ disabled: busy,
322
+ onClick: () => {
323
+ decide(review.approve.label);
324
+ },
325
+ children: t("plan.approve")
326
+ })
327
+ ]
328
+ })]
329
+ })
330
+ ]
331
+ })
332
+ });
333
+ }
334
+ //#endregion
335
+ //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-user-questions/src/client/QuestionComposer.module.css.mjs
336
+ const css = ".DM9ViG_frame{padding:6px calc(var(--dsh-composer-side-clearance) + 16px) 10px;justify-content:center;display:flex}.DM9ViG_card{width:100%;max-width:var(--dsh-chat-content-width);border:1px solid var(--dsw-alias-border-l2-darkmode-thin);background:var(--dsw-specific-input-major);max-height:min(60vh,520px);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:20px;flex-direction:column;padding:0 0 10px;display:flex;overflow:hidden}.DM9ViG_card,.DM9ViG_card *{box-sizing:border-box}.DM9ViG_cardMinimized{max-height:none}.DM9ViG_cardMinimized .DM9ViG_header{padding-bottom:14px}.DM9ViG_headerActions{flex-shrink:0;align-items:center;gap:4px;display:flex}.DM9ViG_header{flex-shrink:0;justify-content:space-between;align-items:flex-start;gap:16px;padding:20px 16px 0 24px;display:flex}.DM9ViG_headingBlock{min-width:0}.DM9ViG_eyebrow{color:var(--dsw-alias-label-tertiary);margin-bottom:5px;font-size:11px;line-height:16px}.DM9ViG_title{margin:0;font-size:16px;font-weight:500;line-height:22px}.DM9ViG_detail{margin:0 2px 8px}.DM9ViG_footerActions{flex-shrink:0;align-items:center;gap:12px;display:flex}.DM9ViG_pager{flex-shrink:0;align-items:center;gap:6px;display:flex}.DM9ViG_progress{color:var(--dsw-alias-label-secondary);white-space:nowrap;word-spacing:-2px;padding:0 4px;font-size:14px;font-weight:500;line-height:24px}.DM9ViG_iconButton{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;place-items:center;padding:0;display:grid}.DM9ViG_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.DM9ViG_iconButton:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.DM9ViG_body{overscroll-behavior:contain;flex-direction:column;flex:auto;min-height:0;display:flex;overflow-y:auto}.DM9ViG_options{flex-direction:column;gap:1px;margin:8px 0 0;padding:4px 12px;display:flex}.DM9ViG_option{width:100%;min-height:40px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:12px;flex-shrink:0;align-items:flex-start;gap:8px;padding:8px 12px 8px 8px;transition:background-color .12s,border-color .12s;display:flex}.DM9ViG_option:hover:not(:disabled),.DM9ViG_optionSelected{background:var(--dsw-alias-interactive-bg-hover)}.DM9ViG_optionSelected{border-color:var(--dsw-alias-border-l2)}.DM9ViG_option:disabled{cursor:default}.DM9ViG_number{background:var(--dsw-alias-bg-overlay);width:20px;height:20px;color:var(--dsw-alias-label-secondary);border-radius:6px;flex:0 0 20px;place-items:center;margin-top:2px;font-size:12px;font-weight:500;line-height:18px;display:grid}.DM9ViG_checkbox{flex:0 0 20px;place-items:center;width:20px;height:20px;margin-top:2px;display:grid}.DM9ViG_checkbox:before{content:\"\";border:1px solid var(--dsw-alias-border-l4);border-radius:4px;grid-area:1/1;width:14px;height:14px;transition:background-color .12s,border-color .12s}.DM9ViG_checkbox>svg{grid-area:1/1}.DM9ViG_checkboxChecked{color:var(--dsw-alias-label-primary-foreground)}.DM9ViG_checkboxChecked:before{border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-label-primary)}.DM9ViG_optionCopy{flex:1;min-width:0}.DM9ViG_optionLine{flex-wrap:wrap;align-items:baseline;gap:2px 6px;display:flex}.DM9ViG_optionLabel{font-size:14px;font-weight:500;line-height:24px}.DM9ViG_badge{background:var(--dsw-specific-sidebar-nav-item-active-accent);color:var(--dsw-alias-button-info-fill);border-radius:6px;padding:0 4px;font-size:11px;font-weight:600;line-height:18px}.DM9ViG_description{color:var(--dsw-alias-label-tertiary);font-size:14px;font-weight:400;line-height:24px}.DM9ViG_customRow{border:1px solid #0000;border-radius:12px;flex-shrink:0;align-items:flex-start;gap:8px;width:100%;min-height:40px;padding:8px 12px 8px 8px;transition:background-color .12s,border-color .12s;display:flex}.DM9ViG_customRow:hover,.DM9ViG_customRow:focus-within,.DM9ViG_customRowActive{background:var(--dsw-alias-interactive-bg-hover)}.DM9ViG_customRow:focus-within,.DM9ViG_customRowActive{border-color:var(--dsw-alias-border-l2)}.DM9ViG_field{--dsh-answer-field-padding:0;min-width:0;display:grid}.DM9ViG_field>*{min-width:0;padding:var(--dsh-answer-field-padding);font:inherit;white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere;grid-area:1/1;font-size:14px;line-height:24px}.DM9ViG_fieldMirror{box-sizing:content-box;visibility:hidden;max-height:144px;overflow:hidden}.DM9ViG_fieldInput{resize:none;color:var(--dsw-alias-label-primary);caret-color:var(--dsw-alias-state-business-primary);background:0 0;border:none;outline:none;overflow-y:auto}.DM9ViG_fieldInput::placeholder{color:var(--dsw-alias-label-caption)}.DM9ViG_customInline{flex:1}.DM9ViG_customBlock{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);--dsh-answer-field-padding:8px 12px;border-radius:10px;flex-shrink:0;min-height:64px;margin:0 12px}.DM9ViG_customBlock:focus-within{border-color:var(--dsw-alias-state-business-primary)}.DM9ViG_footer{flex-shrink:0;justify-content:space-between;align-items:center;gap:12px;margin-top:12px;padding:0 10px 0 18px;display:flex}.DM9ViG_feedback{min-height:16px;color:var(--dsw-alias-state-error-primary);text-align:right;flex:1;font-size:11px;line-height:16px}@media (width<=720px){.DM9ViG_card{border-radius:16px}.DM9ViG_header{padding:10px 12px 0 18px}.DM9ViG_options{padding:4px 8px}.DM9ViG_title{font-size:15px;line-height:21px}.DM9ViG_option,.DM9ViG_customRow{padding:8px 6px}.DM9ViG_footer{align-items:flex-end;padding:0 10px}.DM9ViG_footerActions{flex-shrink:0}}@media (prefers-reduced-motion:reduce){.DM9ViG_option,.DM9ViG_customRow{transition:none}}";
337
+ const tagId = "@prettier-ai/dsh-client-ui-user-questions/QuestionComposer.module.css";
338
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
339
+ const tag = document.createElement("style");
340
+ tag.dataset.plugin = "@prettier-ai/dsh-client-ui-user-questions";
341
+ tag.dataset.pluginCss = tagId;
342
+ tag.textContent = css;
343
+ document.head.appendChild(tag);
344
+ }
345
+ var QuestionComposer_module_css_default = {
346
+ "badge": "DM9ViG_badge",
347
+ "body": "DM9ViG_body",
348
+ "card": "DM9ViG_card",
349
+ "cardMinimized": "DM9ViG_cardMinimized",
350
+ "checkbox": "DM9ViG_checkbox",
351
+ "checkboxChecked": "DM9ViG_checkboxChecked",
352
+ "customBlock": "DM9ViG_customBlock",
353
+ "customInline": "DM9ViG_customInline",
354
+ "customRow": "DM9ViG_customRow",
355
+ "customRowActive": "DM9ViG_customRowActive",
356
+ "description": "DM9ViG_description",
357
+ "detail": "DM9ViG_detail",
358
+ "eyebrow": "DM9ViG_eyebrow",
359
+ "feedback": "DM9ViG_feedback",
360
+ "field": "DM9ViG_field",
361
+ "fieldInput": "DM9ViG_fieldInput",
362
+ "fieldMirror": "DM9ViG_fieldMirror",
363
+ "footer": "DM9ViG_footer",
364
+ "footerActions": "DM9ViG_footerActions",
365
+ "frame": "DM9ViG_frame",
366
+ "header": "DM9ViG_header",
367
+ "headerActions": "DM9ViG_headerActions",
368
+ "headingBlock": "DM9ViG_headingBlock",
369
+ "iconButton": "DM9ViG_iconButton",
370
+ "number": "DM9ViG_number",
371
+ "option": "DM9ViG_option",
372
+ "optionCopy": "DM9ViG_optionCopy",
373
+ "optionLabel": "DM9ViG_optionLabel",
374
+ "optionLine": "DM9ViG_optionLine",
375
+ "optionSelected": "DM9ViG_optionSelected",
376
+ "options": "DM9ViG_options",
377
+ "pager": "DM9ViG_pager",
378
+ "progress": "DM9ViG_progress",
379
+ "title": "DM9ViG_title"
380
+ };
381
+ //#endregion
382
+ //#region lib/types/client/QuestionComposer.js
383
+ /**
384
+ * Split the conventional recommendation suffix without changing the answer value.
385
+ * @param label - Original option label returned if selected.
386
+ * @returns Display label plus recommendation state.
387
+ */
388
+ function parseRecommendedLabel(label) {
389
+ const suffix = /\s*(?:\((?:recommended|推荐)\)|((?:recommended|推荐)))\s*$/i;
390
+ return suffix.test(label) ? {
391
+ label: label.replace(suffix, ""),
392
+ recommended: true
393
+ } : {
394
+ label,
395
+ recommended: false
396
+ };
397
+ }
398
+ /** Return whether a text-field key event belongs to an active IME composition. */
399
+ function isComposing(event) {
400
+ return event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
401
+ }
402
+ /**
403
+ * Auto-growing free-text answer: a textarea, so a long answer soft-wraps and
404
+ * Shift+Enter breaks a line, over a hidden mirror that owns the height.
405
+ *
406
+ * The mirror renders the draft plus a trailing newline in normal flow and so
407
+ * sizes the grid row (counting rows by '\n' cannot see soft wraps); the
408
+ * textarea shares that one cell and stretches to it, and `rows={1}` keeps the
409
+ * control's own intrinsic height out of the row sizing so the mirror alone
410
+ * decides. Past the mirror's cap the textarea scrolls itself — it is the only
411
+ * scrollport in the stack, there being no second glyph layer to keep aligned.
412
+ * Mirror and textarea MUST share font, line-height, padding and wrapping rules
413
+ * or the two heights diverge.
414
+ *
415
+ * @param props - visual variant, draft text, and the field's event handlers.
416
+ * @returns The mirrored auto-growing field.
417
+ */
418
+ function AnswerField(props) {
419
+ return (0, react_jsx_runtime.jsxs)("div", {
420
+ className: clsx(QuestionComposer_module_css_default.field, props.variant === "inline" ? QuestionComposer_module_css_default.customInline : QuestionComposer_module_css_default.customBlock),
421
+ children: [(0, react_jsx_runtime.jsx)("div", {
422
+ "aria-hidden": true,
423
+ className: QuestionComposer_module_css_default.fieldMirror,
424
+ children: `${props.value}\n`
425
+ }), (0, react_jsx_runtime.jsx)("textarea", {
426
+ autoFocus: props.autoFocus,
427
+ className: QuestionComposer_module_css_default.fieldInput,
428
+ value: props.value,
429
+ disabled: props.disabled,
430
+ rows: 1,
431
+ placeholder: props.placeholder,
432
+ onFocus: props.onFocus,
433
+ onChange: props.onChange,
434
+ onKeyDown: props.onKeyDown
435
+ })]
436
+ });
437
+ }
438
+ /**
439
+ * Composer takeover router. Generic-question drafts live in this entry's
440
+ * Session-scoped Slot store, keyed by the pending carrier, so a strict Session
441
+ * entry remount restores the same request without exposing it to another one.
442
+ *
443
+ * One takeover, two presentations: a request that declares a presentation intent this
444
+ * package renders uses that presentation (a plan review is one decision over one
445
+ * plan, not a question set), and every other request takes the generic flow.
446
+ * The routing lives here, at the one entry that owns the composer seat, so
447
+ * neither presentation can claim a request the other is already rendering.
448
+ *
449
+ * @param props - the selector-matched pending question carrier plus the framework standard kit.
450
+ * @returns The question flow, or the intent's own surface, for this request.
451
+ */
452
+ function QuestionComposer(props) {
453
+ const question = props.matched;
454
+ const review = (0, react.useMemo)(() => planReviewOf(question.questions), [question]);
455
+ return review === void 0 ? (0, react_jsx_runtime.jsx)(QuestionFlow, {
456
+ pending: question,
457
+ t: props.t,
458
+ useStore: props.useStore,
459
+ actions: props.actions
460
+ }, question.key) : (0, react_jsx_runtime.jsx)(PlanReviewPanel, {
461
+ pending: question,
462
+ review,
463
+ t: props.t
464
+ }, question.key);
465
+ }
466
+ function QuestionFlow({ pending, t, useStore, actions }) {
467
+ const questions = pending.questions;
468
+ const markdownLabels = (0, react.useMemo)(() => ({
469
+ code: {
470
+ copyLabel: t("copy"),
471
+ copiedLabel: t("copied")
472
+ },
473
+ footnotes: t("markdown.footnotes")
474
+ }), [t]);
475
+ const initialProgress = (0, react.useMemo)(() => ({
476
+ index: 0,
477
+ drafts: questions.map(() => ({
478
+ selected: [],
479
+ custom: "",
480
+ skipped: false
481
+ }))
482
+ }), [questions]);
483
+ const { index, drafts } = useStore((state) => state.requestKey === pending.key && state.progress.drafts.length === questions.length ? state.progress : void 0) ?? initialProgress;
484
+ const [busy, setBusy] = (0, react.useState)(null);
485
+ const [error, setError] = (0, react.useState)(null);
486
+ const [minimized, setMinimized] = (0, react.useState)(false);
487
+ const focusedQuestions = (0, react.useRef)(/* @__PURE__ */ new Set());
488
+ const question = questions[index];
489
+ const draft = drafts[index];
490
+ const hasOptions = (question.options?.length ?? 0) > 0;
491
+ const replaceProgress = (nextIndex, nextDrafts) => {
492
+ actions.replace(pending.key, {
493
+ index: nextIndex,
494
+ drafts: nextDrafts
495
+ });
496
+ };
497
+ const cancelFlow = () => {
498
+ setBusy("cancel");
499
+ setError(null);
500
+ pending.cancel().then(() => {
501
+ actions.clear(pending.key);
502
+ }).catch((cause) => {
503
+ setBusy(null);
504
+ setError({ text: cause instanceof Error ? cause.message : String(cause) });
505
+ });
506
+ };
507
+ const updateDraft = (update, nextIndex = index) => {
508
+ replaceProgress(nextIndex, drafts.map((item, itemIndex) => itemIndex === index ? update(item) : item));
509
+ setError(null);
510
+ };
511
+ const choose = (label) => {
512
+ updateDraft((current) => {
513
+ if (question.multiSelect === true) {
514
+ const selected = current.selected.includes(label) ? current.selected.filter((item) => item !== label) : [...current.selected, label];
515
+ return {
516
+ ...current,
517
+ selected,
518
+ skipped: false
519
+ };
520
+ }
521
+ return {
522
+ selected: [label],
523
+ custom: "",
524
+ skipped: false
525
+ };
526
+ }, question.multiSelect !== true && index < questions.length - 1 ? index + 1 : index);
527
+ };
528
+ const answered = (item) => item.selected.length > 0 || item.custom.trim() !== "";
529
+ const completed = (item) => answered(item) || item.skipped;
530
+ const submitDrafts = (values) => {
531
+ const missing = values.findIndex((item) => !completed(item));
532
+ if (missing >= 0) {
533
+ replaceProgress(missing, values);
534
+ setError({ key: "error.incomplete" });
535
+ return;
536
+ }
537
+ const answer = { answers: questions.map((item, itemIndex) => {
538
+ const value = values[itemIndex];
539
+ if (value.skipped) return {
540
+ id: item.id,
541
+ selected: []
542
+ };
543
+ const custom = value.custom.trim();
544
+ return {
545
+ id: item.id,
546
+ selected: custom === "" || item.multiSelect === true ? value.selected : [],
547
+ ...custom === "" ? {} : { custom }
548
+ };
549
+ }) };
550
+ setBusy("answer");
551
+ setError(null);
552
+ pending.answer(answer).then(() => {
553
+ actions.clear(pending.key);
554
+ }).catch((cause) => {
555
+ setBusy(null);
556
+ setError({ text: cause instanceof Error ? cause.message : String(cause) });
557
+ });
558
+ };
559
+ const continueFlow = () => {
560
+ if (!answered(draft)) {
561
+ setError({ key: "error.unanswered" });
562
+ return;
563
+ }
564
+ if (index < questions.length - 1) {
565
+ replaceProgress(index + 1, drafts);
566
+ setError(null);
567
+ return;
568
+ }
569
+ submitDrafts(drafts);
570
+ };
571
+ const draftCustom = (event) => {
572
+ const value = event.target.value;
573
+ updateDraft((current) => ({
574
+ ...current,
575
+ selected: question.multiSelect === true ? current.selected : [],
576
+ custom: value,
577
+ skipped: false
578
+ }));
579
+ };
580
+ const continueFromCustom = (event) => {
581
+ if (event.key !== "Enter" || event.shiftKey || isComposing(event)) return;
582
+ event.preventDefault();
583
+ continueFlow();
584
+ };
585
+ const skipQuestion = () => {
586
+ const nextDrafts = drafts.map((item, itemIndex) => itemIndex === index ? {
587
+ selected: [],
588
+ custom: "",
589
+ skipped: true
590
+ } : item);
591
+ replaceProgress(index < questions.length - 1 ? index + 1 : index, nextDrafts);
592
+ setError(null);
593
+ if (index < questions.length - 1) return;
594
+ submitDrafts(nextDrafts);
595
+ };
596
+ return (0, react_jsx_runtime.jsx)("div", {
597
+ className: QuestionComposer_module_css_default.frame,
598
+ "data-question-key": pending.key,
599
+ children: (0, react_jsx_runtime.jsxs)("section", {
600
+ className: clsx(QuestionComposer_module_css_default.card, minimized && QuestionComposer_module_css_default.cardMinimized),
601
+ "aria-labelledby": `question-${pending.key}-${String(index)}`,
602
+ children: [(0, react_jsx_runtime.jsxs)("header", {
603
+ className: QuestionComposer_module_css_default.header,
604
+ children: [(0, react_jsx_runtime.jsxs)("div", {
605
+ className: QuestionComposer_module_css_default.headingBlock,
606
+ children: [question.header !== void 0 && (0, react_jsx_runtime.jsx)("div", {
607
+ className: QuestionComposer_module_css_default.eyebrow,
608
+ children: question.header
609
+ }), (0, react_jsx_runtime.jsx)("h2", {
610
+ className: QuestionComposer_module_css_default.title,
611
+ id: `question-${pending.key}-${String(index)}`,
612
+ children: question.question
613
+ })]
614
+ }), (0, react_jsx_runtime.jsxs)("div", {
615
+ className: QuestionComposer_module_css_default.headerActions,
616
+ children: [(0, react_jsx_runtime.jsx)("button", {
617
+ type: "button",
618
+ className: QuestionComposer_module_css_default.iconButton,
619
+ "aria-label": t(minimized ? "nav.maximize" : "nav.minimize"),
620
+ title: t(minimized ? "nav.maximize" : "nav.minimize"),
621
+ "aria-expanded": !minimized,
622
+ disabled: busy !== null,
623
+ onClick: () => {
624
+ setMinimized((current) => !current);
625
+ },
626
+ children: minimized ? (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {}) : (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
627
+ }), (0, react_jsx_runtime.jsx)("button", {
628
+ type: "button",
629
+ className: QuestionComposer_module_css_default.iconButton,
630
+ "aria-label": t("nav.cancel"),
631
+ title: t("nav.cancel"),
632
+ disabled: busy !== null,
633
+ onClick: cancelFlow,
634
+ children: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
635
+ })]
636
+ })]
637
+ }), !minimized && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
638
+ className: QuestionComposer_module_css_default.body,
639
+ "data-question-scroll": true,
640
+ children: [question.detail !== void 0 && (0, react_jsx_runtime.jsx)("div", {
641
+ className: QuestionComposer_module_css_default.detail,
642
+ children: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.MarkdownText, {
643
+ text: question.detail,
644
+ labels: markdownLabels
645
+ })
646
+ }), (0, react_jsx_runtime.jsxs)("div", {
647
+ className: QuestionComposer_module_css_default.options,
648
+ role: question.multiSelect === true ? "group" : "radiogroup",
649
+ children: [(question.options ?? []).map((option, optionIndex) => {
650
+ const selected = draft.selected.includes(option.label);
651
+ const display = parseRecommendedLabel(option.label);
652
+ return (0, react_jsx_runtime.jsxs)("button", {
653
+ type: "button",
654
+ className: clsx(QuestionComposer_module_css_default.option, selected && question.multiSelect !== true && QuestionComposer_module_css_default.optionSelected),
655
+ role: question.multiSelect === true ? "checkbox" : "radio",
656
+ "aria-checked": selected,
657
+ "aria-label": display.label,
658
+ disabled: busy !== null,
659
+ onClick: () => {
660
+ choose(option.label);
661
+ },
662
+ onKeyDown: (event) => {
663
+ if (event.key !== "Enter" || !drafts.every(completed)) return;
664
+ event.preventDefault();
665
+ submitDrafts(drafts);
666
+ },
667
+ children: [question.multiSelect === true ? (0, react_jsx_runtime.jsx)("span", {
668
+ className: clsx(QuestionComposer_module_css_default.checkbox, selected && QuestionComposer_module_css_default.checkboxChecked),
669
+ "aria-hidden": "true",
670
+ children: selected && (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconCheckOutline14, { size: 12 })
671
+ }) : (0, react_jsx_runtime.jsx)("span", {
672
+ className: QuestionComposer_module_css_default.number,
673
+ children: optionIndex + 1
674
+ }), (0, react_jsx_runtime.jsx)("span", {
675
+ className: QuestionComposer_module_css_default.optionCopy,
676
+ children: (0, react_jsx_runtime.jsxs)("span", {
677
+ className: QuestionComposer_module_css_default.optionLine,
678
+ children: [
679
+ (0, react_jsx_runtime.jsx)("span", {
680
+ className: QuestionComposer_module_css_default.optionLabel,
681
+ children: display.label
682
+ }),
683
+ display.recommended && (0, react_jsx_runtime.jsx)("span", {
684
+ className: QuestionComposer_module_css_default.badge,
685
+ children: t("option.recommended")
686
+ }),
687
+ option.description !== void 0 && (0, react_jsx_runtime.jsx)("span", {
688
+ className: QuestionComposer_module_css_default.description,
689
+ children: option.description
690
+ })
691
+ ]
692
+ })
693
+ })]
694
+ }, `${option.label}-${String(optionIndex)}`);
695
+ }), hasOptions ? (0, react_jsx_runtime.jsxs)("div", {
696
+ className: clsx(QuestionComposer_module_css_default.customRow, draft.custom !== "" && QuestionComposer_module_css_default.customRowActive),
697
+ children: [question.multiSelect === true ? (0, react_jsx_runtime.jsx)("span", {
698
+ className: clsx(QuestionComposer_module_css_default.checkbox, draft.custom !== "" && QuestionComposer_module_css_default.checkboxChecked),
699
+ "aria-hidden": "true",
700
+ children: draft.custom !== "" && (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconCheckOutline14, { size: 12 })
701
+ }) : (0, react_jsx_runtime.jsx)("span", {
702
+ className: QuestionComposer_module_css_default.number,
703
+ "aria-hidden": "true",
704
+ children: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 12 })
705
+ }), (0, react_jsx_runtime.jsx)(AnswerField, {
706
+ variant: "inline",
707
+ value: draft.custom,
708
+ disabled: busy !== null,
709
+ placeholder: t("custom.placeholder"),
710
+ onChange: draftCustom,
711
+ onKeyDown: continueFromCustom
712
+ })]
713
+ }) : (0, react_jsx_runtime.jsx)(AnswerField, {
714
+ autoFocus: !focusedQuestions.current.has(index),
715
+ variant: "block",
716
+ value: draft.custom,
717
+ disabled: busy !== null,
718
+ placeholder: t("custom.placeholder"),
719
+ onFocus: () => {
720
+ focusedQuestions.current.add(index);
721
+ },
722
+ onChange: draftCustom,
723
+ onKeyDown: continueFromCustom
724
+ })]
725
+ })]
726
+ }), (0, react_jsx_runtime.jsxs)("footer", {
727
+ className: QuestionComposer_module_css_default.footer,
728
+ children: [
729
+ (0, react_jsx_runtime.jsxs)("div", {
730
+ className: QuestionComposer_module_css_default.pager,
731
+ children: [
732
+ (0, react_jsx_runtime.jsx)("button", {
733
+ type: "button",
734
+ className: QuestionComposer_module_css_default.iconButton,
735
+ "aria-label": t("nav.prev"),
736
+ disabled: index === 0 || busy !== null,
737
+ onClick: () => {
738
+ replaceProgress(index - 1, drafts);
739
+ setError(null);
740
+ },
741
+ children: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconChevronLeftOutline14, {})
742
+ }),
743
+ (0, react_jsx_runtime.jsxs)("span", {
744
+ className: QuestionComposer_module_css_default.progress,
745
+ children: [
746
+ index + 1,
747
+ " / ",
748
+ questions.length
749
+ ]
750
+ }),
751
+ (0, react_jsx_runtime.jsx)("button", {
752
+ type: "button",
753
+ className: QuestionComposer_module_css_default.iconButton,
754
+ "aria-label": t("nav.next"),
755
+ disabled: index === questions.length - 1 || busy !== null,
756
+ onClick: () => {
757
+ replaceProgress(index + 1, drafts);
758
+ setError(null);
759
+ },
760
+ children: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
761
+ })
762
+ ]
763
+ }),
764
+ (0, react_jsx_runtime.jsx)("div", {
765
+ className: QuestionComposer_module_css_default.feedback,
766
+ role: "status",
767
+ children: error === null ? null : "key" in error ? t(error.key) : error.text
768
+ }),
769
+ (0, react_jsx_runtime.jsxs)("div", {
770
+ className: QuestionComposer_module_css_default.footerActions,
771
+ children: [(0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.Button, {
772
+ variant: "outline",
773
+ disabled: busy !== null,
774
+ onClick: skipQuestion,
775
+ children: t("action.skip")
776
+ }), (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.Button, {
777
+ variant: "primary",
778
+ disabled: busy !== null || !answered(draft),
779
+ onClick: continueFlow,
780
+ children: busy === "answer" ? t("submitting") : index === questions.length - 1 ? t("submit") : t("action.next")
781
+ })]
782
+ })
783
+ ]
784
+ })] })]
785
+ })
786
+ });
787
+ }
788
+ //#endregion
789
+ //#region lib/types/client/locales.js
790
+ /** `question` namespace dictionaries. */
791
+ /** Simplified Chinese dictionary (the key-set source of truth). */
792
+ const zh = {
793
+ "error.incomplete": "请先完成这道问题。",
794
+ "error.unanswered": "请选择一个选项或填写自定义答案。",
795
+ "nav.prev": "上一题",
796
+ "nav.next": "下一题",
797
+ "nav.minimize": "收起问题卡片",
798
+ "nav.maximize": "展开问题卡片",
799
+ "nav.cancel": "放弃整组问题",
800
+ "option.recommended": "推荐",
801
+ "custom.placeholder": "输入你的答案",
802
+ "action.skip": "跳过本题",
803
+ "action.next": "下一题",
804
+ "plan.header": "计划待审",
805
+ "plan.approve": "确认执行",
806
+ "plan.decline": "拒绝",
807
+ "plan.discuss": "去聊天里说"
808
+ };
809
+ /** English dictionary, checked complete against the zh key set. */
810
+ const en = {
811
+ "error.incomplete": "Please complete this question first.",
812
+ "error.unanswered": "Please select an option or enter a custom answer.",
813
+ "nav.prev": "Previous question",
814
+ "nav.next": "Next question",
815
+ "nav.minimize": "Collapse the question card",
816
+ "nav.maximize": "Expand the question card",
817
+ "nav.cancel": "Dismiss all questions",
818
+ "option.recommended": "Recommended",
819
+ "custom.placeholder": "Type your answer",
820
+ "action.skip": "Skip this question",
821
+ "action.next": "Next",
822
+ "plan.header": "Plan review",
823
+ "plan.approve": "Approve",
824
+ "plan.decline": "Refuse",
825
+ "plan.discuss": "Chat about it"
826
+ };
827
+ //#endregion
828
+ //#region lib/types/client/index.js
829
+ /** Dictionary namespace owned by this plugin. */
830
+ const NS = "question";
831
+ /** Required services: Agent scopes, Remote Events, Session UI, Slot registry, and copy. */
832
+ const inject = [
833
+ "sessions",
834
+ "remote",
835
+ "uiSession",
836
+ "slots",
837
+ "locale"
838
+ ];
839
+ /** Present one request until the user answers, cancels, or its lifetime ends. */
840
+ async function answerQuestion(ctx, owner, request, next, registerPendingInteraction) {
841
+ const sessionId = ctx.sessions.scopeOf(owner);
842
+ if (sessionId === void 0) return next();
843
+ const pending = new PendingQuestion(sessionId, request.questions, request.signal);
844
+ const completed = Promise.withResolvers();
845
+ const remove = registerPendingInteraction(pending, async () => {
846
+ pending.delegate();
847
+ await completed.promise;
848
+ });
849
+ try {
850
+ try {
851
+ return await pending.result;
852
+ } catch (error) {
853
+ if (pending.isDelegation(error)) return await next();
854
+ throw error;
855
+ }
856
+ } finally {
857
+ remove();
858
+ completed.resolve();
859
+ }
860
+ }
861
+ /**
862
+ * Client plugin body: register the `question` dictionaries and the question
863
+ * composer into the composer chain. Zero business face — data and verbs live
864
+ * on the matched carrier; t rides the standard locale seat.
865
+ * @param ctx - client root context.
866
+ */
867
+ function apply(ctx) {
868
+ ctx.effect(() => ctx.locale.register(NS, {
869
+ zh,
870
+ en
871
+ }), "ui-user-questions: dictionaries");
872
+ const questionDraftStore = createQuestionDraftStore();
873
+ const registerPendingInteraction = ctx.uiSession.registerPendingInteraction((pending) => pending.kind === "plan-review" ? 2 : 1);
874
+ ctx.slots.inject("conversation.composer", () => ctx.slots.register({
875
+ name: "conversation.composer",
876
+ select: ({ pendingInteraction }) => pendingInteraction instanceof PendingQuestion ? pendingInteraction : null,
877
+ locale: NS,
878
+ store: questionDraftStore
879
+ }, QuestionComposer));
880
+ ctx.remote.$on("user-questions/request", function(request, next) {
881
+ return answerQuestion(ctx, this, request, next, registerPendingInteraction);
882
+ });
883
+ }
884
+ //#endregion
885
+ exports.apply = apply;
886
+ exports.inject = inject;
887
+ return module.exports;
888
+ }
889
+ });
890
+
891
+ //# sourceMappingURL=client.js.map