@checkstack/ui 1.11.0 → 1.13.0

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.
Files changed (71) hide show
  1. package/.storybook/main.ts +43 -0
  2. package/CHANGELOG.md +326 -0
  3. package/package.json +23 -18
  4. package/scripts/generate-stdlib-types.ts +23 -0
  5. package/src/components/Accordion.tsx +17 -9
  6. package/src/components/ActionCard.tsx +99 -11
  7. package/src/components/BrandIcon.tsx +57 -0
  8. package/src/components/CodeEditor/CodeEditor.tsx +159 -14
  9. package/src/components/CodeEditor/TypefoxEditor.tsx +537 -168
  10. package/src/components/CodeEditor/editorTheme.test.ts +41 -0
  11. package/src/components/CodeEditor/editorTheme.ts +26 -0
  12. package/src/components/CodeEditor/generated/builtin-modules.json +1 -0
  13. package/src/components/CodeEditor/importSpecifiers.test.ts +286 -0
  14. package/src/components/CodeEditor/importSpecifiers.ts +267 -0
  15. package/src/components/CodeEditor/index.ts +26 -0
  16. package/src/components/CodeEditor/monacoGuard.ts +76 -0
  17. package/src/components/CodeEditor/monacoTsService.ts +185 -0
  18. package/src/components/CodeEditor/popoutTitle.test.ts +37 -0
  19. package/src/components/CodeEditor/popoutTitle.ts +31 -0
  20. package/src/components/CodeEditor/scriptContext.test.ts +15 -7
  21. package/src/components/CodeEditor/scriptContext.ts +12 -18
  22. package/src/components/CodeEditor/scriptDiagnostics.test.ts +135 -0
  23. package/src/components/CodeEditor/scriptDiagnostics.ts +172 -0
  24. package/src/components/CodeEditor/types.ts +79 -0
  25. package/src/components/CodeEditor/validateScripts.ts +172 -0
  26. package/src/components/CodeEditor/vscodeServicesSignal.ts +72 -0
  27. package/src/components/ConfirmationModal.tsx +7 -1
  28. package/src/components/Dialog.tsx +32 -11
  29. package/src/components/DurationInput.tsx +121 -0
  30. package/src/components/DynamicForm/DynamicForm.tsx +119 -47
  31. package/src/components/DynamicForm/DynamicOptionsField.tsx +19 -14
  32. package/src/components/DynamicForm/FormField.tsx +183 -15
  33. package/src/components/DynamicForm/MultiTypeEditorField.tsx +78 -2
  34. package/src/components/DynamicForm/SecretEnvEditor.tsx +315 -0
  35. package/src/components/DynamicForm/index.ts +20 -0
  36. package/src/components/DynamicForm/secretEnv.logic.test.ts +126 -0
  37. package/src/components/DynamicForm/secretEnv.logic.ts +87 -0
  38. package/src/components/DynamicForm/types.ts +134 -1
  39. package/src/components/DynamicForm/utils.test.ts +38 -0
  40. package/src/components/DynamicForm/utils.ts +54 -0
  41. package/src/components/DynamicForm/validation.logic.test.ts +255 -0
  42. package/src/components/DynamicForm/validation.logic.ts +210 -0
  43. package/src/components/DynamicIcon.tsx +39 -17
  44. package/src/components/Markdown.tsx +68 -2
  45. package/src/components/Popover.tsx +6 -1
  46. package/src/components/ScriptTestPanel.logic.test.ts +139 -0
  47. package/src/components/ScriptTestPanel.logic.ts +137 -0
  48. package/src/components/ScriptTestPanel.tsx +394 -0
  49. package/src/components/Sheet.tsx +21 -6
  50. package/src/components/Spinner.tsx +56 -0
  51. package/src/components/StatusBadge.tsx +78 -0
  52. package/src/components/StrategyConfigCard.tsx +3 -3
  53. package/src/components/Tabs.tsx +7 -1
  54. package/src/components/TimeOfDayInput.tsx +116 -0
  55. package/src/components/UserMenu.logic.test.ts +37 -0
  56. package/src/components/UserMenu.logic.ts +30 -0
  57. package/src/components/UserMenu.tsx +40 -12
  58. package/src/components/comboboxInteraction.ts +39 -0
  59. package/src/components/iconRegistry.tsx +27 -0
  60. package/src/components/portalContainer.ts +24 -0
  61. package/src/index.ts +7 -0
  62. package/stories/ActionCard.stories.tsx +60 -0
  63. package/stories/CodeEditor.stories.tsx +47 -2
  64. package/stories/DurationInput.stories.tsx +59 -0
  65. package/stories/Introduction.mdx +1 -1
  66. package/stories/Markdown.stories.tsx +56 -0
  67. package/stories/ScriptTestPanel.stories.tsx +106 -0
  68. package/stories/SecretEnvEditor.stories.tsx +80 -0
  69. package/stories/Spinner.stories.tsx +90 -0
  70. package/stories/TimeOfDayInput.stories.tsx +34 -0
  71. package/tsconfig.json +4 -0
@@ -4,15 +4,31 @@ import {
4
4
  ChevronRight,
5
5
  GripVertical,
6
6
  AlertTriangle,
7
+ MoreVertical,
7
8
  Trash2,
8
9
  } from "lucide-react";
9
10
  import { Card, CardContent, CardHeader } from "./Card";
10
11
  import { Button } from "./Button";
11
12
  import { Toggle } from "./Toggle";
12
- import { DynamicIcon, type LucideIconName } from "./DynamicIcon";
13
+ import { DynamicIcon, type IconName } from "./DynamicIcon";
13
14
  import { Badge, type BadgeProps } from "./Badge";
15
+ import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
16
+ import { DropdownMenuItem, MenuCloseContext } from "./Menu";
14
17
  import { cn } from "../utils";
15
18
 
19
+ /**
20
+ * A single entry in an {@link ActionCard}'s three-dot overflow menu. Used to
21
+ * relocate per-card commands (Duplicate, Disable/Enable, Delete) off the
22
+ * header row into a compact menu for a Home-Assistant-style collapsed card.
23
+ */
24
+ export interface ActionCardMenuItem {
25
+ label: string;
26
+ icon?: IconName;
27
+ onClick: () => void;
28
+ /** `destructive` tints the item red (e.g. Delete). Defaults to neutral. */
29
+ variant?: "default" | "destructive";
30
+ }
31
+
16
32
  export interface ActionCardProps {
17
33
  /** Stable identifier used for drag-reorder + React key. */
18
34
  id: string;
@@ -22,8 +38,8 @@ export interface ActionCardProps {
22
38
  description?: string;
23
39
  /** Plugin/category label rendered as a subdued badge. */
24
40
  category?: string;
25
- /** Lucide icon (PascalCase) shown to the left of the title. */
26
- icon?: LucideIconName;
41
+ /** Icon (PascalCase) shown to the left of the title - lucide or brand. */
42
+ icon?: IconName;
27
43
  /** Toggle for the action's `enabled` flag. Omit to hide the toggle. */
28
44
  enabled?: boolean;
29
45
  onEnabledChange?: (enabled: boolean) => void;
@@ -36,6 +52,26 @@ export interface ActionCardProps {
36
52
  /** Controlled expanded state. */
37
53
  expanded?: boolean;
38
54
  onExpandedChange?: (expanded: boolean) => void;
55
+ /**
56
+ * When provided, the card behaves as a Home-Assistant-style collapsed
57
+ * summary row: clicking the header opens the item's full configuration in
58
+ * a side sheet (via this callback) instead of expanding inline. The body
59
+ * `children` are not rendered inline in this mode - the host renders them
60
+ * inside its own sheet. Omit to keep the legacy inline-expand behaviour.
61
+ */
62
+ onOpenSheet?: () => void;
63
+ /**
64
+ * Compact, single-line summary shown under the title in the collapsed
65
+ * summary row (e.g. derived from the item's config). Distinct from
66
+ * `description`, which is the operator's free-text note; `summary` wins
67
+ * when both are present.
68
+ */
69
+ summary?: string;
70
+ /**
71
+ * Overflow-menu commands rendered behind a three-dot button on the header
72
+ * row (Duplicate / Disable / Delete, etc.). Omit to hide the menu.
73
+ */
74
+ actions?: ActionCardMenuItem[];
39
75
  /** Extra badges (e.g. produces / consumes hints). */
40
76
  badges?: Array<{
41
77
  label: string;
@@ -101,6 +137,9 @@ export const ActionCard: React.FC<ActionCardProps> = ({
101
137
  defaultExpanded = true,
102
138
  expanded,
103
139
  onExpandedChange,
140
+ onOpenSheet,
141
+ summary,
142
+ actions,
104
143
  badges,
105
144
  children,
106
145
  className,
@@ -108,8 +147,16 @@ export const ActionCard: React.FC<ActionCardProps> = ({
108
147
  }) => {
109
148
  const [internalExpanded, setInternalExpanded] =
110
149
  React.useState(defaultExpanded);
111
- const isExpanded = expanded ?? internalExpanded;
112
- const toggleExpanded = () => {
150
+ const [menuOpen, setMenuOpen] = React.useState(false);
151
+ // Sheet mode: the header opens a side sheet instead of expanding inline,
152
+ // so the card stays a compact summary row at all times.
153
+ const sheetMode = onOpenSheet !== undefined;
154
+ const isExpanded = sheetMode ? false : (expanded ?? internalExpanded);
155
+ const handleHeaderClick = () => {
156
+ if (sheetMode) {
157
+ onOpenSheet();
158
+ return;
159
+ }
113
160
  const next = !isExpanded;
114
161
  if (expanded === undefined) setInternalExpanded(next);
115
162
  onExpandedChange?.(next);
@@ -139,12 +186,14 @@ export const ActionCard: React.FC<ActionCardProps> = ({
139
186
  )}
140
187
  <button
141
188
  type="button"
142
- onClick={toggleExpanded}
189
+ onClick={handleHeaderClick}
143
190
  className="flex items-center flex-1 gap-2 text-left"
144
- aria-expanded={isExpanded}
145
- aria-controls={`${id}-body`}
191
+ aria-expanded={sheetMode ? undefined : isExpanded}
192
+ aria-controls={sheetMode ? undefined : `${id}-body`}
146
193
  >
147
- {isExpanded ? (
194
+ {sheetMode ? (
195
+ <ChevronRight className="w-4 h-4 shrink-0 text-muted-foreground" />
196
+ ) : isExpanded ? (
148
197
  <ChevronDown className="w-4 h-4 shrink-0 text-muted-foreground" />
149
198
  ) : (
150
199
  <ChevronRight className="w-4 h-4 shrink-0 text-muted-foreground" />
@@ -172,9 +221,9 @@ export const ActionCard: React.FC<ActionCardProps> = ({
172
221
  </Badge>
173
222
  ))}
174
223
  </div>
175
- {description && (
224
+ {(summary ?? description) && (
176
225
  <p className="text-xs truncate text-muted-foreground">
177
- {description}
226
+ {summary ?? description}
178
227
  </p>
179
228
  )}
180
229
  {hasErrors && (
@@ -198,6 +247,45 @@ export const ActionCard: React.FC<ActionCardProps> = ({
198
247
  aria-label={enabled ? "Disable action" : "Enable action"}
199
248
  />
200
249
  )}
250
+ {actions && actions.length > 0 && (
251
+ <Popover open={menuOpen} onOpenChange={setMenuOpen}>
252
+ <PopoverTrigger asChild>
253
+ <Button
254
+ type="button"
255
+ variant="ghost"
256
+ size="icon"
257
+ className="w-8 h-8 text-muted-foreground hover:text-foreground shrink-0"
258
+ aria-label="More actions"
259
+ >
260
+ <MoreVertical className="w-4 h-4" />
261
+ </Button>
262
+ </PopoverTrigger>
263
+ <PopoverContent align="end" className="w-48 p-1">
264
+ <MenuCloseContext.Provider
265
+ value={{ onClose: () => setMenuOpen(false) }}
266
+ >
267
+ {actions.map((item) => (
268
+ <DropdownMenuItem
269
+ key={item.label}
270
+ onClick={item.onClick}
271
+ icon={
272
+ item.icon ? (
273
+ <DynamicIcon name={item.icon} className="w-4 h-4" />
274
+ ) : undefined
275
+ }
276
+ className={
277
+ item.variant === "destructive"
278
+ ? "text-destructive hover:text-destructive"
279
+ : undefined
280
+ }
281
+ >
282
+ {item.label}
283
+ </DropdownMenuItem>
284
+ ))}
285
+ </MenuCloseContext.Provider>
286
+ </PopoverContent>
287
+ </Popover>
288
+ )}
201
289
  {onDelete && (
202
290
  <Button
203
291
  type="button"
@@ -0,0 +1,57 @@
1
+ import type { SVGProps } from "react";
2
+ import type { BrandIconName } from "@checkstack/common";
3
+
4
+ // Brand marks vendored as inline SVGs.
5
+ //
6
+ // lucide-react v1 removed ALL brand icons (GitHub, GitLab, ...) for trademark
7
+ // reasons, so we ship the handful we actually need ourselves. Path data is the
8
+ // official mark from Simple Icons (https://simpleicons.org), ISC/CC0. Props
9
+ // mirror the slice of the lucide icon surface our call sites use (`className` +
10
+ // pass-through SVG props), so these are drop-in replacements for the old
11
+ // `import { Github } from "lucide-react"`. `fill="currentColor"` makes them
12
+ // inherit text color like a lucide icon does via `stroke="currentColor"`.
13
+
14
+ type BrandIconProps = SVGProps<SVGSVGElement>;
15
+
16
+ export const GithubIcon = ({ className, ...props }: BrandIconProps) => (
17
+ <svg
18
+ viewBox="0 0 24 24"
19
+ width={24}
20
+ height={24}
21
+ fill="currentColor"
22
+ className={className}
23
+ aria-hidden="true"
24
+ {...props}
25
+ >
26
+ <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
27
+ </svg>
28
+ );
29
+
30
+ export const GitlabIcon = ({ className, ...props }: BrandIconProps) => (
31
+ <svg
32
+ viewBox="0 0 24 24"
33
+ width={24}
34
+ height={24}
35
+ fill="currentColor"
36
+ className={className}
37
+ aria-hidden="true"
38
+ {...props}
39
+ >
40
+ <path d="m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z" />
41
+ </svg>
42
+ );
43
+
44
+ /**
45
+ * Brand-name -> component map. Used by `DynamicIcon` to resolve a data-driven
46
+ * icon name (e.g. an auth strategy's `icon: "Github"`) to a vendored brand
47
+ * mark, since these names no longer exist in lucide's `icons` export. Keys are
48
+ * the `BrandIconName` union from `@checkstack/common`, so adding a brand there
49
+ * without a component here is a type error.
50
+ */
51
+ export const brandIcons: Record<
52
+ BrandIconName,
53
+ (props: BrandIconProps) => React.JSX.Element
54
+ > = {
55
+ Github: GithubIcon,
56
+ Gitlab: GitlabIcon,
57
+ };
@@ -2,12 +2,77 @@
2
2
  // `@typefox/monaco-editor-react`-backed `TypefoxEditor` (real VS Code language
3
3
  // services in the browser). Consumers (DynamicForm, automation, healthcheck)
4
4
  // import this and are unaffected by the underlying editor implementation.
5
- import { TypefoxEditor } from "./TypefoxEditor";
5
+ import { useEffect, useState, type ComponentType } from "react";
6
+ import { Maximize2 } from "lucide-react";
7
+ import type { TypefoxEditorProps } from "./TypefoxEditor";
8
+ import { popoutTitle } from "./popoutTitle";
6
9
  import type { CodeEditorProps } from "./types";
10
+ import {
11
+ Dialog,
12
+ DialogContent,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from "../Dialog";
16
+ import { Skeleton } from "../Skeleton";
17
+ import { usePerformance } from "../PerformanceProvider";
18
+ import { cn } from "../../utils";
19
+
20
+ // Load the Monaco-backed editor chunk on demand WITHOUT React.lazy/Suspense.
21
+ //
22
+ // The `import("./TypefoxEditor")` is still dynamic, so the whole `@codingame/*`
23
+ // / Monaco stack stays in its own chunk and off pages that merely import
24
+ // primitives from the `@checkstack/ui` barrel (e.g. the login page) - the
25
+ // bundle-split goal is preserved. But we deliberately do NOT use `React.lazy` +
26
+ // `<Suspense>`: suspending THROUGH `@typefox`'s `MonacoEditorReactComp` makes
27
+ // React (under StrictMode's mount -> unmount -> remount) re-run the wrapper's
28
+ // ONE-SHOT global monaco-vscode init against already-initialized services,
29
+ // throwing "Services are already initialized" so the editor never reaches
30
+ // `onEditorStartDone` and never renders (dev-only; the prod Rollup build is
31
+ // fine). The wrapper expects a stable, non-suspending parent. So we resolve the
32
+ // chunk imperatively and render the wrapper only once it's loaded - a plain,
33
+ // synchronous mount, exactly as the pre-#236 static import behaved.
34
+ let editorModulePromise: Promise<typeof import("./TypefoxEditor")> | undefined;
35
+ const loadEditorModule = (): Promise<typeof import("./TypefoxEditor")> =>
36
+ (editorModulePromise ??= import("./TypefoxEditor"));
37
+
38
+ const useTypefoxEditor = (): ComponentType<TypefoxEditorProps> | null => {
39
+ const [Editor, setEditor] = useState<ComponentType<TypefoxEditorProps> | null>(
40
+ null,
41
+ );
42
+ useEffect(() => {
43
+ let active = true;
44
+ void loadEditorModule().then((mod) => {
45
+ if (active) setEditor(() => mod.TypefoxEditor);
46
+ });
47
+ return () => {
48
+ active = false;
49
+ };
50
+ }, []);
51
+ return Editor;
52
+ };
53
+
54
+ /**
55
+ * Placeholder shown while the Monaco chunk loads. A plain `Skeleton` block
56
+ * (which already honours `usePerformance` / `isLowPower`) sized to the editor's
57
+ * height, so the layout doesn't jump and low-power devices avoid a heavy
58
+ * spinner.
59
+ */
60
+ const EditorLoadingFallback = ({ minHeightPx }: { minHeightPx: number }) => (
61
+ <Skeleton
62
+ className="w-full rounded-md"
63
+ style={{ height: `${minHeightPx}px` }}
64
+ />
65
+ );
7
66
 
8
67
  /**
9
68
  * Code editor with context-aware IntelliSense, template / shell completion, and
10
69
  * structural validation. See `CodeEditorProps`.
70
+ *
71
+ * Renders the inline editor with a subtle top-right "expand" affordance that
72
+ * opens the SAME editor (same `value` / `onChange` / completion props) in a
73
+ * large full-screen overlay for comfortably editing big scripts. Both the
74
+ * inline and overlay editors are `TypefoxEditor` instances bound to the same
75
+ * controlled `value`, so edits stay in sync and closing the overlay keeps them.
11
76
  */
12
77
  export const CodeEditor = ({
13
78
  id,
@@ -21,25 +86,103 @@ export const CodeEditor = ({
21
86
  templateProperties,
22
87
  shellEnvVars,
23
88
  markers,
89
+ acquireTypes,
90
+ acquireResetKey,
91
+ sdkTypes,
92
+ sdkTypesResetKey,
93
+ importablePackages,
94
+ allowPopout = true,
95
+ title,
96
+ deferInit,
24
97
  }: CodeEditorProps) => {
98
+ const [popoutOpen, setPopoutOpen] = useState(false);
99
+ const { isLowPower } = usePerformance();
100
+ // Resolved Monaco editor component (null until its chunk loads). Loaded
101
+ // imperatively rather than via React.lazy/Suspense - see loadEditorModule.
102
+ const TypefoxEditor = useTypefoxEditor();
103
+
25
104
  // CodeEditorProps.minHeight is a CSS length string ("240px"); TypefoxEditor
26
105
  // takes a pixel number.
27
106
  const minHeightPx = Number.parseInt(minHeight, 10) || 100;
107
+ const editorId = id ?? "code-editor";
108
+
109
+ // Shared editor props for both the inline and overlay instances. The overlay
110
+ // instance overrides only `id` (a distinct model URI so the two Monaco models
111
+ // don't fight) and `fillHeight` (so it fills the tall dialog body). Both are
112
+ // bound to the same `value` / `onChange`, keeping edits in sync.
113
+ const sharedEditorProps: Omit<TypefoxEditorProps, "id" | "fillHeight"> = {
114
+ value,
115
+ onChange,
116
+ language,
117
+ minHeight: minHeightPx,
118
+ readOnly,
119
+ placeholder,
120
+ typeDefinitions,
121
+ templateProperties,
122
+ shellEnvVars,
123
+ markers,
124
+ acquireTypes,
125
+ acquireResetKey,
126
+ sdkTypes,
127
+ sdkTypesResetKey,
128
+ importablePackages,
129
+ deferInit,
130
+ };
131
+
132
+ const dialogTitle = title ?? popoutTitle({ language });
28
133
 
29
134
  return (
30
- <TypefoxEditor
31
- id={id ?? "code-editor"}
32
- value={value}
33
- onChange={onChange}
34
- language={language}
35
- minHeight={minHeightPx}
36
- readOnly={readOnly}
37
- placeholder={placeholder}
38
- typeDefinitions={typeDefinitions}
39
- templateProperties={templateProperties}
40
- shellEnvVars={shellEnvVars}
41
- markers={markers}
42
- />
135
+ <div className="relative">
136
+ {TypefoxEditor ? (
137
+ <TypefoxEditor id={editorId} {...sharedEditorProps} />
138
+ ) : (
139
+ <EditorLoadingFallback minHeightPx={minHeightPx} />
140
+ )}
141
+ {allowPopout && (
142
+ <button
143
+ type="button"
144
+ aria-label="Expand editor"
145
+ title="Expand editor"
146
+ onClick={() => setPopoutOpen(true)}
147
+ className={cn(
148
+ // Sits above the editor in the top-right corner. A faint background
149
+ // keeps the muted icon legible over code; hover emphasises it.
150
+ "absolute right-2 top-2 z-10 inline-flex h-7 w-7 items-center justify-center",
151
+ "rounded-md bg-background/70 text-muted-foreground",
152
+ !isLowPower && "backdrop-blur-sm",
153
+ "transition-colors hover:bg-accent hover:text-accent-foreground",
154
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
155
+ )}
156
+ >
157
+ <Maximize2 className="h-4 w-4" />
158
+ </button>
159
+ )}
160
+ <Dialog open={popoutOpen} onOpenChange={setPopoutOpen}>
161
+ {/* Tall flex column: override the default `overflow-y-auto` so the
162
+ editor (not the dialog) scrolls internally, and give the body a
163
+ fixed tall height so the `fillHeight` editor has something to fill. */}
164
+ <DialogContent
165
+ size="full"
166
+ className="flex h-[85dvh] flex-col overflow-hidden overflow-y-hidden"
167
+ >
168
+ <DialogHeader>
169
+ <DialogTitle>{dialogTitle}</DialogTitle>
170
+ </DialogHeader>
171
+ {/* The second Monaco instance only mounts while the dialog is open,
172
+ so there's no double-editor cost when closed. Distinct
173
+ `${id}-popout` id => distinct model URI. */}
174
+ {popoutOpen && TypefoxEditor && (
175
+ <div className="flex-1 min-h-0">
176
+ <TypefoxEditor
177
+ id={`${editorId}-popout`}
178
+ fillHeight
179
+ {...sharedEditorProps}
180
+ />
181
+ </div>
182
+ )}
183
+ </DialogContent>
184
+ </Dialog>
185
+ </div>
43
186
  );
44
187
  };
45
188
 
@@ -49,6 +192,8 @@ export type {
49
192
  TemplateProperty,
50
193
  ShellEnvVar,
51
194
  EditorMarker,
195
+ AcquireTypes,
196
+ AcquiredTypeFile,
52
197
  } from "./types";
53
198
 
54
199
  export {