@checkstack/ui 1.11.0 → 1.12.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.
- package/.storybook/main.ts +43 -0
- package/CHANGELOG.md +181 -0
- package/package.json +4 -4
- package/scripts/generate-stdlib-types.ts +23 -0
- package/src/components/ActionCard.tsx +96 -8
- package/src/components/CodeEditor/CodeEditor.tsx +95 -14
- package/src/components/CodeEditor/TypefoxEditor.tsx +279 -123
- package/src/components/CodeEditor/generated/builtin-modules.json +1 -0
- package/src/components/CodeEditor/importSpecifiers.test.ts +286 -0
- package/src/components/CodeEditor/importSpecifiers.ts +267 -0
- package/src/components/CodeEditor/index.ts +24 -0
- package/src/components/CodeEditor/monacoTsService.ts +217 -0
- package/src/components/CodeEditor/popoutTitle.test.ts +37 -0
- package/src/components/CodeEditor/popoutTitle.ts +31 -0
- package/src/components/CodeEditor/scriptDiagnostics.test.ts +135 -0
- package/src/components/CodeEditor/scriptDiagnostics.ts +172 -0
- package/src/components/CodeEditor/types.ts +59 -0
- package/src/components/CodeEditor/validateScripts.ts +132 -0
- package/src/components/Dialog.tsx +32 -11
- package/src/components/DurationInput.tsx +121 -0
- package/src/components/DynamicForm/DynamicForm.tsx +25 -1
- package/src/components/DynamicForm/FormField.tsx +109 -1
- package/src/components/DynamicForm/MultiTypeEditorField.tsx +67 -2
- package/src/components/DynamicForm/SecretEnvEditor.tsx +315 -0
- package/src/components/DynamicForm/index.ts +6 -0
- package/src/components/DynamicForm/secretEnv.logic.test.ts +126 -0
- package/src/components/DynamicForm/secretEnv.logic.ts +87 -0
- package/src/components/DynamicForm/types.ts +72 -1
- package/src/components/DynamicForm/utils.ts +32 -0
- package/src/components/Popover.tsx +6 -1
- package/src/components/ScriptTestPanel.logic.test.ts +139 -0
- package/src/components/ScriptTestPanel.logic.ts +137 -0
- package/src/components/ScriptTestPanel.tsx +394 -0
- package/src/components/Sheet.tsx +21 -6
- package/src/components/TimeOfDayInput.tsx +116 -0
- package/src/components/comboboxInteraction.ts +39 -0
- package/src/components/portalContainer.ts +24 -0
- package/src/index.ts +4 -0
- package/stories/ActionCard.stories.tsx +60 -0
- package/stories/CodeEditor.stories.tsx +47 -2
- package/stories/DurationInput.stories.tsx +59 -0
- package/stories/ScriptTestPanel.stories.tsx +106 -0
- package/stories/SecretEnvEditor.stories.tsx +80 -0
- package/stories/TimeOfDayInput.stories.tsx +34 -0
- package/tsconfig.json +1 -0
package/.storybook/main.ts
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import type { StorybookConfig } from "@storybook/react-vite";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { mergeConfig } from "vite";
|
|
6
|
+
|
|
7
|
+
const storybookDir = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const uiRoot = path.resolve(storybookDir, "..");
|
|
9
|
+
const localRequire = createRequire(import.meta.url);
|
|
10
|
+
|
|
11
|
+
// Resolve the `vscode` npm-alias (= @codingame/monaco-vscode-extension-api) to
|
|
12
|
+
// an absolute path so Vite/Rolldown can alias it. This mirrors the same fix in
|
|
13
|
+
// core/frontend/vite.config.ts.
|
|
14
|
+
//
|
|
15
|
+
// `CodeEditor` (via @typefox/monaco-editor-react + monaco-languageclient) pulls
|
|
16
|
+
// in the @codingame/monaco-vscode-* stack, whose runtime package does
|
|
17
|
+
// `require("vscode")`. Under bun's isolated node_modules the `"vscode":
|
|
18
|
+
// "npm:@codingame/monaco-vscode-extension-api"` alias only exists inside the
|
|
19
|
+
// typefox / monaco-languageclient scopes, so the Storybook build can't resolve
|
|
20
|
+
// a bare `vscode` import and fails with "Rolldown failed to resolve import
|
|
21
|
+
// 'vscode'". We resolve the alias *through* @typefox so the path follows bun's
|
|
22
|
+
// store layout on any machine/CI rather than being hardcoded.
|
|
23
|
+
const typefoxDir = path.dirname(
|
|
24
|
+
localRequire.resolve("@typefox/monaco-editor-react", { paths: [uiRoot] }),
|
|
25
|
+
);
|
|
26
|
+
const vscodeApiDir = path.dirname(
|
|
27
|
+
localRequire.resolve("vscode", { paths: [typefoxDir] }),
|
|
28
|
+
);
|
|
2
29
|
|
|
3
30
|
const config: StorybookConfig = {
|
|
4
31
|
stories: [
|
|
@@ -17,6 +44,22 @@ const config: StorybookConfig = {
|
|
|
17
44
|
typescript: {
|
|
18
45
|
reactDocgen: "react-docgen",
|
|
19
46
|
},
|
|
47
|
+
viteFinal: (viteConfig) =>
|
|
48
|
+
mergeConfig(viteConfig, {
|
|
49
|
+
// The @typefox/monaco-editor-react + @codingame/monaco-vscode-* stack
|
|
50
|
+
// loads its language services in ES module workers.
|
|
51
|
+
worker: {
|
|
52
|
+
format: "es",
|
|
53
|
+
},
|
|
54
|
+
resolve: {
|
|
55
|
+
alias: {
|
|
56
|
+
// Alias the `vscode` npm-alias to its real package dir so @codingame's
|
|
57
|
+
// CJS `require("vscode")` resolves under bun's isolated store instead
|
|
58
|
+
// of failing the build.
|
|
59
|
+
vscode: vscodeApiDir,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
20
63
|
};
|
|
21
64
|
|
|
22
65
|
export default config;
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,186 @@
|
|
|
1
1
|
# @checkstack/ui
|
|
2
2
|
|
|
3
|
+
## 1.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b995afb: Redesign the automation visual editor to a Home-Assistant-style collapsed-card UX.
|
|
8
|
+
|
|
9
|
+
Every item in all three sections (actions, triggers, conditions) now renders as a compact summary row by default - icon, title, and a one-line summary derived from its config. Clicking the row opens the item's full configuration in a right-side sheet that edits the same live definition (no draft/commit step), so closing the sheet keeps the changes. The saved `definition` is unchanged - only the editor presentation - so the visual and YAML views still round-trip losslessly.
|
|
10
|
+
|
|
11
|
+
- `@checkstack/ui` `ActionCard` gains three optional, backward-compatible props: `onOpenSheet` (turns the card into a non-expanding summary row that opens a host-supplied sheet on header click), `summary` (the compact one-line hint shown under the title), and `actions` (a typed `ActionCardMenuItem[]` rendered as a three-dot overflow menu). The new `ActionCardMenuItem` type is exported. Existing inline-expand usages are unaffected when the new props are omitted.
|
|
12
|
+
- Per-card commands move into the overflow menu: Disable/Enable, a new Duplicate, and Delete. The drag grip stays on the action card header; actions keep dnd-kit reordering and the parallel id array. Triggers and conditions remain non-reorderable.
|
|
13
|
+
- Duplicate clones an item with fresh, unique ids (via the existing id helpers) and inserts it directly after the original, keeping the editor's parallel id array in sync.
|
|
14
|
+
- Composite actions (choose / parallel / repeat / sequence) keep nesting: a child card inside a parent's sheet opens its OWN sheet, stacking via Radix Dialog's portal + overlay.
|
|
15
|
+
- Cards with validation errors auto-open their sheet and show an error badge on the collapsed row, so problems are never hidden behind a collapsed row plus a closed sheet.
|
|
16
|
+
|
|
17
|
+
- 270ef29: Add the sensing-layer editor UX (Wave 2 Phase 19) - the visual widgets for the duration-aware and structured-condition building blocks from Phases 15-18.
|
|
18
|
+
|
|
19
|
+
- New `@checkstack/ui` components (each with a Storybook story):
|
|
20
|
+
- `DurationInput` - number + unit (`seconds` / `minutes` / `hours`) picker emitting the single-unit `Duration` object the backend accepts, so it round-trips losslessly through YAML.
|
|
21
|
+
- `TimeOfDayInput` - HH:MM (24h) input emitting the `"HH:mm"` string the `time` condition's `after` / `before` accept. Both are plain inputs (no animations), so no `usePerformance` gating is needed.
|
|
22
|
+
- `DynamicForm`'s `FormField` gains an additive `x-duration` / `format: "duration"` branch that renders `DurationInput` for schema-driven duration configs. (Additive alongside the existing dispatch; reconciles cleanly with the parallel branch's `FormField` edits.)
|
|
23
|
+
- The `ConditionEditor` kind selector gains `numeric_state` / `time` / `state` structured branches: an operator dropdown (above / below / between) + threshold for numeric, `TimeOfDayInput` + weekday toggles + timezone for time, and a status dropdown + optional `DurationInput` dwell for state. The raw-expression escape hatch is kept. Pure `kindOf` / `defaultForKind` helpers are split into a UI-free `condition-kind` module so they unit-test under bun (the UI barrel drags Monaco).
|
|
24
|
+
- The trigger card gains a `for:` dwell toggle + `DurationInput` (Phase 15's schema was already round-tripping in YAML).
|
|
25
|
+
|
|
26
|
+
Visual and YAML views stay lossless; structured conditions authored in either are editable in the other.
|
|
27
|
+
|
|
28
|
+
- b995afb: Surface inline-script type errors as automation action badges.
|
|
29
|
+
|
|
30
|
+
Every inline `run_script` action in the automation editor is now type-checked
|
|
31
|
+
against its generated `context` types continuously - including actions whose
|
|
32
|
+
cards are collapsed - and any errors show up as the action card's error badge
|
|
33
|
+
(and in the definition issue list), the same surface structural validation
|
|
34
|
+
uses. Previously a type error was only visible as a red squiggle inside the
|
|
35
|
+
open Monaco editor, so a broken script behind a collapsed card (or one
|
|
36
|
+
invalidated by adding a new trigger) went unnoticed until runtime, where the
|
|
37
|
+
bad property access silently read `undefined`.
|
|
38
|
+
|
|
39
|
+
Validation runs entirely in the browser via the same standalone TypeScript
|
|
40
|
+
worker the editor uses (new `validateTypeScriptSources` export on
|
|
41
|
+
`@checkstack/ui`), so there is no backend round-trip. Each script is checked by
|
|
42
|
+
prepending its generated `context.d.ts` to the source, which keeps the
|
|
43
|
+
`context` global scoped to that one off-screen file and avoids colliding with
|
|
44
|
+
any open editor. When an automation already contains scripts, a hidden editor
|
|
45
|
+
boots the shared editor services on open so validation runs immediately rather
|
|
46
|
+
than only after the first script card is expanded.
|
|
47
|
+
|
|
48
|
+
This covers the automation currently open in the editor. Scripts in other
|
|
49
|
+
automations, or definitions authored via YAML/API, are not type-checked here -
|
|
50
|
+
that platform-wide coverage remains future work for a backend typecheck.
|
|
51
|
+
|
|
52
|
+
Also: action cards no longer auto-open their detail sheet when they have
|
|
53
|
+
validation issues; issues now surface only as the card badge, so multiple
|
|
54
|
+
flagged actions no longer pop several sheets open at once.
|
|
55
|
+
|
|
56
|
+
- b995afb: Improve the automation Run Script secret → env mapping editor and script IntelliSense.
|
|
57
|
+
|
|
58
|
+
- **Searchable secret picker with existence validation.** The secret → env mapping editor (`SecretEnvEditor`) now uses a searchable, keyboard-navigable combobox (modeled on `VariablePicker` / `PackageNameCombobox`, `isLowPower`-aware) populated from the secrets plugin's `listSecretNames`, replacing the plain `<input>` + `<datalist>`. A free-typed name still round-trips (a secret may be created later). When a row references a name that the loaded list does not contain, the row shows a non-blocking warning (red border + message); save is not prevented. The existence check lives in a pure, unit-tested `unknownSecretNames` helper.
|
|
59
|
+
- **Clearer field description.** The `secretEnv` field descriptions on the `run_script` / `run_shell` actions no longer show the stored `${{ secrets.NAME }}` template (which is confusing in a UI that takes a bare name); they now describe the actual UI behavior and how the value is injected (`process.env.<ENV_NAME>` / `$<ENV_NAME>`) and masked.
|
|
60
|
+
- **`process.env.<ENV_NAME>` autocomplete.** Declared `secretEnv` env-var names now autocomplete under `process.env.` in the Run Script (TypeScript) Monaco editor and are typed `string`, via an ambient `NodeJS.ProcessEnv` augmentation merged into the editor type definitions. New pure, unit-tested generators `generateSecretEnvTypes` and `secretEnvEnvNames` (exported from `@checkstack/automation-frontend`) drive this; the augmentation coexists with `@types/node`'s existing index signature.
|
|
61
|
+
- **Shared combobox-interaction helper.** The "opens-then-immediately-closes" popover guard (`comboboxAnchorProps` / `isAnchorInteraction`) is promoted from `@checkstack/script-packages-frontend` into `@checkstack/ui` so the new secret picker and the existing package/version comboboxes share one implementation; the package comboboxes now import it from `@checkstack/ui` and the local copy is removed.
|
|
62
|
+
|
|
63
|
+
- b995afb: Add an "expand to overlay" popout to the shared `CodeEditor` so big scripts (shell / TypeScript / JavaScript) can be edited comfortably in a large full-screen overlay.
|
|
64
|
+
|
|
65
|
+
Every consumer of `CodeEditor` (automation Run Script, healthcheck collectors, etc.) now gets a subtle "Expand editor" affordance (a `Maximize2` icon button) in the editor's top-right corner. Clicking it opens the shared `Dialog` at `size="full"` containing a large editor that fills the dialog.
|
|
66
|
+
|
|
67
|
+
- The overlay editor is a second `TypefoxEditor` instance bound to the SAME `value` / `onChange` and all the same completion props (`typeDefinitions`, `templateProperties`, `shellEnvVars`, `markers`, `acquireTypes`, `acquireResetKey`, `importablePackages`, `language`, `readOnly`, `placeholder`), so IntelliSense / ATA / import-name / shell-var completion all work in the overlay exactly as inline. Both editors are controlled on the same value, so edits stay in sync and closing the dialog keeps them.
|
|
68
|
+
- The overlay editor only MOUNTS while the dialog is open (lazy), so there is no second Monaco instance cost when closed. It uses a distinct `${id}-popout` model id so the two Monaco models don't fight over the same URI.
|
|
69
|
+
- New opt-in `TypefoxEditor` prop `fillHeight`: when true the editor container uses `height: 100%` (with `minHeight` as a floor) instead of a fixed px height, so it fills the tall flex dialog body and Monaco's `automaticLayout` resizes to fit. Inline behaviour is unchanged when `fillHeight` is absent/false.
|
|
70
|
+
- `CodeEditorProps` gains two additive optional props: `allowPopout` (default `true`; set `false` to hide the affordance) and `title` (override the overlay dialog title, which otherwise derives from `language`, e.g. "Edit script - TypeScript").
|
|
71
|
+
- `TypefoxEditor` is now properly controlled: external `value` changes are applied to the live model (guarded by an equality check so a user's own edit is a no-op and there's no loop). This is what keeps the inline and popout editors in sync — editing one updates the other — and also fixes external resets (YAML→Visual, loaded definitions) reflecting in the editor.
|
|
72
|
+
- `DialogContent`'s inner content wrapper gains `min-h-0 flex-1` so it fills the height when a consumer makes `DialogContent` a tall flex column (e.g. the popout body). Inert for the default non-flex dialog, so existing dialogs are unaffected.
|
|
73
|
+
|
|
74
|
+
The `Dialog` already degrades its own animations under `usePerformance` / `isLowPower`; the popout button adds no heavy effects.
|
|
75
|
+
|
|
76
|
+
- b995afb: Suggest Node and Bun built-in modules in script-editor import-name completion.
|
|
77
|
+
|
|
78
|
+
The import-specifier completion now also offers the always-available runtime built-ins (`node:fs`, bare `fs`, `bun`, `bun:test`, `node:crypto`, ...) alongside the installed allowlist packages. These are importable in the script sandbox regardless of the allowlist (the sandbox is a Bun subprocess, which provides Node's builtins plus its own `bun:` modules), and their types are already loaded ambiently, so completing one needs no lazy acquisition.
|
|
79
|
+
|
|
80
|
+
- The built-in name list is DERIVED authoritatively at build time from the same bundled `@types/node` + `bun-types` declarations the editor injects: every importable built-in is a top-level `declare module "<spec>"`, so the generator (`scripts/generate-stdlib-types.ts`) now also parses those names (via the new pure `extractBuiltinModuleSpecifiers`) and emits `generated/builtin-modules.json`. No hand-maintained list - it auto-updates whenever the bundled types are regenerated. Wildcard / asset-glob ambient shims (names containing a star, e.g. asset globs or a `bun.lock` path glob) are filtered out.
|
|
81
|
+
- The completion provider merges built-ins with the injected installed packages (deduped + sorted via the pure `mergeImportCompletionEntries`), labelling each via `detail` ("Node.js" / "Bun built-in" / "installed package"). Built-ins appear even when the allowlist is empty; the provider still only fires inside an import-string position and coexists with the TS worker's own completions.
|
|
82
|
+
|
|
83
|
+
The existing node/bun stdlib TYPE hosting is unchanged (still injected from the separately code-split `stdlib-types.json` asset), so global completions (`process.*`, `Buffer`, ...) and member completions (`import * as fs from "node:fs"`) are unaffected. New pure helpers are fully unit-tested; the Monaco glue is untested per the no-DOM rule.
|
|
84
|
+
|
|
85
|
+
- 270ef29: Add in-UI script testing for automation `run_script` / `run_shell` actions.
|
|
86
|
+
|
|
87
|
+
A new `testScript` RPC runs a TypeScript or shell script against an
|
|
88
|
+
editable, auto-seeded sample context using the same sandboxed runner the
|
|
89
|
+
real action uses, so operators can test scripts directly in the editor
|
|
90
|
+
without dispatching a whole automation. Surfaces beneath any script field
|
|
91
|
+
flagged `x-script-testable` via the new `ScriptTestPanel` /
|
|
92
|
+
`ContextSampleEditor` components in `@checkstack/ui` and the
|
|
93
|
+
`scriptTestRenderer` prop threaded through `DynamicForm`.
|
|
94
|
+
|
|
95
|
+
- `@checkstack/automation-common`: adds the `testScript` contract +
|
|
96
|
+
`ScriptTest*` schemas (gated by `automation.manage`).
|
|
97
|
+
- `@checkstack/automation-backend`: implements `testScript` reusing the
|
|
98
|
+
shared ESM / shell runners; central-only, time-bounded.
|
|
99
|
+
- `@checkstack/backend-api`: new `x-script-testable` config-schema
|
|
100
|
+
metadata propagated to the frontend JSON Schema.
|
|
101
|
+
- `@checkstack/ui`: new `ScriptTestPanel` + `ContextSampleEditor`
|
|
102
|
+
components and a `scriptTestRenderer` prop on `DynamicForm`.
|
|
103
|
+
- `@checkstack/automation-frontend`: wires the test panel into the action
|
|
104
|
+
editor.
|
|
105
|
+
- `@checkstack/integration-script-backend`: marks the `run_script` /
|
|
106
|
+
`run_shell` script fields as testable.
|
|
107
|
+
|
|
108
|
+
- b995afb: Autocomplete the import specifier itself in script editors.
|
|
109
|
+
|
|
110
|
+
Lazy type acquisition only loads a package's types once its name is already in the buffer, so while you were still typing the import specifier (`import {} from "lod"`) there were no suggestions - the lazy-ATA catch-22. Script editors now suggest installed package names directly in import-specifier position; selecting one (e.g. `lodash`) inserts the name, and the existing ATA loop then loads its `@types/lodash` closure so members complete.
|
|
111
|
+
|
|
112
|
+
- `@checkstack/ui`: `CodeEditor`/`TypefoxEditor` gained an injected `importablePackages?: string[]` prop and a dedicated Monaco completion provider (registered once per `typescript`/`javascript` language, scoped to the editor's model, disposed on unmount). It fires ONLY when the cursor is inside an import/require module-specifier string - detected by a new pure, unit-tested helper `importSpecifierCompletionContext(lineUpToCursor)` that handles `from "…"`, bare `import "…"`, `require("…")`, and dynamic `import("…")`, returns the partial specifier + the replace range, and returns null once the string is closed or outside an import. Items are `kind: Module`, insert the bare name without touching the quotes, and coexist with (do not replace) the TS worker's own completions. Trigger characters: `"`, `'`, and `/` (for scoped subpaths); manual invoke (Ctrl+Space) also works. A new pure helper `importablePackageNames` filters a raw manifest name list (excludes `@types/*`, dedupes, sorts).
|
|
113
|
+
- `@checkstack/script-packages-frontend`: `useScriptPackageTypeAcquisition()` now also returns `importablePackages`, derived from the installed manifest (what is actually resolvable at runtime) with `@types/*` companions excluded - you import `lodash`, never `@types/lodash` (the `@types` package still backs the closure types).
|
|
114
|
+
- `@checkstack/automation-frontend` / `@checkstack/healthcheck-frontend`: pass `importablePackages` into `DynamicForm` alongside the existing `acquireTypes` wiring, so both the Run Script action editor and healthcheck collector editors get import-name completion.
|
|
115
|
+
|
|
116
|
+
The completion list is plugin-agnostic in `@checkstack/ui` (the names are injected); it never fires outside import-string positions, so normal completions are unaffected.
|
|
117
|
+
|
|
118
|
+
- b995afb: Fix package IntelliSense in script editors: lazy Automatic Type Acquisition (ATA) with proper `@types/*` resolution.
|
|
119
|
+
|
|
120
|
+
Script editors (automation "Run Script (TypeScript)" and healthcheck collectors) now provide real autocomplete for installed npm packages. Importing a package whose types live in DefinitelyTyped - e.g. `import { debounce } from "lodash"` (lodash ships no own types; `@types/lodash` does) - now yields member completions. Previously no package completions appeared at all.
|
|
121
|
+
|
|
122
|
+
Root cause: the old rollup wrapped each package's raw, multi-file `.d.ts` (with `export =`, `export as namespace`, and triple-slash `/// <reference path>` chains) inside a single `declare module "<name>" { ... }`, which the TypeScript worker silently rejected, and it truncated large type sets (lodash is ~866 KB across ~700 files) at a 256 KB cap.
|
|
123
|
+
|
|
124
|
+
The fix registers the REAL declaration files at their `node_modules/...` virtual paths and lets TypeScript's own NodeJs + `@types` resolution do the work:
|
|
125
|
+
|
|
126
|
+
- `@checkstack/script-packages-backend`: replaced `rollupPackageTypes` with a tree-driven closure extractor (`resolvePackageTypeClosure`). Given a bare specifier, it resolves against the materialized tree - own types via `package.json` `types`/`typings`/`exports` (bundled-types packages like `zod`/`dayjs`), the `@types/<mangled>` companion when it exists (`lodash` -> `@types/lodash`, scoped `@babel/core` -> `@types/babel__core`), or both, or neither (graceful empty, never a throw). It follows `/// <reference path|types>` and relative imports, includes each package's `package.json`, leaves every file UNWRAPPED, and surfaces a `truncated` flag instead of silently capping. Served from a new raw, HTTP-cacheable route `GET /api/script-packages/types/:lockfileHash/:specifier` (`Cache-Control: private, max-age=1y, immutable`), auth-gated by `script-packages.read`.
|
|
127
|
+
- `@checkstack/script-packages-common`: **BREAKING** - replaced the `listPackageTypes` RPC procedure and `PackageTypesSchema { name, version, dts }` with `PackageTypeClosureSchema` (a `{ path, content }` file-map plus `hasOwnTypes`/`hasAtTypes`/`notFound`/`truncated`) served over the cacheable HTTP route. Added a shared `buildTypeAcquisitionPath`/`parseTypeAcquisitionPath` path contract.
|
|
128
|
+
- `@checkstack/ui`: `CodeEditor`/`TypefoxEditor` gained an injected `acquireTypes` resolver + `acquireResetKey`. On debounced buffer change it parses bare `import`/`require` specifiers (pure, unit-tested) and lazily fetches + registers each NEW package's closure via `addExtraLib` at `file:///node_modules/...`, deduped by a shared acquired-set that resets when the install hash changes. Compiler options set `moduleResolution: NodeJs`, `baseUrl: "file:///"`, and `typeRoots` so a bare import resolves to its `@types` companion. The `context` ambient global keeps working unchanged.
|
|
129
|
+
- `@checkstack/script-packages-frontend`: replaced the old `useScriptPackageTypes` (which concatenated the broken `dts`) with `useScriptPackageTypeAcquisition()`, returning the `acquireTypes` resolver (targets the cacheable route, zod-validates the response) and the current `lockfileHash` as `acquireResetKey`.
|
|
130
|
+
- `@checkstack/automation-frontend` / `@checkstack/healthcheck-frontend`: wired the resolver into the Run Script and collector editors.
|
|
131
|
+
|
|
132
|
+
State & scale: the type closure is derived on read from the materialized package tree (no new durable state). The editor's acquired-set is pod-local UI bookkeeping; the route is keyed by the cluster-wide `lockfileHash`, so the browser HTTP cache is correct across pods and only refetches after a new install changes the hash.
|
|
133
|
+
|
|
134
|
+
- b995afb: Fix the automation Run Script action's `secretEnv` (secret → env mapping) test wiring and tolerate bare secret names.
|
|
135
|
+
|
|
136
|
+
- `@checkstack/ui` `ScriptTestPanel` now accepts the script field's declared `secretEnv` and renders an optional per-secret test-override input. The `ScriptTestRenderer` callback (DynamicForm) receives the SIBLING `x-secret-env` mapping value, located by annotation (not by field name), so a testable script field forwards it to the panel. Previously the test path never sent `secretEnv`, so `buildTestSecretEnv` got `undefined` and `process.env.<env>` was undefined in an in-UI test. Now an override-less test injects `__SECRET_<NAME>__` placeholders, and any operator override is masked from the output. Real secret values are still NEVER resolved in the test path.
|
|
137
|
+
- `@checkstack/automation-frontend` forwards the action's `secretEnv` and the collected overrides to `testScript`.
|
|
138
|
+
- `@checkstack/secrets-common`: the `secretEnv` mapping VALUE now accepts EITHER a `${{ secrets.NAME }}` template OR a bare secret name, normalizing a bare name to the canonical `${{ secrets.NAME }}` template on parse. This is a forgiving / NARROWING input change (more inputs accepted; stored/output form is unchanged and still the template), not a breaking change. Existing data and YAML shorthand like `secretEnv: { secret: SECRET }` now pass config validation instead of failing with "Must contain a ${{ secrets.NAME }} reference". Partial inline interpolation (e.g. `u:${{ secrets.pw }}@host`) keeps working unchanged; values that are neither a secret reference nor a valid secret name are still rejected.
|
|
139
|
+
- `@checkstack/ui` `parseSecretName` tolerates a legacy bare secret name for display so the picker shows the same name for both the template and the bare form.
|
|
140
|
+
|
|
141
|
+
The healthcheck collector test panel was checked: its config has no `x-secret-env` field, so it needed no secret wiring (only the `onRun` signature change, which is backward compatible).
|
|
142
|
+
|
|
143
|
+
- 270ef29: Secrets platform Phase 2: secret -> env-var mapping with central resolve, inject, and mask.
|
|
144
|
+
|
|
145
|
+
- Script consumers declare a least-privilege `secretEnv` allowlist
|
|
146
|
+
(`{ ENV_NAME: "${{ secrets.NAME }}" }`). The automation `run_script` /
|
|
147
|
+
`run_shell` actions resolve ONLY the declared secrets via
|
|
148
|
+
`secretResolverRef.resolveForRun`, inject them into the runner env for
|
|
149
|
+
that run (memory-only; the ESM runner gained a per-run `env` option), and
|
|
150
|
+
mask their values out of stdout/stderr/result/error via the run-scoped
|
|
151
|
+
masking context. A missing required secret fails the run clearly. No
|
|
152
|
+
ambient secret access.
|
|
153
|
+
- Test panel: `testScript` / `testCollectorScript` inject named
|
|
154
|
+
`__SECRET_<NAME>__` placeholders by default, or user-supplied per-secret
|
|
155
|
+
overrides; real production values are never resolved in the test path,
|
|
156
|
+
and overrides are masked out of the result.
|
|
157
|
+
- Healthcheck collectors carry the `secretEnv` field for authoring +
|
|
158
|
+
the test panel; runtime injection on satellites lands in Phase 3.
|
|
159
|
+
- Editor UX: a new `@checkstack/ui` `SecretEnvEditor` renders `x-secret-env`
|
|
160
|
+
record fields with `${{ secrets.* }}` name autocomplete (from
|
|
161
|
+
`listSecretNames`), wired into the automation action editor and the
|
|
162
|
+
healthcheck collector editor. New `withConfigMeta` helper +
|
|
163
|
+
`x-secret-env` config-meta key in `@checkstack/backend-api`.
|
|
164
|
+
|
|
165
|
+
- b995afb: fix(ui): make Popover/combobox lists scrollable inside a Sheet or Dialog
|
|
166
|
+
|
|
167
|
+
A `Popover` (and the comboboxes built on it, e.g. the automation trigger Event
|
|
168
|
+
picker, the secret-name picker, the package picker) portals its content to
|
|
169
|
+
`document.body`. When opened inside a modal `Sheet`/`Dialog`, the dialog's
|
|
170
|
+
`react-remove-scroll` scroll-lock blocked wheel/touch scrolling on that
|
|
171
|
+
body-portaled content, so a long list's `overflow-y-auto` could not scroll.
|
|
172
|
+
|
|
173
|
+
`SheetContent` and `DialogContent` now publish their content element through a
|
|
174
|
+
`PortalContainerContext`, and `PopoverContent` portals INTO it when present.
|
|
175
|
+
That keeps the popover inside the dialog's allowed-scroll subtree, so its lists
|
|
176
|
+
scroll again. Radix positions popovers with `position: fixed`, so placement and
|
|
177
|
+
clipping are unaffected; outside a Sheet/Dialog the popover still portals to
|
|
178
|
+
`body` as before.
|
|
179
|
+
|
|
180
|
+
- 270ef29: Collapse `ScriptTestPanel` behind a compact disclosure by default.
|
|
181
|
+
|
|
182
|
+
The inline script-test panel previously expanded its sample-context editor and results under every testable script field. It now defaults to collapsed: a compact "Test script" affordance shows, and the panel expands on demand. Running a test still auto-expands the results, and the last run's outcome surfaces as a badge while collapsed. A new `defaultOpen` prop opts back into the always-expanded behaviour.
|
|
183
|
+
|
|
3
184
|
## 1.11.0
|
|
4
185
|
|
|
5
186
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@checkstack/common": "0.
|
|
9
|
-
"@checkstack/frontend-api": "0.
|
|
8
|
+
"@checkstack/common": "0.12.0",
|
|
9
|
+
"@checkstack/frontend-api": "0.6.0",
|
|
10
10
|
"@codingame/monaco-vscode-editor-api": "25.1.2",
|
|
11
11
|
"@codingame/monaco-vscode-languages-service-override": "25.1.2",
|
|
12
12
|
"@codingame/monaco-vscode-standalone-json-language-features": "25.1.2",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"yaml": "^2.9.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@checkstack/scripts": "0.3.
|
|
41
|
+
"@checkstack/scripts": "0.3.4",
|
|
42
42
|
"@checkstack/test-utils-frontend": "0.0.5",
|
|
43
43
|
"@checkstack/tsconfig": "0.0.7",
|
|
44
44
|
"@storybook/addon-a11y": "^10.3.6",
|
|
@@ -21,10 +21,18 @@
|
|
|
21
21
|
* lives at `src/components/CodeEditor/generated/stdlib-types.json` and is
|
|
22
22
|
* lazy-imported by the editor (so the ~3 MB payload is code-split into its
|
|
23
23
|
* own chunk and never blocks initial page load).
|
|
24
|
+
*
|
|
25
|
+
* It ALSO emits `generated/builtin-modules.json`: the authoritative list of
|
|
26
|
+
* importable built-in specifiers (`node:fs`, bare `fs`, `bun`, `bun:test`, ...)
|
|
27
|
+
* derived from the SAME bundled declarations (every importable built-in is a
|
|
28
|
+
* top-level `declare module "<spec>"`). The editor ships this so the
|
|
29
|
+
* import-name completion provider can suggest sandbox built-ins regardless of
|
|
30
|
+
* the installed-package allowlist, and it auto-updates with the bundled types.
|
|
24
31
|
*/
|
|
25
32
|
import { createRequire } from "node:module";
|
|
26
33
|
import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
|
|
27
34
|
import path from "node:path";
|
|
35
|
+
import { extractBuiltinModuleSpecifiers } from "../src/components/CodeEditor/importSpecifiers";
|
|
28
36
|
|
|
29
37
|
const require = createRequire(import.meta.url);
|
|
30
38
|
|
|
@@ -88,3 +96,18 @@ const totalBytes = Object.values(files).reduce((acc, c) => acc + c.length, 0);
|
|
|
88
96
|
console.log(
|
|
89
97
|
`✅ Wrote ${Object.keys(files).length} files (${(totalBytes / 1024 / 1024).toFixed(2)} MB) → ${path.relative(process.cwd(), outFile)}`,
|
|
90
98
|
);
|
|
99
|
+
|
|
100
|
+
// Derive the authoritative built-in import specifier list from the SAME
|
|
101
|
+
// bundled declarations: every importable built-in (`node:fs`, bare `fs`,
|
|
102
|
+
// `bun`, `bun:test`, ...) is a top-level `declare module "<spec>"`, so the
|
|
103
|
+
// name set falls out of the d.ts text directly. Wildcard/asset-glob shims
|
|
104
|
+
// (`*.txt`, `*/bun.lock`) are filtered out by the extractor. This auto-updates
|
|
105
|
+
// whenever the bundled `@types/node` / `bun-types` are regenerated, so the
|
|
106
|
+
// editor's import-name completions never drift from the runtime stdlib.
|
|
107
|
+
const allDeclarations = Object.values(files).join("\n");
|
|
108
|
+
const builtinModules = extractBuiltinModuleSpecifiers(allDeclarations);
|
|
109
|
+
const builtinsFile = path.join(outDir, "builtin-modules.json");
|
|
110
|
+
await writeFile(builtinsFile, JSON.stringify(builtinModules), "utf8");
|
|
111
|
+
console.log(
|
|
112
|
+
`✅ Wrote ${builtinModules.length} built-in module specifiers → ${path.relative(process.cwd(), builtinsFile)}`,
|
|
113
|
+
);
|
|
@@ -4,6 +4,7 @@ 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";
|
|
@@ -11,8 +12,23 @@ import { Button } from "./Button";
|
|
|
11
12
|
import { Toggle } from "./Toggle";
|
|
12
13
|
import { DynamicIcon, type LucideIconName } 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?: LucideIconName;
|
|
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;
|
|
@@ -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
|
|
112
|
-
|
|
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={
|
|
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
|
-
{
|
|
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"
|
|
@@ -2,12 +2,28 @@
|
|
|
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 {
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { Maximize2 } from "lucide-react";
|
|
7
|
+
import { TypefoxEditor, 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 { cn } from "../../utils";
|
|
7
17
|
|
|
8
18
|
/**
|
|
9
19
|
* Code editor with context-aware IntelliSense, template / shell completion, and
|
|
10
20
|
* structural validation. See `CodeEditorProps`.
|
|
21
|
+
*
|
|
22
|
+
* Renders the inline editor with a subtle top-right "expand" affordance that
|
|
23
|
+
* opens the SAME editor (same `value` / `onChange` / completion props) in a
|
|
24
|
+
* large full-screen overlay for comfortably editing big scripts. Both the
|
|
25
|
+
* inline and overlay editors are `TypefoxEditor` instances bound to the same
|
|
26
|
+
* controlled `value`, so edits stay in sync and closing the overlay keeps them.
|
|
11
27
|
*/
|
|
12
28
|
export const CodeEditor = ({
|
|
13
29
|
id,
|
|
@@ -21,25 +37,88 @@ export const CodeEditor = ({
|
|
|
21
37
|
templateProperties,
|
|
22
38
|
shellEnvVars,
|
|
23
39
|
markers,
|
|
40
|
+
acquireTypes,
|
|
41
|
+
acquireResetKey,
|
|
42
|
+
importablePackages,
|
|
43
|
+
allowPopout = true,
|
|
44
|
+
title,
|
|
24
45
|
}: CodeEditorProps) => {
|
|
46
|
+
const [popoutOpen, setPopoutOpen] = useState(false);
|
|
47
|
+
|
|
25
48
|
// CodeEditorProps.minHeight is a CSS length string ("240px"); TypefoxEditor
|
|
26
49
|
// takes a pixel number.
|
|
27
50
|
const minHeightPx = Number.parseInt(minHeight, 10) || 100;
|
|
51
|
+
const editorId = id ?? "code-editor";
|
|
52
|
+
|
|
53
|
+
// Shared editor props for both the inline and overlay instances. The overlay
|
|
54
|
+
// instance overrides only `id` (a distinct model URI so the two Monaco models
|
|
55
|
+
// don't fight) and `fillHeight` (so it fills the tall dialog body). Both are
|
|
56
|
+
// bound to the same `value` / `onChange`, keeping edits in sync.
|
|
57
|
+
const sharedEditorProps: Omit<TypefoxEditorProps, "id" | "fillHeight"> = {
|
|
58
|
+
value,
|
|
59
|
+
onChange,
|
|
60
|
+
language,
|
|
61
|
+
minHeight: minHeightPx,
|
|
62
|
+
readOnly,
|
|
63
|
+
placeholder,
|
|
64
|
+
typeDefinitions,
|
|
65
|
+
templateProperties,
|
|
66
|
+
shellEnvVars,
|
|
67
|
+
markers,
|
|
68
|
+
acquireTypes,
|
|
69
|
+
acquireResetKey,
|
|
70
|
+
importablePackages,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const dialogTitle = title ?? popoutTitle({ language });
|
|
28
74
|
|
|
29
75
|
return (
|
|
30
|
-
<
|
|
31
|
-
id={
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
76
|
+
<div className="relative">
|
|
77
|
+
<TypefoxEditor id={editorId} {...sharedEditorProps} />
|
|
78
|
+
{allowPopout && (
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
aria-label="Expand editor"
|
|
82
|
+
title="Expand editor"
|
|
83
|
+
onClick={() => setPopoutOpen(true)}
|
|
84
|
+
className={cn(
|
|
85
|
+
// Sits above the editor in the top-right corner. A faint background
|
|
86
|
+
// keeps the muted icon legible over code; hover emphasises it.
|
|
87
|
+
"absolute right-2 top-2 z-10 inline-flex h-7 w-7 items-center justify-center",
|
|
88
|
+
"rounded-md bg-background/70 text-muted-foreground backdrop-blur-sm",
|
|
89
|
+
"transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
90
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
<Maximize2 className="h-4 w-4" />
|
|
94
|
+
</button>
|
|
95
|
+
)}
|
|
96
|
+
<Dialog open={popoutOpen} onOpenChange={setPopoutOpen}>
|
|
97
|
+
{/* Tall flex column: override the default `overflow-y-auto` so the
|
|
98
|
+
editor (not the dialog) scrolls internally, and give the body a
|
|
99
|
+
fixed tall height so the `fillHeight` editor has something to fill. */}
|
|
100
|
+
<DialogContent
|
|
101
|
+
size="full"
|
|
102
|
+
className="flex h-[85dvh] flex-col overflow-hidden overflow-y-hidden"
|
|
103
|
+
>
|
|
104
|
+
<DialogHeader>
|
|
105
|
+
<DialogTitle>{dialogTitle}</DialogTitle>
|
|
106
|
+
</DialogHeader>
|
|
107
|
+
{/* Lazy: the second Monaco instance only mounts while the dialog is
|
|
108
|
+
open, so there's no double-editor cost when closed. Distinct
|
|
109
|
+
`${id}-popout` id => distinct model URI. */}
|
|
110
|
+
{popoutOpen && (
|
|
111
|
+
<div className="flex-1 min-h-0">
|
|
112
|
+
<TypefoxEditor
|
|
113
|
+
id={`${editorId}-popout`}
|
|
114
|
+
fillHeight
|
|
115
|
+
{...sharedEditorProps}
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</DialogContent>
|
|
120
|
+
</Dialog>
|
|
121
|
+
</div>
|
|
43
122
|
);
|
|
44
123
|
};
|
|
45
124
|
|
|
@@ -49,6 +128,8 @@ export type {
|
|
|
49
128
|
TemplateProperty,
|
|
50
129
|
ShellEnvVar,
|
|
51
130
|
EditorMarker,
|
|
131
|
+
AcquireTypes,
|
|
132
|
+
AcquiredTypeFile,
|
|
52
133
|
} from "./types";
|
|
53
134
|
|
|
54
135
|
export {
|