@checkstack/ui 1.12.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.
- package/CHANGELOG.md +145 -0
- package/package.json +20 -15
- package/src/components/Accordion.tsx +17 -9
- package/src/components/ActionCard.tsx +4 -4
- package/src/components/BrandIcon.tsx +57 -0
- package/src/components/CodeEditor/CodeEditor.tsx +71 -7
- package/src/components/CodeEditor/TypefoxEditor.tsx +266 -53
- package/src/components/CodeEditor/editorTheme.test.ts +41 -0
- package/src/components/CodeEditor/editorTheme.ts +26 -0
- package/src/components/CodeEditor/index.ts +3 -1
- package/src/components/CodeEditor/monacoGuard.ts +76 -0
- package/src/components/CodeEditor/monacoTsService.ts +5 -37
- package/src/components/CodeEditor/scriptContext.test.ts +15 -7
- package/src/components/CodeEditor/scriptContext.ts +12 -18
- package/src/components/CodeEditor/types.ts +20 -0
- package/src/components/CodeEditor/validateScripts.ts +53 -13
- package/src/components/CodeEditor/vscodeServicesSignal.ts +72 -0
- package/src/components/ConfirmationModal.tsx +7 -1
- package/src/components/DynamicForm/DynamicForm.tsx +101 -53
- package/src/components/DynamicForm/DynamicOptionsField.tsx +19 -14
- package/src/components/DynamicForm/FormField.tsx +84 -24
- package/src/components/DynamicForm/MultiTypeEditorField.tsx +11 -0
- package/src/components/DynamicForm/index.ts +14 -0
- package/src/components/DynamicForm/types.ts +63 -1
- package/src/components/DynamicForm/utils.test.ts +38 -0
- package/src/components/DynamicForm/utils.ts +22 -0
- package/src/components/DynamicForm/validation.logic.test.ts +255 -0
- package/src/components/DynamicForm/validation.logic.ts +210 -0
- package/src/components/DynamicIcon.tsx +39 -17
- package/src/components/Markdown.tsx +68 -2
- package/src/components/Spinner.tsx +56 -0
- package/src/components/StatusBadge.tsx +78 -0
- package/src/components/StrategyConfigCard.tsx +3 -3
- package/src/components/Tabs.tsx +7 -1
- package/src/components/UserMenu.logic.test.ts +37 -0
- package/src/components/UserMenu.logic.ts +30 -0
- package/src/components/UserMenu.tsx +40 -12
- package/src/components/iconRegistry.tsx +27 -0
- package/src/index.ts +3 -0
- package/stories/Introduction.mdx +1 -1
- package/stories/Markdown.stories.tsx +56 -0
- package/stories/Spinner.stories.tsx +90 -0
- package/tsconfig.json +3 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
// Guarded accessor for the monaco-vscode editor API. ALL runtime monaco access
|
|
3
|
+
// in this package goes through here; direct value imports of
|
|
4
|
+
// `@codingame/monaco-vscode-editor-api` elsewhere are banned by lint
|
|
5
|
+
// (`no-restricted-imports`) so the guard cannot be bypassed.
|
|
6
|
+
//
|
|
7
|
+
// Why: `monaco.editor.*` / `monaco.languages.*` functions resolve services via
|
|
8
|
+
// `StandaloneServices.get()`, which AUTO-INITIALIZES the monaco-vscode services
|
|
9
|
+
// on first use (CodinGame `standaloneServices.js` — `get()` calls `initialize`
|
|
10
|
+
// when not yet initialized). `@typefox/monaco-editor-react` tracks its OWN,
|
|
11
|
+
// independent init flags; if any `monaco.*` call runs BEFORE the wrapper's init,
|
|
12
|
+
// it trips CodinGame's `servicesInitialized` flag and the wrapper's later
|
|
13
|
+
// `initialize()` throws "Services are already initialized" — the editor then
|
|
14
|
+
// never renders. This is dev-only (production's single mount masked it once),
|
|
15
|
+
// and it is exactly the bug this guard prevents from regressing.
|
|
16
|
+
//
|
|
17
|
+
// In dev, calling any `monaco.editor.*` / `monaco.languages.*` FUNCTION before
|
|
18
|
+
// `areVscodeServicesReady()` throws immediately, at the exact call site, with a
|
|
19
|
+
// clear message. In production the raw API is returned (zero overhead).
|
|
20
|
+
|
|
21
|
+
import * as monacoApi from "@codingame/monaco-vscode-editor-api";
|
|
22
|
+
import { areVscodeServicesReady } from "./vscodeServicesSignal";
|
|
23
|
+
|
|
24
|
+
// The namespaces whose functions auto-initialize the services on first use.
|
|
25
|
+
const GUARDED_NAMESPACES = new Set(["editor", "languages"]);
|
|
26
|
+
|
|
27
|
+
// Wrap a namespace so calling any of its functions before services are ready
|
|
28
|
+
// throws. Non-function members (enums like `MarkerSeverity`, classes) pass
|
|
29
|
+
// through untouched. Returns the same type `T` (the Proxy is invisible to types,
|
|
30
|
+
// so call sites still type-check against the real monaco signatures).
|
|
31
|
+
const guardNamespace = <T extends object>(ns: T, nsName: string): T =>
|
|
32
|
+
new Proxy(ns, {
|
|
33
|
+
get(target, prop, receiver): unknown {
|
|
34
|
+
const value: unknown = Reflect.get(target, prop, receiver);
|
|
35
|
+
if (typeof value !== "function" || typeof prop !== "string") {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
const fn = value;
|
|
39
|
+
return (...args: unknown[]): unknown => {
|
|
40
|
+
if (!areVscodeServicesReady()) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`monaco.${nsName}.${prop}() was called before the monaco-vscode ` +
|
|
43
|
+
`services were initialized. That call auto-initializes the ` +
|
|
44
|
+
`services and breaks the editor's @typefox init ("Services are ` +
|
|
45
|
+
`already initialized"), so the editor never renders. Gate it ` +
|
|
46
|
+
`behind \`apiReady\` (the editor's onEditorStartDone) or ` +
|
|
47
|
+
`\`areVscodeServicesReady()\`. See ` +
|
|
48
|
+
`core/ui/src/components/CodeEditor/monacoGuard.ts.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return Reflect.apply(fn, ns, args);
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const guardedMonaco: typeof monacoApi = import.meta.env.DEV
|
|
57
|
+
? new Proxy(monacoApi, {
|
|
58
|
+
get(target, prop, receiver): unknown {
|
|
59
|
+
const value: unknown = Reflect.get(target, prop, receiver);
|
|
60
|
+
if (
|
|
61
|
+
typeof prop === "string" &&
|
|
62
|
+
GUARDED_NAMESPACES.has(prop) &&
|
|
63
|
+
typeof value === "object" &&
|
|
64
|
+
value !== null
|
|
65
|
+
) {
|
|
66
|
+
return guardNamespace(value, prop);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
: monacoApi;
|
|
72
|
+
|
|
73
|
+
// The guarded runtime value. Consumers import this for runtime monaco access
|
|
74
|
+
// and import the TYPE namespace separately, type-only, from
|
|
75
|
+
// `@codingame/monaco-vscode-editor-api` (which the lint rule allows).
|
|
76
|
+
export { guardedMonaco as monaco };
|
|
@@ -65,43 +65,11 @@ const tsWorkerLoader: WorkerLoader = () =>
|
|
|
65
65
|
const jsonWorkerLoader: WorkerLoader = () =>
|
|
66
66
|
new Worker(jsonWorkerUrl, { type: "module" });
|
|
67
67
|
|
|
68
|
-
// The monaco-vscode
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// validator checks it and otherwise no-ops. Net effect: scripts validate once
|
|
74
|
-
// any script editor has been opened this session (covering collapsed cards
|
|
75
|
-
// from then on); a never-opened, all-collapsed automation is left to the
|
|
76
|
-
// deferred backend typecheck.
|
|
77
|
-
let vscodeServicesReady = false;
|
|
78
|
-
const servicesReadyListeners = new Set<() => void>();
|
|
79
|
-
|
|
80
|
-
/** Called by the editor once the monaco-vscode services have initialized. */
|
|
81
|
-
export const markVscodeServicesReady = (): void => {
|
|
82
|
-
if (vscodeServicesReady) return;
|
|
83
|
-
vscodeServicesReady = true;
|
|
84
|
-
for (const listener of servicesReadyListeners) listener();
|
|
85
|
-
servicesReadyListeners.clear();
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
/** True once an editor has initialized the monaco-vscode services. */
|
|
89
|
-
export const areVscodeServicesReady = (): boolean => vscodeServicesReady;
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Subscribe to the one-time "services ready" transition. Fires immediately if
|
|
93
|
-
* already ready. Returns an unsubscribe. Lets the headless validator re-run the
|
|
94
|
-
* moment the first editor brings the services up (otherwise a never-edited
|
|
95
|
-
* definition would not re-validate just because a card was opened).
|
|
96
|
-
*/
|
|
97
|
-
export const onVscodeServicesReady = (listener: () => void): (() => void) => {
|
|
98
|
-
if (vscodeServicesReady) {
|
|
99
|
-
listener();
|
|
100
|
-
return () => {};
|
|
101
|
-
}
|
|
102
|
-
servicesReadyListeners.add(listener);
|
|
103
|
-
return () => servicesReadyListeners.delete(listener);
|
|
104
|
-
};
|
|
68
|
+
// The "monaco-vscode services ready" signal (`markVscodeServicesReady` /
|
|
69
|
+
// `areVscodeServicesReady` / `onVscodeServicesReady`) lives in the Monaco-free
|
|
70
|
+
// `vscodeServicesSignal` module so the barrel and the automation editor can
|
|
71
|
+
// observe readiness without importing this Monaco-heavy module. See that file
|
|
72
|
+
// for the full rationale.
|
|
105
73
|
|
|
106
74
|
let workerFactoryRegistered = false;
|
|
107
75
|
|
|
@@ -7,13 +7,17 @@ import {
|
|
|
7
7
|
import type { JsonSchemaProperty } from "../DynamicForm/types";
|
|
8
8
|
|
|
9
9
|
describe("healthcheckScriptContext", () => {
|
|
10
|
-
it("emits
|
|
10
|
+
it("emits the global helper + context types, but NOT a bare-name module block", () => {
|
|
11
11
|
const ctx = healthcheckScriptContext({});
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
12
|
+
// The bare-name `@checkstack/healthcheck` module block is REMOVED (plan
|
|
13
|
+
// §6.2 / §6.4): the subpath module `@checkstack/sdk/healthcheck` is now
|
|
14
|
+
// resolved from the injected @checkstack/sdk editor bundle, not from
|
|
15
|
+
// scriptContext. scriptContext must NOT declare ANY package module block.
|
|
16
|
+
expect(ctx.typeDefinitions).not.toContain('declare module "@checkstack/healthcheck"');
|
|
17
|
+
expect(ctx.typeDefinitions).not.toContain('declare module "@checkstack/sdk/healthcheck"');
|
|
18
|
+
expect(ctx.typeDefinitions).not.toContain("declare module");
|
|
15
19
|
expect(ctx.typeDefinitions).toContain("HealthCheckScriptResult");
|
|
16
|
-
//
|
|
20
|
+
// The global declaration stays — it makes Monaco autocomplete `defineHea…`
|
|
17
21
|
// _without_ requiring the user to type the import first (Monaco 0.55
|
|
18
22
|
// doesn't expose `includeCompletionsForModuleExports`).
|
|
19
23
|
expect(ctx.typeDefinitions).toMatch(/declare function defineHealthCheck/);
|
|
@@ -143,9 +147,13 @@ describe("healthcheckScriptContext", () => {
|
|
|
143
147
|
});
|
|
144
148
|
|
|
145
149
|
describe("integrationScriptContext", () => {
|
|
146
|
-
it("emits
|
|
150
|
+
it("emits the global helper + context types, but NOT a bare-name module block", () => {
|
|
147
151
|
const ctx = integrationScriptContext({});
|
|
148
|
-
|
|
152
|
+
// Bare-name `@checkstack/integration` block removed (§6.2/§6.4); the
|
|
153
|
+
// subpath module resolves from the injected @checkstack/sdk editor bundle.
|
|
154
|
+
expect(ctx.typeDefinitions).not.toContain('declare module "@checkstack/integration"');
|
|
155
|
+
expect(ctx.typeDefinitions).not.toContain('declare module "@checkstack/sdk/integration"');
|
|
156
|
+
expect(ctx.typeDefinitions).not.toContain("declare module");
|
|
149
157
|
expect(ctx.typeDefinitions).toContain("IntegrationScriptResult");
|
|
150
158
|
// Global form — analogous to defineHealthCheck.
|
|
151
159
|
expect(ctx.typeDefinitions).toMatch(/declare function defineIntegration/);
|
|
@@ -8,8 +8,9 @@ import { jsonSchemaToTypeScript } from "./generateTypeDefinitions";
|
|
|
8
8
|
*
|
|
9
9
|
* - `healthcheckInlineContext` — inline TS/JS health checks.
|
|
10
10
|
* `context.config` is typed from the collector's own config schema.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* The `@checkstack/sdk/healthcheck` module (resolved from the injected
|
|
12
|
+
* @checkstack/sdk editor bundle) exposes the required return shape so
|
|
13
|
+
* users get a type _error_ when the shape is wrong.
|
|
13
14
|
*
|
|
14
15
|
* - `healthcheckShellContext` — shell health checks. No platform-injected
|
|
15
16
|
* env vars today (the user supplies `env` themselves), so we surface
|
|
@@ -18,7 +19,8 @@ import { jsonSchemaToTypeScript } from "./generateTypeDefinitions";
|
|
|
18
19
|
*
|
|
19
20
|
* - `integrationInlineContext` — TS/JS integration scripts.
|
|
20
21
|
* `context.event.payload` is typed from the event's payload schema.
|
|
21
|
-
*
|
|
22
|
+
* The `@checkstack/sdk/integration` module (resolved from the injected
|
|
23
|
+
* @checkstack/sdk editor bundle) exposes the result shape.
|
|
22
24
|
*
|
|
23
25
|
* - `integrationShellContext` — shell integration scripts. The platform
|
|
24
26
|
* injects `EVENT_ID`, `DELIVERY_ID`, `SUBSCRIPTION_ID`,
|
|
@@ -119,9 +121,10 @@ interface HealthCheckScriptContext {
|
|
|
119
121
|
* to be so mistakes are caught before the script ever runs.
|
|
120
122
|
*
|
|
121
123
|
* Available both as a global (this declaration) and as a named export
|
|
122
|
-
* from \`@checkstack/healthcheck
|
|
123
|
-
*
|
|
124
|
-
*
|
|
124
|
+
* from \`@checkstack/sdk/healthcheck\`. The global form means the editor
|
|
125
|
+
* can autocomplete it without the user typing the import first; the
|
|
126
|
+
* named-export form (resolved from the injected @checkstack/sdk editor
|
|
127
|
+
* bundle, not declared here) is for explicit, IDE-style imports.
|
|
125
128
|
*/
|
|
126
129
|
declare function defineHealthCheck<
|
|
127
130
|
T extends
|
|
@@ -132,11 +135,6 @@ declare function defineHealthCheck<
|
|
|
132
135
|
>(value: T): T;
|
|
133
136
|
|
|
134
137
|
declare const context: HealthCheckScriptContext;
|
|
135
|
-
|
|
136
|
-
declare module "@checkstack/healthcheck" {
|
|
137
|
-
export type { HealthCheckScriptResult, HealthCheckScriptContext };
|
|
138
|
-
export { defineHealthCheck };
|
|
139
|
-
}
|
|
140
138
|
`;
|
|
141
139
|
}
|
|
142
140
|
|
|
@@ -187,7 +185,8 @@ interface IntegrationScriptContext {
|
|
|
187
185
|
* Helper that asserts the return shape of an integration script at the
|
|
188
186
|
* type level. See \`defineHealthCheck\` for the analogous health-check
|
|
189
187
|
* helper. Available both as a global and as a named export from
|
|
190
|
-
* \`@checkstack/integration
|
|
188
|
+
* \`@checkstack/sdk/integration\` (resolved from the injected
|
|
189
|
+
* @checkstack/sdk editor bundle, not declared here).
|
|
191
190
|
*/
|
|
192
191
|
declare function defineIntegration<
|
|
193
192
|
T extends
|
|
@@ -200,11 +199,6 @@ declare function defineIntegration<
|
|
|
200
199
|
>(value: T): T;
|
|
201
200
|
|
|
202
201
|
declare const context: IntegrationScriptContext;
|
|
203
|
-
|
|
204
|
-
declare module "@checkstack/integration" {
|
|
205
|
-
export type { IntegrationScriptResult, IntegrationScriptContext };
|
|
206
|
-
export { defineIntegration };
|
|
207
|
-
}
|
|
208
202
|
`;
|
|
209
203
|
}
|
|
210
204
|
|
|
@@ -221,7 +215,7 @@ declare module "@checkstack/integration" {
|
|
|
221
215
|
* `defineHealthCheck` is available without an import (declared as an
|
|
222
216
|
* ambient global in the editor's type defs and injected onto
|
|
223
217
|
* `globalThis` by the runner). The named-import form
|
|
224
|
-
* `import { defineHealthCheck } from "@checkstack/healthcheck"` also
|
|
218
|
+
* `import { defineHealthCheck } from "@checkstack/sdk/healthcheck"` also
|
|
225
219
|
* works if users prefer it.
|
|
226
220
|
*/
|
|
227
221
|
const HEALTHCHECK_INLINE_TS_STARTER = `import { loadavg } from "node:os";
|
|
@@ -144,6 +144,18 @@ export interface CodeEditorProps {
|
|
|
144
144
|
* refresh against the new install.
|
|
145
145
|
*/
|
|
146
146
|
acquireResetKey?: string;
|
|
147
|
+
/**
|
|
148
|
+
* The running release's `@checkstack/sdk` editor bundle as virtual `.d.ts`
|
|
149
|
+
* files (TS/JS editors). Makes `import { defineHealthCheck } from
|
|
150
|
+
* "@checkstack/sdk/healthcheck"` resolve with real, version-matched types.
|
|
151
|
+
* Fetched live by the consumer so `@checkstack/ui` stays network-agnostic.
|
|
152
|
+
*/
|
|
153
|
+
sdkTypes?: ReadonlyArray<AcquiredTypeFile>;
|
|
154
|
+
/**
|
|
155
|
+
* Release-version reset key for `sdkTypes`. When it changes, the mounted SDK
|
|
156
|
+
* libs reset so the editor never serves stale SDK types after an upgrade.
|
|
157
|
+
*/
|
|
158
|
+
sdkTypesResetKey?: string;
|
|
147
159
|
/**
|
|
148
160
|
* Importable installed package NAMES (TS/JS editors). When provided, the
|
|
149
161
|
* editor suggests these while the cursor is inside an import specifier
|
|
@@ -165,4 +177,12 @@ export interface CodeEditorProps {
|
|
|
165
177
|
* keeping `@checkstack/ui` plugin-agnostic.
|
|
166
178
|
*/
|
|
167
179
|
title?: string;
|
|
180
|
+
/**
|
|
181
|
+
* When `true`, this editor never CLAIMS the one-time monaco-vscode cold init -
|
|
182
|
+
* it waits for another (visible) editor to bring the services up, then mounts.
|
|
183
|
+
* Set this for OFFSCREEN/hidden editors (e.g. the automation
|
|
184
|
+
* `ScriptServicesBooter`): a hidden editor's init may never complete, so it
|
|
185
|
+
* must not be the sole initializer. Defaults to `false`.
|
|
186
|
+
*/
|
|
187
|
+
deferInit?: boolean;
|
|
168
188
|
}
|
|
@@ -15,15 +15,16 @@
|
|
|
15
15
|
// global to the shared service). Prepending the type defs onto each validated
|
|
16
16
|
// source keeps `context` scoped to that one off-screen file. See
|
|
17
17
|
// `buildValidationSource`.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
//
|
|
19
|
+
// The Monaco editor API, the standalone TS worker accessors, and the shared
|
|
20
|
+
// `monacoTsService` setup are imported LAZILY (in-body `await import(...)`)
|
|
21
|
+
// rather than at module scope. This keeps the entire `@codingame/*` stack off
|
|
22
|
+
// the `@checkstack/ui` barrel: `validateTypeScriptSources` is re-exported from
|
|
23
|
+
// the barrel, so a static Monaco import here would ship Monaco to every page
|
|
24
|
+
// that touches the barrel (e.g. the login page). The lazy imports only resolve
|
|
25
|
+
// once an editor has already brought the services up, so they hit an
|
|
26
|
+
// already-loaded chunk and add no extra cost.
|
|
27
|
+
import { areVscodeServicesReady } from "./vscodeServicesSignal";
|
|
27
28
|
import {
|
|
28
29
|
buildValidationSource,
|
|
29
30
|
mapWorkerDiagnostics,
|
|
@@ -31,6 +32,10 @@ import {
|
|
|
31
32
|
type ScriptDiagnostic,
|
|
32
33
|
} from "./scriptDiagnostics";
|
|
33
34
|
|
|
35
|
+
type MonacoEditorApi = typeof import("@codingame/monaco-vscode-editor-api");
|
|
36
|
+
type TsLanguageFeatures =
|
|
37
|
+
typeof import("@codingame/monaco-vscode-standalone-typescript-language-features");
|
|
38
|
+
|
|
34
39
|
export type { ScriptDiagnostic } from "./scriptDiagnostics";
|
|
35
40
|
|
|
36
41
|
export interface ScriptValidationInput {
|
|
@@ -72,7 +77,26 @@ export async function validateTypeScriptSources({
|
|
|
72
77
|
if (!areVscodeServicesReady()) {
|
|
73
78
|
return results;
|
|
74
79
|
}
|
|
80
|
+
|
|
81
|
+
// Lazy-load the Monaco stack only now that an editor has brought the services
|
|
82
|
+
// up (keeps these heavy `@codingame/*` modules off the barrel - see the
|
|
83
|
+
// module header). Because services are ready, these chunks are already
|
|
84
|
+
// resolved, so the imports are effectively free here.
|
|
85
|
+
let monaco: MonacoEditorApi;
|
|
86
|
+
let getJavaScriptWorker: TsLanguageFeatures["getJavaScriptWorker"];
|
|
87
|
+
let getTypeScriptWorker: TsLanguageFeatures["getTypeScriptWorker"];
|
|
75
88
|
try {
|
|
89
|
+
const [editorApi, tsLanguageFeatures, { ensureStandaloneStdlib }] =
|
|
90
|
+
await Promise.all([
|
|
91
|
+
import("@codingame/monaco-vscode-editor-api"),
|
|
92
|
+
import(
|
|
93
|
+
"@codingame/monaco-vscode-standalone-typescript-language-features"
|
|
94
|
+
),
|
|
95
|
+
import("./monacoTsService"),
|
|
96
|
+
]);
|
|
97
|
+
monaco = editorApi;
|
|
98
|
+
getJavaScriptWorker = tsLanguageFeatures.getJavaScriptWorker;
|
|
99
|
+
getTypeScriptWorker = tsLanguageFeatures.getTypeScriptWorker;
|
|
76
100
|
await ensureStandaloneStdlib();
|
|
77
101
|
} catch {
|
|
78
102
|
return results;
|
|
@@ -80,7 +104,15 @@ export async function validateTypeScriptSources({
|
|
|
80
104
|
|
|
81
105
|
for (const input of sources) {
|
|
82
106
|
try {
|
|
83
|
-
results.set(
|
|
107
|
+
results.set(
|
|
108
|
+
input.id,
|
|
109
|
+
await validateOne({
|
|
110
|
+
input,
|
|
111
|
+
monaco,
|
|
112
|
+
getJavaScriptWorker,
|
|
113
|
+
getTypeScriptWorker,
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
84
116
|
} catch {
|
|
85
117
|
results.set(input.id, []);
|
|
86
118
|
}
|
|
@@ -88,9 +120,17 @@ export async function validateTypeScriptSources({
|
|
|
88
120
|
return results;
|
|
89
121
|
}
|
|
90
122
|
|
|
91
|
-
async function validateOne(
|
|
92
|
-
input
|
|
93
|
-
|
|
123
|
+
async function validateOne({
|
|
124
|
+
input,
|
|
125
|
+
monaco,
|
|
126
|
+
getJavaScriptWorker,
|
|
127
|
+
getTypeScriptWorker,
|
|
128
|
+
}: {
|
|
129
|
+
input: ScriptValidationInput;
|
|
130
|
+
monaco: MonacoEditorApi;
|
|
131
|
+
getJavaScriptWorker: TsLanguageFeatures["getJavaScriptWorker"];
|
|
132
|
+
getTypeScriptWorker: TsLanguageFeatures["getTypeScriptWorker"];
|
|
133
|
+
}): Promise<ScriptDiagnostic[]> {
|
|
94
134
|
const { text, prependedLineCount } = buildValidationSource({
|
|
95
135
|
typeDefinitions: input.typeDefinitions,
|
|
96
136
|
source: input.source,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Lightweight, Monaco-free signal for the one-time "monaco-vscode services
|
|
2
|
+
// ready" transition.
|
|
3
|
+
//
|
|
4
|
+
// Extracted from `monacoTsService` so consumers that only need to OBSERVE
|
|
5
|
+
// readiness (the `@checkstack/ui` barrel re-export, the automation editor's
|
|
6
|
+
// `ScriptServicesBooter` + headless validator) do NOT transitively pull the
|
|
7
|
+
// entire `@codingame/*` Monaco stack into their bundle. Importing this module
|
|
8
|
+
// loads zero Monaco code, so pages that never mount an editor (e.g. the login
|
|
9
|
+
// page) stay Monaco-free.
|
|
10
|
+
//
|
|
11
|
+
// Why a global flag at all: the monaco-vscode API initializes globally exactly
|
|
12
|
+
// ONCE, and that init is owned by the editor wrapper (`MonacoEditorReactComp`),
|
|
13
|
+
// which throws "Services are already initialized" if anything else inits first.
|
|
14
|
+
// So the headless validator must NOT touch the worker / models until an editor
|
|
15
|
+
// has brought the services up. The editor flips this flag from its
|
|
16
|
+
// `onEditorStartDone` (via `markVscodeServicesReady`); the validator checks
|
|
17
|
+
// `areVscodeServicesReady()` and otherwise no-ops. Net effect: scripts validate
|
|
18
|
+
// once any script editor has been opened this session (covering collapsed cards
|
|
19
|
+
// from then on); a never-opened, all-collapsed automation is left to the
|
|
20
|
+
// deferred backend typecheck.
|
|
21
|
+
|
|
22
|
+
let vscodeServicesReady = false;
|
|
23
|
+
const servicesReadyListeners = new Set<() => void>();
|
|
24
|
+
|
|
25
|
+
/** Called by the editor once the monaco-vscode services have initialized. */
|
|
26
|
+
export const markVscodeServicesReady = (): void => {
|
|
27
|
+
if (vscodeServicesReady) return;
|
|
28
|
+
vscodeServicesReady = true;
|
|
29
|
+
for (const listener of servicesReadyListeners) listener();
|
|
30
|
+
servicesReadyListeners.clear();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** True once an editor has initialized the monaco-vscode services. */
|
|
34
|
+
export const areVscodeServicesReady = (): boolean => vscodeServicesReady;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Subscribe to the one-time "services ready" transition. Fires immediately if
|
|
38
|
+
* already ready. Returns an unsubscribe. Lets the headless validator re-run the
|
|
39
|
+
* moment the first editor brings the services up (otherwise a never-edited
|
|
40
|
+
* definition would not re-validate just because a card was opened).
|
|
41
|
+
*/
|
|
42
|
+
export const onVscodeServicesReady = (listener: () => void): (() => void) => {
|
|
43
|
+
if (vscodeServicesReady) {
|
|
44
|
+
listener();
|
|
45
|
+
return () => {};
|
|
46
|
+
}
|
|
47
|
+
servicesReadyListeners.add(listener);
|
|
48
|
+
return () => servicesReadyListeners.delete(listener);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ─── Cold-init serialization ─────────────────────────────────────────────────
|
|
52
|
+
// monaco-vscode services initialize globally exactly once. @typefox's React
|
|
53
|
+
// wrapper performs that init when given a `vscodeApiConfig`, and it is
|
|
54
|
+
// StrictMode-safe for a SINGLE editor - but two editors mounting at once both
|
|
55
|
+
// race the init and corrupt it. So exactly ONE editor claims the cold init and
|
|
56
|
+
// mounts (with `vscodeApiConfig`); every other editor waits for
|
|
57
|
+
// `areVscodeServicesReady()` before mounting (it then attaches to the
|
|
58
|
+
// already-initialized services). The claim is released if the claiming editor
|
|
59
|
+
// unmounts before services come up, so a sibling can take over.
|
|
60
|
+
let coldInitClaimed = false;
|
|
61
|
+
|
|
62
|
+
/** First caller (while not ready and unclaimed) gets the cold-init role. */
|
|
63
|
+
export const claimColdInit = (): boolean => {
|
|
64
|
+
if (vscodeServicesReady || coldInitClaimed) return false;
|
|
65
|
+
coldInitClaimed = true;
|
|
66
|
+
return true;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Release the claim if services never came up (claimer unmounted early). */
|
|
70
|
+
export const releaseColdInit = (): void => {
|
|
71
|
+
if (!vscodeServicesReady) coldInitClaimed = false;
|
|
72
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { cn } from "../utils";
|
|
3
3
|
import { Button } from "./Button";
|
|
4
|
+
import { usePerformance } from "./PerformanceProvider";
|
|
4
5
|
import { AlertTriangle, X } from "lucide-react";
|
|
5
6
|
|
|
6
7
|
export interface ConfirmationModalProps {
|
|
@@ -26,6 +27,8 @@ export const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
|
|
|
26
27
|
variant = "danger",
|
|
27
28
|
isLoading = false,
|
|
28
29
|
}) => {
|
|
30
|
+
const { isLowPower } = usePerformance();
|
|
31
|
+
|
|
29
32
|
if (!isOpen) return;
|
|
30
33
|
|
|
31
34
|
const handleConfirm = () => {
|
|
@@ -65,7 +68,10 @@ export const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
|
|
|
65
68
|
onClick={handleBackdropClick}
|
|
66
69
|
>
|
|
67
70
|
<div
|
|
68
|
-
className=
|
|
71
|
+
className={cn(
|
|
72
|
+
"bg-background rounded-lg shadow-xl max-w-md w-full mx-4 my-4 max-h-[calc(100dvh-2rem)] overflow-y-auto pointer-events-auto",
|
|
73
|
+
!isLowPower && "animate-in fade-in zoom-in duration-200",
|
|
74
|
+
)}
|
|
69
75
|
role="dialog"
|
|
70
76
|
aria-modal="true"
|
|
71
77
|
aria-labelledby="modal-title"
|