@hoox-sh/hoox-tui 0.3.0 → 0.3.2

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.
@@ -5,20 +5,27 @@
5
5
 
6
6
  /** @jsxImportSource @opentui/react */
7
7
  /**
8
- * Thin wrappers around @opentui-ui/dialog/react with Hoox color tokens.
9
- * Provides ConfirmDialog, ChoiceDialog, and LoadingDialog helpers.
8
+ * In-house dialog system for Hoox TUI.
10
9
  *
11
- * All dialogs render centered with a dimmed backdrop (backdropOpacity 0.35)
12
- * and use Colors tokens from @hoox-sh/hoox-shared for consistent Hoox branding.
10
+ * Intentionally does **not** depend on `@opentui-ui/dialog`, which nests a
11
+ * second `@opentui/core` and crashes global installs with:
12
+ * OPENTUI_FORCE_WCWIDTH already registered with different configuration
13
+ *
14
+ * Provides DialogProvider + useDialog + showConfirm / showChoice / showLoading
15
+ * with the same call shapes views already use.
13
16
  */
14
- import { useState } from "react";
15
17
  import {
16
- type ChoiceContext,
17
- type ConfirmContext,
18
- type DialogId,
19
- useDialogKeyboard,
20
- } from "@opentui-ui/dialog/react";
18
+ createContext,
19
+ useCallback,
20
+ useContext,
21
+ useEffect,
22
+ useMemo,
23
+ useState,
24
+ type ReactNode,
25
+ } from "react";
26
+ import { useKeyboard } from "@opentui/react";
21
27
  import { Colors } from "@hoox-sh/hoox-shared";
28
+ import { setOpenDialogCount } from "./dialog-state";
22
29
 
23
30
  // ── Types ──────────────────────────────────────────────────────────────────
24
31
 
@@ -55,227 +62,495 @@ export interface ChoiceDialogOptions<K extends string = string> {
55
62
  closeOnClickOutside?: boolean;
56
63
  }
57
64
 
58
- /** Dialog manager interface — subset of what useDialog() returns */
65
+ /** Dialog manager interface used by views + showConfirm helpers */
59
66
  export interface DialogHandle {
67
+ /**
68
+ * Low-level confirm used by some tests. Prefer {@link showConfirm}.
69
+ * When `content` is provided, it is ignored — title/message form is used.
70
+ */
60
71
  confirm(options: {
61
- content: (ctx: ConfirmContext) => unknown;
72
+ content?: (ctx: {
73
+ resolve: (value: boolean) => void;
74
+ dismiss: () => void;
75
+ }) => unknown;
76
+ title?: string;
77
+ message?: string;
78
+ confirmLabel?: string;
79
+ cancelLabel?: string;
62
80
  closeOnClickOutside?: boolean;
63
81
  }): Promise<boolean>;
64
82
  choice<K extends string>(options: {
65
- content: (ctx: ChoiceContext<K>) => unknown;
83
+ content?: (ctx: {
84
+ resolve: (value: K) => void;
85
+ dismiss: () => void;
86
+ dialogId: string | number;
87
+ }) => unknown;
88
+ title?: string;
89
+ choices?: ChoiceOption<K>[];
66
90
  fallback?: K;
67
91
  closeOnClickOutside?: boolean;
68
92
  }): Promise<K | undefined>;
69
93
  show(options: {
70
- content: () => unknown;
94
+ content?: () => unknown;
95
+ message?: string;
71
96
  id?: string | number;
72
97
  }): string | number;
73
98
  close(id?: string | number): void;
99
+ /** High-level helpers (also used directly by showConfirm) */
100
+ confirmSimple(options: ConfirmDialogOptions): Promise<boolean>;
101
+ choiceSimple<K extends string>(
102
+ options: ChoiceDialogOptions<K>
103
+ ): Promise<K | undefined>;
74
104
  }
75
105
 
76
- // ── Internal: keyboard-navigable choice list ──────────────────────────────
106
+ // ── Internal stack model ───────────────────────────────────────────────────
77
107
 
78
- /** Props for the keyboard-navigable choice list rendered inside a dialog */
79
- interface ChoiceListProps<K extends string = string> {
80
- choices: ChoiceOption<K>[];
81
- accentColor: string;
82
- foregroundColor: string;
83
- mutedColor: string;
84
- highlightColor: string;
85
- resolve: (value: K) => void;
86
- dismiss: () => void;
87
- dialogId: DialogId;
108
+ type StackEntry =
109
+ | {
110
+ kind: "confirm";
111
+ id: number;
112
+ title: string;
113
+ message: string;
114
+ confirmLabel: string;
115
+ cancelLabel: string;
116
+ closeOnClickOutside: boolean;
117
+ resolve: (value: boolean) => void;
118
+ }
119
+ | {
120
+ kind: "choice";
121
+ id: number;
122
+ title: string;
123
+ choices: ChoiceOption[];
124
+ fallback?: string;
125
+ closeOnClickOutside: boolean;
126
+ resolve: (value: string | undefined) => void;
127
+ }
128
+ | {
129
+ kind: "loading";
130
+ id: number;
131
+ message: string;
132
+ };
133
+
134
+ let nextId = 1;
135
+
136
+ const DialogCtx = createContext<DialogHandle | null>(null);
137
+
138
+ // ── Overlay UI ─────────────────────────────────────────────────────────────
139
+
140
+ function ConfirmOverlay({
141
+ entry,
142
+ onResolve,
143
+ }: {
144
+ entry: Extract<StackEntry, { kind: "confirm" }>;
145
+ onResolve: (value: boolean) => void;
146
+ }) {
147
+ useKeyboard((key) => {
148
+ const name = String(key.name ?? "").toLowerCase();
149
+ if (name === "return" || name === "enter" || name === "y") {
150
+ onResolve(true);
151
+ return;
152
+ }
153
+ if (name === "escape" || name === "n") {
154
+ onResolve(false);
155
+ }
156
+ });
157
+
158
+ return (
159
+ <box
160
+ position="absolute"
161
+ left={0}
162
+ top={0}
163
+ width="100%"
164
+ height="100%"
165
+ justifyContent="center"
166
+ alignItems="center"
167
+ backgroundColor={Colors.backdrop}
168
+ onMouseUp={entry.closeOnClickOutside ? () => onResolve(false) : undefined}
169
+ >
170
+ <box
171
+ flexDirection="column"
172
+ gap={1}
173
+ padding={2}
174
+ border={true}
175
+ borderStyle="double"
176
+ borderColor={Colors.accent}
177
+ backgroundColor={Colors.card}
178
+ minWidth={44}
179
+ onMouseUp={(e: { stopPropagation?: () => void }) => {
180
+ e?.stopPropagation?.();
181
+ }}
182
+ >
183
+ <text fg={Colors.accent} bold>
184
+ {entry.title}
185
+ </text>
186
+ <text fg={Colors.foreground}>{entry.message}</text>
187
+ <box flexDirection="row" gap={2} paddingTop={1}>
188
+ <text
189
+ fg={Colors.accent}
190
+ bg={Colors.card}
191
+ bold
192
+ onMouseUp={() => onResolve(true)}
193
+ >
194
+ {` [Y/Enter] ${entry.confirmLabel} `}
195
+ </text>
196
+ <text fg={Colors.muted} onMouseUp={() => onResolve(false)}>
197
+ {` [N/Esc] ${entry.cancelLabel} `}
198
+ </text>
199
+ </box>
200
+ </box>
201
+ </box>
202
+ );
88
203
  }
89
204
 
90
- /**
91
- * Internal component that renders a keyboard-navigable choice list.
92
- * Uses useDialogKeyboard scoped to the dialog so only the topmost
93
- * dialog receives keyboard events.
94
- */
95
- function ChoiceList<K extends string = string>({
96
- choices,
97
- accentColor,
98
- foregroundColor,
99
- mutedColor,
100
- highlightColor,
101
- resolve,
102
- dismiss,
103
- dialogId,
104
- }: ChoiceListProps<K>) {
205
+ function ChoiceOverlay({
206
+ entry,
207
+ onResolve,
208
+ }: {
209
+ entry: Extract<StackEntry, { kind: "choice" }>;
210
+ onResolve: (value: string | undefined) => void;
211
+ }) {
105
212
  const [selectedIndex, setSelectedIndex] = useState(0);
106
213
 
107
- useDialogKeyboard((key) => {
108
- if (key.name === "down" || key.name === "j") {
109
- setSelectedIndex((prev) => (prev < choices.length - 1 ? prev + 1 : 0));
214
+ useKeyboard((key) => {
215
+ const name = String(key.name ?? "").toLowerCase();
216
+ if (name === "down" || name === "j") {
217
+ setSelectedIndex((prev) =>
218
+ prev < entry.choices.length - 1 ? prev + 1 : 0
219
+ );
220
+ return;
110
221
  }
111
- if (key.name === "up" || key.name === "k") {
112
- setSelectedIndex((prev) => (prev > 0 ? prev - 1 : choices.length - 1));
222
+ if (name === "up" || name === "k") {
223
+ setSelectedIndex((prev) =>
224
+ prev > 0 ? prev - 1 : entry.choices.length - 1
225
+ );
226
+ return;
113
227
  }
114
- if (key.name === "return") {
115
- const choice = choices[selectedIndex];
116
- if (choice) resolve(choice.key);
228
+ if (name === "return" || name === "enter") {
229
+ const choice = entry.choices[selectedIndex];
230
+ if (choice) onResolve(choice.key);
231
+ return;
117
232
  }
118
- if (key.name === "escape") {
119
- dismiss();
233
+ if (name === "escape") {
234
+ onResolve(entry.fallback);
120
235
  }
121
- }, dialogId);
236
+ });
122
237
 
123
238
  return (
124
- <box flexDirection="column" gap={0}>
125
- {choices.map((choice, idx) => (
126
- <box flexDirection="row" gap={2} paddingLeft={1} paddingRight={1}>
127
- {/* Selection indicator */}
128
- <text fg={idx === selectedIndex ? accentColor : mutedColor}>
129
- {idx === selectedIndex ? "▶" : " "}
130
- </text>
131
- {/* Choice label */}
132
- <text
133
- fg={idx === selectedIndex ? foregroundColor : mutedColor}
134
- bg={idx === selectedIndex ? highlightColor : undefined}
135
- onMouseUp={() => resolve(choice.key)}
136
- >
137
- {choice.label}
239
+ <box
240
+ position="absolute"
241
+ left={0}
242
+ top={0}
243
+ width="100%"
244
+ height="100%"
245
+ justifyContent="center"
246
+ alignItems="center"
247
+ backgroundColor={Colors.backdrop}
248
+ onMouseUp={
249
+ entry.closeOnClickOutside ? () => onResolve(entry.fallback) : undefined
250
+ }
251
+ >
252
+ <box
253
+ flexDirection="column"
254
+ gap={1}
255
+ padding={2}
256
+ border={true}
257
+ borderStyle="double"
258
+ borderColor={Colors.accent}
259
+ backgroundColor={Colors.card}
260
+ minWidth={48}
261
+ >
262
+ <text fg={Colors.accent} bold>
263
+ {entry.title}
264
+ </text>
265
+ <text dim fg={Colors.muted}>
266
+ Use ↑↓ to navigate, Enter to select, Esc to cancel
267
+ </text>
268
+ <box flexDirection="column" gap={0} paddingTop={1}>
269
+ {entry.choices.map((choice, idx) => (
270
+ <box
271
+ key={choice.key}
272
+ flexDirection="row"
273
+ gap={2}
274
+ paddingLeft={1}
275
+ paddingRight={1}
276
+ >
277
+ <text fg={idx === selectedIndex ? Colors.accent : Colors.muted}>
278
+ {idx === selectedIndex ? "▶" : " "}
279
+ </text>
280
+ <text
281
+ fg={idx === selectedIndex ? Colors.foreground : Colors.muted}
282
+ bg={idx === selectedIndex ? Colors.card : undefined}
283
+ onMouseUp={() => onResolve(choice.key)}
284
+ >
285
+ {choice.label}
286
+ </text>
287
+ {choice.description ? (
288
+ <text dim fg={Colors.muted}>
289
+ — {choice.description}
290
+ </text>
291
+ ) : null}
292
+ </box>
293
+ ))}
294
+ </box>
295
+ <box flexDirection="row" justifyContent="flex-end" paddingTop={1}>
296
+ <text fg={Colors.muted} onMouseUp={() => onResolve(entry.fallback)}>
297
+ {" Cancel "}
138
298
  </text>
139
- {/* Optional description */}
140
- {choice.description && (
141
- <text dim fg={mutedColor}>
142
- — {choice.description}
143
- </text>
144
- )}
145
299
  </box>
146
- ))}
300
+ </box>
301
+ </box>
302
+ );
303
+ }
304
+
305
+ function LoadingOverlay({ message }: { message: string }) {
306
+ return (
307
+ <box
308
+ position="absolute"
309
+ left={0}
310
+ top={0}
311
+ width="100%"
312
+ height="100%"
313
+ justifyContent="center"
314
+ alignItems="center"
315
+ backgroundColor={Colors.backdrop}
316
+ >
317
+ <box
318
+ flexDirection="column"
319
+ padding={2}
320
+ gap={1}
321
+ border={true}
322
+ borderStyle="single"
323
+ borderColor={Colors.border}
324
+ backgroundColor={Colors.card}
325
+ justifyContent="center"
326
+ alignItems="center"
327
+ >
328
+ <text fg={Colors.accent}>{message}</text>
329
+ <text dim fg={Colors.muted}>
330
+ Please wait...
331
+ </text>
332
+ </box>
147
333
  </box>
148
334
  );
149
335
  }
150
336
 
151
- // ── Dialog wrapper functions ───────────────────────────────────────────────
337
+ // ── Provider ───────────────────────────────────────────────────────────────
338
+
339
+ export interface DialogProviderProps {
340
+ children: ReactNode;
341
+ /** Accepted for API compat with old @opentui-ui/dialog provider; unused. */
342
+ size?: string;
343
+ backdropColor?: string;
344
+ backdropOpacity?: number;
345
+ }
346
+
347
+ export function DialogProvider({ children }: DialogProviderProps) {
348
+ const [stack, setStack] = useState<StackEntry[]>([]);
349
+
350
+ useEffect(() => {
351
+ setOpenDialogCount(stack.length);
352
+ return () => setOpenDialogCount(0);
353
+ }, [stack.length]);
354
+
355
+ const popId = useCallback((id: number) => {
356
+ setStack((s) => s.filter((e) => e.id !== id));
357
+ }, []);
358
+
359
+ const handle = useMemo<DialogHandle>(() => {
360
+ const confirmSimple = (options: ConfirmDialogOptions) =>
361
+ new Promise<boolean>((resolve) => {
362
+ const id = nextId++;
363
+ setStack((s) => [
364
+ ...s,
365
+ {
366
+ kind: "confirm",
367
+ id,
368
+ title: options.title,
369
+ message: options.message,
370
+ confirmLabel: options.confirmLabel ?? "Confirm",
371
+ cancelLabel: options.cancelLabel ?? "Cancel",
372
+ closeOnClickOutside: options.closeOnClickOutside ?? true,
373
+ resolve: (value) => {
374
+ popId(id);
375
+ resolve(value);
376
+ },
377
+ },
378
+ ]);
379
+ });
380
+
381
+ const choiceSimple = <K extends string>(options: ChoiceDialogOptions<K>) =>
382
+ new Promise<K | undefined>((resolve) => {
383
+ const id = nextId++;
384
+ setStack((s) => [
385
+ ...s,
386
+ {
387
+ kind: "choice",
388
+ id,
389
+ title: options.title,
390
+ choices: options.choices as ChoiceOption[],
391
+ fallback: options.fallback,
392
+ closeOnClickOutside: options.closeOnClickOutside ?? true,
393
+ resolve: (value) => {
394
+ popId(id);
395
+ resolve(value as K | undefined);
396
+ },
397
+ },
398
+ ]);
399
+ });
400
+
401
+ return {
402
+ confirmSimple,
403
+ choiceSimple,
404
+ confirm: (options) => {
405
+ // Tests pass title/message via custom shapes; content() is ignored.
406
+ const title =
407
+ options.title ??
408
+ (typeof options.content === "function" ? "Confirm" : "Confirm");
409
+ const message = options.message ?? "Continue?";
410
+ return confirmSimple({
411
+ title,
412
+ message,
413
+ confirmLabel: options.confirmLabel,
414
+ cancelLabel: options.cancelLabel,
415
+ closeOnClickOutside: options.closeOnClickOutside,
416
+ });
417
+ },
418
+ choice: <K extends string>(options: {
419
+ title?: string;
420
+ choices?: ChoiceOption<K>[];
421
+ fallback?: K;
422
+ closeOnClickOutside?: boolean;
423
+ }) => {
424
+ return choiceSimple<K>({
425
+ title: options.title ?? "Select",
426
+ choices: options.choices ?? [],
427
+ fallback: options.fallback,
428
+ closeOnClickOutside: options.closeOnClickOutside,
429
+ });
430
+ },
431
+ show: (options) => {
432
+ const id = nextId++;
433
+ setStack((s) => [
434
+ ...s,
435
+ {
436
+ kind: "loading",
437
+ id,
438
+ message: options.message ?? "Loading…",
439
+ },
440
+ ]);
441
+ return id;
442
+ },
443
+ close: (id?) => {
444
+ if (id == null) {
445
+ setStack([]);
446
+ return;
447
+ }
448
+ popId(Number(id));
449
+ },
450
+ };
451
+ }, [popId]);
452
+
453
+ const top = stack[stack.length - 1] ?? null;
454
+
455
+ // Mark shell overlay open when a dialog is showing so view keys are gated.
456
+ // We piggy-back on ui-store modal only for keyboard gate — dialogs render here.
457
+ // Views use isShellOverlayOpen() which checks commandPalette + modal.
458
+ // Dialogs also use useKeyboard at higher priority than views when mounted last.
459
+
460
+ return (
461
+ <DialogCtx.Provider value={handle}>
462
+ {children}
463
+ {top?.kind === "confirm" ? (
464
+ <ConfirmOverlay
465
+ entry={top}
466
+ onResolve={(v) => {
467
+ top.resolve(v);
468
+ }}
469
+ />
470
+ ) : null}
471
+ {top?.kind === "choice" ? (
472
+ <ChoiceOverlay
473
+ entry={top}
474
+ onResolve={(v) => {
475
+ top.resolve(v);
476
+ }}
477
+ />
478
+ ) : null}
479
+ {top?.kind === "loading" ? (
480
+ <LoadingOverlay message={top.message} />
481
+ ) : null}
482
+ </DialogCtx.Provider>
483
+ );
484
+ }
485
+
486
+ export function useDialog(): DialogHandle {
487
+ const ctx = useContext(DialogCtx);
488
+ if (!ctx) {
489
+ throw new Error("useDialog must be used within <DialogProvider>");
490
+ }
491
+ return ctx;
492
+ }
493
+
494
+ // ── Public helpers (stable call sites) ─────────────────────────────────────
152
495
 
153
496
  /**
154
497
  * Show a confirmation dialog styled with Hoox colors.
155
498
  * Returns `true` if confirmed, `false` if canceled or dismissed.
156
- *
157
- * @example
158
- * const confirmed = await showConfirm(dialog, {
159
- * title: "Delete Worker",
160
- * message: "This action cannot be undone.",
161
- * confirmLabel: "Delete",
162
- * cancelLabel: "Keep",
163
- * })
164
499
  */
165
500
  export async function showConfirm(
166
501
  dialog: DialogHandle,
167
502
  options: ConfirmDialogOptions
168
503
  ): Promise<boolean> {
504
+ if (typeof dialog.confirmSimple === "function") {
505
+ return dialog.confirmSimple(options);
506
+ }
507
+ // Fallback for test doubles that only implement confirm()
169
508
  return dialog.confirm({
170
- content: (ctx: ConfirmContext) => (
171
- <box flexDirection="column" padding={1} gap={1}>
172
- <text fg={Colors.accent}>
173
- <b>{options.title}</b>
174
- </text>
175
- <text fg={Colors.muted}>{options.message}</text>
176
- <box flexDirection="row" gap={2} justifyContent="flex-end">
177
- <text fg={Colors.muted} onMouseUp={() => ctx.resolve(false)}>
178
- {" "}
179
- {options.cancelLabel ?? "Cancel"}
180
- {" "}
181
- </text>
182
- <text
183
- fg={Colors.accent}
184
- bg={Colors.card}
185
- onMouseUp={() => ctx.resolve(true)}
186
- >
187
- {" "}
188
- {options.confirmLabel ?? "Confirm"}
189
- {" "}
190
- </text>
191
- </box>
192
- </box>
193
- ),
194
- closeOnClickOutside: options.closeOnClickOutside ?? true,
509
+ title: options.title,
510
+ message: options.message,
511
+ confirmLabel: options.confirmLabel,
512
+ cancelLabel: options.cancelLabel,
513
+ closeOnClickOutside: options.closeOnClickOutside,
195
514
  });
196
515
  }
197
516
 
198
517
  /**
199
518
  * Show a multiple-choice dialog styled with Hoox colors.
200
519
  * Returns the selected choice key, or `fallback` if dismissed.
201
- *
202
- * @example
203
- * const action = await showChoice(dialog, {
204
- * title: "Select Action",
205
- * choices: [
206
- * { key: "restart", label: "Restart Worker", description: "Graceful restart" },
207
- * { key: "stop", label: "Stop Worker", description: "Immediate halt" },
208
- * ],
209
- * })
210
520
  */
211
521
  export async function showChoice<K extends string>(
212
522
  dialog: DialogHandle,
213
523
  options: ChoiceDialogOptions<K>
214
524
  ): Promise<K | undefined> {
525
+ if (typeof dialog.choiceSimple === "function") {
526
+ return dialog.choiceSimple(options);
527
+ }
215
528
  return dialog.choice<K>({
216
- content: (ctx: ChoiceContext<K>) => (
217
- <box flexDirection="column" padding={1} gap={1}>
218
- {/* Header */}
219
- <text fg={Colors.accent}>
220
- <b>{options.title}</b>
221
- </text>
222
- <text dim fg={Colors.muted}>
223
- Use ↑↓ to navigate, Enter to select, Esc to cancel
224
- </text>
225
-
226
- {/* Divider */}
227
- <box height={1} />
228
-
229
- {/* Keyboard-navigable choice list */}
230
- <ChoiceList
231
- choices={options.choices}
232
- accentColor={Colors.accent}
233
- foregroundColor={Colors.foreground}
234
- mutedColor={Colors.muted}
235
- highlightColor={Colors.card}
236
- resolve={ctx.resolve}
237
- dismiss={ctx.dismiss}
238
- dialogId={ctx.dialogId}
239
- />
240
-
241
- {/* Footer actions */}
242
- <box flexDirection="row" justifyContent="flex-end" paddingTop={1}>
243
- <text fg={Colors.muted} onMouseUp={ctx.dismiss}>
244
- {" Cancel "}
245
- </text>
246
- </box>
247
- </box>
248
- ),
529
+ title: options.title,
530
+ choices: options.choices,
249
531
  fallback: options.fallback,
250
- closeOnClickOutside: options.closeOnClickOutside ?? true,
532
+ closeOnClickOutside: options.closeOnClickOutside,
251
533
  });
252
534
  }
253
535
 
254
536
  /**
255
- * Show a non-interactive loading dialog (centered, dimmed backdrop).
256
- * Returns a `close` function to dismiss the dialog when loading completes.
257
- *
258
- * @example
259
- * const close = showLoading(dialog, "Deploying workers...")
260
- * await deployAll()
261
- * close()
537
+ * Show a non-interactive loading dialog.
538
+ * Returns a `close` function to dismiss when work completes.
262
539
  */
263
540
  export function showLoading(dialog: DialogHandle, message: string): () => void {
264
- const id = dialog.show({
265
- content: () => (
266
- <box
267
- flexDirection="column"
268
- padding={2}
269
- gap={1}
270
- justifyContent="center"
271
- alignItems="center"
272
- >
273
- <text fg={Colors.accent}>{message}</text>
274
- <text dim fg={Colors.muted}>
275
- Please wait...
276
- </text>
277
- </box>
278
- ),
279
- });
541
+ const id = dialog.show({ message });
280
542
  return () => dialog.close(id);
281
543
  }
544
+
545
+ // Re-export for select.tsx / tests that referenced dialog keyboard helpers
546
+ export type DialogId = string | number;
547
+
548
+ /** @deprecated No-op; local keyboard is handled inside overlays. */
549
+ export function useDialogKeyboard(
550
+ _handler: (key: { name?: string }) => void,
551
+ _dialogId?: DialogId
552
+ ): void {
553
+ // Overlays own their own useKeyboard; this stub avoids @opentui-ui/dialog.
554
+ void _handler;
555
+ void _dialogId;
556
+ }