@checkstack/ui 1.12.0 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +154 -0
- package/package.json +24 -19
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,159 @@
|
|
|
1
1
|
# @checkstack/ui
|
|
2
2
|
|
|
3
|
+
## 1.13.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [13373ce]
|
|
8
|
+
- @checkstack/common@0.14.0
|
|
9
|
+
- @checkstack/frontend-api@0.7.1
|
|
10
|
+
- @checkstack/template-engine@0.4.1
|
|
11
|
+
|
|
12
|
+
## 1.13.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- 9dcc848: AI chat UX: ordered turns, readable diffs, persistent errors, auto-titles, decision acknowledgments, and a smarter topical guard.
|
|
17
|
+
|
|
18
|
+
- Turns render as ordered parts (text / tool-call status / confirm card) in chronological order, with inline tool-error lines and a mid-turn "Thinking..." indicator, instead of one text blob plus a flat tool list. The confirm card and tool-step parts no longer vanish after a turn finishes (hydration seeds once per conversation id via `useInitOnceForKey`, so background refetches are no-ops).
|
|
19
|
+
- Errors persist: in-stream provider errors are lifted into the chat hook's durable error state and shown in a dismissible banner with selectable text and a Copy button (single-line digest, full text on hover); it clears on send / open / new chat. The backend installs an `onError` handler that logs the provider's full HTTP response and returns a readable message, and normalizes the model message history (drop empty rows, merge consecutive same-role rows, strip a leading non-user row) so a single provider hiccup can no longer brick a conversation.
|
|
20
|
+
- Confirm/applied card diffs render as a GitHub-style split diff (line-number gutters, per-line tint, word-level highlighting, an "Expand" pop-out). `computeFieldDiff` recurses into arrays element-wise so a single changed leaf is pinpointed instead of dumping whole serialized arrays.
|
|
21
|
+
- Conversations auto-title after the first user message (cheap `generateText` reusing the turn's model, fire-and-forget, heuristic fallback). "New chat" opens immediately and reuses an empty untitled draft instead of spawning duplicates; "Delete" is a soft archive (`archived_at` on `ai_conversations`, data retained). A clean model picker always renders a `Select` of `[defaultModel, ...availableModels]` de-duplicated.
|
|
22
|
+
- The assistant acknowledges a confirm-card decision (a new `decision` mode -> `streamDecision`) instead of going silent after an apply/decline; the decision note is derived server-side from the stored proposal and is ephemeral.
|
|
23
|
+
- A cheap topical pre-classifier short-circuits off-topic turns with a canned refusal (fail-open, spend recorded). It marks meta/capability/greeting/how-to questions as ON_TOPIC; only clearly unrelated requests (coding help, creative writing, trivia) are refused.
|
|
24
|
+
- The chat agent no longer emits duplicate proposals for one request: propose/auto-apply results carry an explicit model-facing "stop and wait" note, and a per-turn `<tool>:<argsHash>` dedupe short-circuits repeated identical mutating calls.
|
|
25
|
+
- Assistant messages render through the shared `<MarkdownBlock>`: it now parses a SAFE subset of raw HTML (`rehype-raw` + `rehype-sanitize`) so native `<details>`/`<summary>` widgets render, and enables `remark-gfm` so GFM tables, strikethrough, and autolinks render (the assistant often summarizes drafts as tables).
|
|
26
|
+
|
|
27
|
+
State and scale: the archive marker, titles, and permission mode all live in the shared `ai_conversations` table, read identically on every pod; the classifier holds no state and its spend is recorded in the shared `ai_spend` ledger. No new pod-local state.
|
|
28
|
+
|
|
29
|
+
This is a beta minor.
|
|
30
|
+
|
|
31
|
+
- 9dcc848: Plugin-owned AI tools: every domain plugin contributes its own AI tools (chat assistant + automation AI action), and `ai-backend` is platform-only.
|
|
32
|
+
|
|
33
|
+
Every plugin-specific AI tool is owned by the plugin whose domain it acts on, registered via that plugin's own `aiToolExtensionPoint` / `aiToolProjectionExtensionPoint` from its init - the same path an external plugin author uses. `ai-backend` no longer imports or depends on any capability plugin's `*-common`; the dependency direction is strictly plugin -> ai-platform. Pure helpers (`computeFieldDiff`, capability-summary, `ScriptContextKind`) live in `@checkstack/ai-common`.
|
|
34
|
+
|
|
35
|
+
Tools shipped:
|
|
36
|
+
|
|
37
|
+
- Health checks and automations: full CRUD - `healthcheck.propose` / `automation.propose` and `*.update` (`mutate`, deep-validated) and `*.delete` (`destructive`, always confirm-gated). `healthcheck.propose`'s dry-run calls the new deep `validateConfiguration` so propose-time validation matches apply-time. Assertions are validated against the collector's result schema and the canonical operator vocabulary. Capability-catalog tools (`ai.listCapabilities`, `ai.getCapabilitySchema`), script context tools (`ai.getScriptContext`, `ai.testScript`), and notify-subscriber tools (`healthcheck.notifySystemSubscribers` / `...GroupSubscribers`).
|
|
38
|
+
- Catalog: `catalog.createSystem` / `updateSystem` / `createGroup` / `updateGroup` (`mutate`), `catalog.deleteSystem` / `deleteGroup` (`destructive`), membership tools (`mutate`), plus `catalog.listSystems` / `listGroups` read projections.
|
|
39
|
+
- Incident: `incident.create` / `update` / `addUpdate` / `resolve` / `addLink` (`mutate`), `incident.delete` / `removeLink` (`destructive`), and `incident.get` / `incident.list` read projections.
|
|
40
|
+
- Maintenance: `maintenance.create` / `update` / `addUpdate` / `close` / `addLink` (`mutate`), `maintenance.delete` / `removeLink` (`destructive`), and `maintenance.list` / `get` read projections.
|
|
41
|
+
- Read projections for SLO (`slo.listObjectives`), dependency (`dependency.list`), incident (`incident.list`), healthcheck (`healthcheck.status`), and anomaly (`anomaly.explain`), each gated by the source procedure's own access rule and routed as the principal.
|
|
42
|
+
- Documentation grounding: `ai.searchDocs` / `ai.getDoc` over a build-time bundled docs index (BM25-ish ranking), so the assistant grounds how-to answers in Checkstack's own docs offline.
|
|
43
|
+
- URL introspection: `ai.probeUrl`, an SSRF-guarded read tool the assistant uses to inspect a real endpoint before drafting a health check. Update tools compute a before -> after field diff rendered on the confirm card (approve mode) or an "Applied" card (auto mode), so a change is never silent.
|
|
44
|
+
|
|
45
|
+
`ai_analyze` automation action (automation-backend, with an editor connection picker + audited tool calls): runs a bounded AI agent on the run context as the automation's `runAs` service account, so it can never exceed that identity's permissions; destructive tools are never offered; mutating tools auto-apply through the service account's client. Produces an `automation.analysis` artifact downstream actions can branch on. The agent loop is exposed as a headless `aiAgentRunnerRef` service so automation-backend can drive it without depending on ai-backend.
|
|
46
|
+
|
|
47
|
+
`notification.notifyForSubscription` is now callable by user / application principals holding `notification.send` (previously service-only). Every tool routes through the user-scoped client, so handler-side authorization is enforced exactly as a direct UI/RPC action; the resolver gate plus the propose/apply re-check at propose AND apply are the additional authority. A systemic authz regression test asserts every registered tool falls into exactly one safe authorization category.
|
|
48
|
+
|
|
49
|
+
A new `ai_transport` enum value `automation` records the AI action's tool calls in the `ai_tool_calls` audit log. No new durable state beyond that; each tool is a thin, deterministic wrapper over an existing RPC, so every pod behaves identically.
|
|
50
|
+
|
|
51
|
+
This is a beta minor.
|
|
52
|
+
|
|
53
|
+
- 9dcc848: Redesign the dashboard as an extensible "needs attention" overview, and normalize system state badges.
|
|
54
|
+
|
|
55
|
+
The dashboard now surfaces ONLY systems that need attention (degraded, unhealthy, breaching/at-risk SLO, under an incident or active maintenance, anomalous, or with a dependency problem) and hides everything healthy. A compact header summarises fleet health and filters by severity; each problem renders as an elevated card with one row per issue that deep-links to where the issue originates. A calm "all clear" state shows when nothing needs attention, a live "recent activity" feed sits below, and a "View catalog" link replaces the duplicated system list.
|
|
56
|
+
|
|
57
|
+
New platform contract `SystemSignalsSlot` (`@checkstack/catalog-common`): a headless, render-once slot where any plugin bulk-fetches and reports structured `SystemSignal[]` per system via `onSignals(sourceId, map)`. The dashboard aggregates every source agnostic to which plugins contribute; each core reliability plugin (healthcheck, incident, SLO, maintenance, anomaly, dependency) ships a filler, and third-party plugins add new per-system state the same way with no dashboard change. Signals carry an `iconName` rendered via `DynamicIcon` so the contract stays React-free. The dashboard's old summary tiles and overview sheets are removed, so it no longer depends on those plugins' packages. The group "subscribe" control moved onto the catalog browse page's group headers.
|
|
58
|
+
|
|
59
|
+
System state badges are normalized into one icon-only `@checkstack/ui` `StatusBadge` primitive - a small tinted icon chip with the full label on hover/focus (and via `aria-label`). Each signal uses its feature's navbar icon (health = Activity, incident = AlertTriangle, SLO = Target, maintenance = Wrench, dependency = GitBranch; anomaly = ChartSpline). Badges self-sort by severity via CSS `order` (error -> warn -> info), tooltips are scoped to a named group, and in catalog browse rows the cluster moved to the right edge.
|
|
60
|
+
|
|
61
|
+
This is a beta minor.
|
|
62
|
+
|
|
63
|
+
- 9dcc848: Surface integration connection validation errors inline, and fix blank secrets clearing stored credentials on edit.
|
|
64
|
+
|
|
65
|
+
`@checkstack/ui` `DynamicForm` gains opt-in, backward-compatible props plus pure DOM-free helpers:
|
|
66
|
+
|
|
67
|
+
- `showInlineErrors` (default `false`): renders a concise per-field error under each touched required field; the `onValidChange` validity boolean derives from the same per-field error map.
|
|
68
|
+
- `fieldErrors`: an externally-supplied `{ [fieldPath]: message }` map (dot-joined for nested fields) for surfacing SERVER validation inline; nested paths flag their parent.
|
|
69
|
+
- `keepExistingSecretFields`: in EDIT mode, lists `x-secret` keys already stored server-side - a blank input means "keep existing" and is treated as VALID (CREATE mode omits it). New exported helpers: `deriveClientFieldErrors`, `deriveServerFieldErrors`, `parseServerValidationData`, `omitKeepExistingSecrets`, `listSecretFieldKeys`. `DynamicForm` also no longer shows a required (`*`) marker on the child fields of an OPTIONAL nested object while that object is empty (e.g. the OpenAI-compatible `spendCap`); required nested objects are unchanged.
|
|
70
|
+
|
|
71
|
+
`@checkstack/integration-backend`: connection-config validation failures attach the structured zod issues to `ORPCError.data` under a `CONFIG_VALIDATION` discriminator; the human-readable message is unchanged.
|
|
72
|
+
|
|
73
|
+
`@checkstack/integration-frontend` `ProviderConnectionsPage`: validation failures appear inline on the offending fields (the toast remains a fallback); Create/Save stays disabled while invalid; on edit a blank `x-secret` field is treated as "keep existing" (no required error, omitted from the update so the stored secret is not cleared).
|
|
74
|
+
|
|
75
|
+
BREAKING CHANGES: none. The new `DynamicForm` props are optional and default to previous behavior.
|
|
76
|
+
|
|
77
|
+
This is a beta minor.
|
|
78
|
+
|
|
79
|
+
- 9dcc848: Add environments as a first-class catalog primitive, with per-environment health-check fan-out, config templating, per-environment reactive health, and script run-context exposure.
|
|
80
|
+
|
|
81
|
+
- Catalog primitive: an environment is a sibling of groups - a named, instance-global record carrying free-form custom fields (baseUrl, region, tier, ...) that any system can belong to many-to-many. New `environments` + `systems_environments` tables, `EnvironmentSchema` + create/update schemas, `EntityService` environment CRUD and membership joins, RPC endpoints gated by a new `catalogAccess.environment` access rule, a GitOps `Environment` kind + `System.environments` extension, and frontend management (an `EnvironmentEditor`, an Environments management panel, and a per-system environment picker). The Environments card's Add/Edit/Delete affordances are gated on `catalogAccess.environment.manage`.
|
|
82
|
+
- Per-environment fan-out: run identity becomes `(systemId, configurationId, environmentId)`. Runs, aggregates, and state transitions gain a nullable `environmentId`. The health-check assignment gains an `environmentIds` selector with three modes (All / Specific / None; `null` and `[]` are distinct). The queue executor resolves the effective environment set via the catalog `resolveSystemEnvironments` read and executes one isolated run per environment.
|
|
83
|
+
- Config templating: a new `x-templatable` config-field marker renders a string field through the template engine at execute time, against `{ environment, check, system }`. A shared `renderTemplatableConfig` and a `renderTemplatePreview` helper (re-exported from `@checkstack/template-engine`) keep editor previews identical to the run-time render. The HTTP collector's `url`, `headers[].value`, and `body` are templatable, rendered per environment (the strategy client build moves inside the per-env loop); the `url`'s `.url()` validation moves post-render. Secrets resolve before templating; a field marked both secret and `x-templatable` is rejected at plugin load. `DynamicForm` shows a live "Preview" line, and the catalog `EnvironmentPreviewPicker` ("Preview as: <environment>") drives it in the collector editor (only when the schema has a templatable field).
|
|
84
|
+
- Script run-context: `CollectorRunContext` gains an optional `environment` field (`{ id, name, fields }`, metadata only). Shell collectors receive `CHECKSTACK_ENV_ID` / `_NAME` / `CHECKSTACK_ENV_<FIELD>` vars; inline TS collectors read `globalThis.context.environment`; the editor test panel mirrors both. The env-less path is unchanged.
|
|
85
|
+
- Per-environment reactive health (see BREAKING below), env-keyed read/write paths, env-qualified serialization locks, an optional `trigger.payload.environmentId`, per-environment isolation, and an `ENVIRONMENT_RESOLUTION_FAILED` signal when catalog resolution degrades to a single env-less run.
|
|
86
|
+
|
|
87
|
+
BREAKING CHANGES: the reactive `health` entity's id-shape and cardinality change. It now encodes two views: per-environment (id `"<systemId>::<environmentId>"`) and a system rollup (id `"<systemId>"`, the worst status across environments + env-less runs). The rollup PRESERVES the pre-existing system-level contract - dashboards, status badges, and automations referencing health by `systemId` keep working without re-authoring - but the entity's contract surface changed (new id-shape, higher cardinality, new payload field), so it is flagged breaking. `getBulkHealthState` parses env-qualified ids and keys results by the original id.
|
|
88
|
+
|
|
89
|
+
State and scale: membership and custom fields live only in catalog Postgres and are re-read every tick via the cross-plugin RPC; env-keyed health reads from shared `health_check_runs` / aggregates / transitions (compute-on-read). Every pod resolves the same effective set and the same per-environment health. No pod-local environment state.
|
|
90
|
+
|
|
91
|
+
Also: `unwrapSchema` in `zod-config.ts` loops instead of single-pass-stripping so multi-layer wrappers (`.optional().default()`) still resolve `x-templatable` meta. The env-less `{{ environment.* }}` run notice logs at `debug` (a legitimate recurring configuration), while the post-render HTTP `.url()` check still fails a genuinely-broken empty render with a clear "Rendered URL is invalid" error.
|
|
92
|
+
|
|
93
|
+
This is a beta minor.
|
|
94
|
+
|
|
95
|
+
- 9dcc848: Cut initial-load JS: lazy plugin contributions, a hardened lazy-by-default contribution contract, on-demand Monaco, and a lighter icon/chart load.
|
|
96
|
+
|
|
97
|
+
- Lazy plugin route pages: each plugin's route `element` references a `React.lazy`-wrapped page rendered inside a shared `<Suspense>` boundary. Plugins still register synchronously, so nav, slots, commands, API factories, and `foreignSignals` are available on first paint. This moves ~37 route-page chunks (~600 KB) out of the entry; the entry chunk drops from ~2.4 MB to ~190 KB. Auth flow pages stay eager. The `@checkstack/scripts` scaffold template generates lazy route pages too.
|
|
98
|
+
- Hardened contribution contract (BREAKING, frontend plugin contract): plugins declare contributions lazily and let the framework own code-splitting, Suspense, and per-plugin error isolation. Routes use `load: () => import("./Page").then((m) => ({ default: m.Page }))` instead of `element: <Page />` (`element` is still accepted for the rare page that must paint without a chunk fetch; provide exactly one). Slot extensions accept either an eager `component` or a lazy `load`; new `getLazyContribution` + `ExtensionComponent` exports from `@checkstack/frontend-api` render either kind. This also fixes runtime-installed plugins: `ExtensionSlot` subscribes to the plugin registry, and the API registry rebuilds when the plugin set changes (`getPlugins()` returns an immutable snapshot via `useSyncExternalStore`). A per-plugin error boundary contains a bad contribution.
|
|
99
|
+
- On-demand Monaco: the `@checkstack/ui` barrel no longer pulls the `@codingame/*` / `monaco-languageclient` stack into the initial load. `CodeEditor` lazy-loads its Monaco-backed editor behind `React.lazy` + Suspense, `validateTypeScriptSources` imports the editor API via in-body `await import(...)`, and the "vscode services ready" signal moved to a Monaco-free module. The ~10 MB editor body loads only when a `CodeEditor` mounts. A `react-vendor` `manualChunks` split was added for stable vendor caching.
|
|
100
|
+
- lucide-react 1.x + lighter icons/charts (BREAKING for icon consumers): lucide-react unified from three drifting ranges to `^1.17.0`. lucide v1 removed brand icons, so the GitHub/GitLab marks are vendored in `@checkstack/ui` (`GithubIcon`, `GitlabIcon`, `brandIcons`); a new `IconName` type (`LucideIconName | BrandIconName`) in `@checkstack/common` is canonical, accepted by `AuthStrategy.icon` and the card components, so data-driven brand names keep working. `DynamicIcon` no longer eagerly imports lucide's ~1600-icon map (~1 MB) - it lives in a `React.lazy` `iconRegistry` chunk fetched on first data-driven render, while statically named-imported icons tree-shake normally. The recharts-backed health-check charts (~300 KB) and the `HealthCheckSystemOverview` drawer leave the initial load.
|
|
101
|
+
|
|
102
|
+
BREAKING CHANGES:
|
|
103
|
+
|
|
104
|
+
- Frontend plugin contract: routes/slot contributions are lazy-by-default (`load` instead of `element`/eager elements) as described above.
|
|
105
|
+
- Any external consumer importing a brand icon from `lucide-react` (e.g. `import { Github } from "lucide-react"`) must switch to the vendored `@checkstack/ui` brand icons or a custom SVG.
|
|
106
|
+
|
|
107
|
+
This is a beta minor.
|
|
108
|
+
|
|
109
|
+
- 9dcc848: Add the auto-generated, version-pinned `@checkstack/sdk` package + codegen, and serve its types live to the in-app editor.
|
|
110
|
+
|
|
111
|
+
- A new committed workspace package `@checkstack/sdk`, generated from the platform's source of truth by `scripts/generate-sdk.ts` (`generate:sdk` / `generate:sdk:check`): a fully-typed oRPC client (`createCheckstackClient`) over the REST surface with one `InferClient` per plugin contract, real script-authoring helpers (`@checkstack/sdk/healthcheck`, `@checkstack/sdk/integration`) whose runtime body is the same identity function the in-app runner injects, per-subpath `.d.ts` under the package `exports` map, and an editor-only ambient bundle. A `generate:sdk:check` CI guard fails when the committed SDK files drift from a fresh generation. The `@checkstack/sdk` version is stamped from `@checkstack/release` and MUST NOT appear in a changeset (a guard enforces this); the `@checkstack/release` bump here advances the release version so the generated SDK can be published later. The generated client also normalizes its base URL without a backtracking-prone regex, closing a CodeQL `js/polynomial-redos` finding.
|
|
112
|
+
- Live editor type injection: a new version-keyed route `GET /api/script-packages/sdk-types/:releaseVersion` (raw handler in `@checkstack/script-packages-backend`) serves the generated SDK editor bundle with `Cache-Control: private, max-age=1y, immutable`; the pure path-build/parse module lives in `@checkstack/script-packages-common`, shared by backend and frontend. A mismatched version returns `409` so the editor refetches and never serves stale types after an upgrade. The frontend `useSdkTypeInjection` hook fetches the bundle once per session and mounts it into Monaco via `addExtraLib`. Schema-narrowed `context.config` / `context.event.payload` editor types stay local; the package-resolving module declarations come from the one published `@checkstack/sdk` source.
|
|
113
|
+
|
|
114
|
+
BREAKING CHANGES: the script-authoring import surface moves from the bare `@checkstack/healthcheck` / `@checkstack/integration` virtual modules to the `@checkstack/sdk/healthcheck` / `@checkstack/sdk/integration` subpaths of the published `@checkstack/sdk` package. The old bare-name imports no longer resolve (an old import now errors in the editor, surfacing the migration). Existing scripts must update the module specifier:
|
|
115
|
+
|
|
116
|
+
- import { defineHealthCheck } from "@checkstack/healthcheck";
|
|
117
|
+
+ import { defineHealthCheck } from "@checkstack/sdk/healthcheck";
|
|
118
|
+
|
|
119
|
+
- import { defineIntegration } from "@checkstack/integration";
|
|
120
|
+
+ import { defineIntegration } from "@checkstack/sdk/integration";
|
|
121
|
+
|
|
122
|
+
The helper names and their runtime behaviour are unchanged - only the module specifier moves. The global (no-import) helper form continues to work unchanged.
|
|
123
|
+
|
|
124
|
+
This is a beta minor.
|
|
125
|
+
|
|
126
|
+
- 9dcc848: Guard component animations behind isLowPower, and add a shared inline Spinner.
|
|
127
|
+
|
|
128
|
+
- `@checkstack/ui` shared components (`Tabs`, `ConfirmationModal`, `Accordion`, `CodeEditor` popout-button backdrop blur) now drop their `animate-*` / `backdrop-blur` classes when the device reports the low-power tier, matching `LoadingSpinner` / `Skeleton`. No public API change; normal-power rendering is unchanged.
|
|
129
|
+
- A new shared inline `Spinner` (`@checkstack/ui`) renders a lucide `Loader2` whose `animate-spin` is gated internally behind `usePerformance().isLowPower`, so call sites inherit the low-power guard. Props: `size` (`sm`/`md`/`lg`), `className`, rest spread to the icon; decorative by default (`aria-hidden`), `role="status"` when given `aria-label`. The hand-rolled `Loader2` button/table spinners in `HealthCheckDrawer`, `HealthCheckRunsTable`, `IncidentEditor`, `IncidentUpdateForm`, `ProviderConnectionsPage`, `MaintenanceEditor`, `MaintenanceUpdateForm`, `UserChannelCard`, and `DynamicOptionsField` are migrated onto it.
|
|
130
|
+
- Remaining unguarded `animate-*` / `animate-in` / blur classes across the auth, gitops, healthcheck, incident, integration, maintenance, and notification frontends are gated behind `usePerformance().isLowPower`, so effects degrade gracefully on low-power devices per the performance rule.
|
|
131
|
+
|
|
132
|
+
Normal-power behavior is unchanged; low-power rendering drops the animations.
|
|
133
|
+
|
|
134
|
+
This is a beta minor.
|
|
135
|
+
|
|
136
|
+
- 9dcc848: Align workspace dependency versions and migrate React Router to v7.
|
|
137
|
+
|
|
138
|
+
BREAKING CHANGES (React Router v7): All frontend packages now depend on `react-router-dom@^7.16.0`. Previously the workspace declared four divergent ranges (`^6.20.0`, `^6.22.0`, `^7.1.1`, `^7.14.2`), which resolved both `react-router@6` and `react-router@7` into a single bundle. Everything is now unified on v7. The public imports the app uses (`BrowserRouter`, `Routes`, `Route`, `Link`, `NavLink`, `MemoryRouter`, `useNavigate`, `useParams`, `useSearchParams`, `useLocation`) are unchanged between v6 and v7, so no source rewrites were required - but any out-of-tree plugin still on react-router v6 should upgrade to v7 (see the React Router v6 -> v7 upgrade guide) to share the host's single router instance via the import map.
|
|
139
|
+
|
|
140
|
+
Other unified ranges (no API change): `react` -> `^18.3.1`, the `@orpc/*` family (`contract`, `server`, `client`, `tanstack-query`, `openapi`, `zod`) -> `^1.14.4`, and `better-auth` -> `^1.6.13`.
|
|
141
|
+
|
|
142
|
+
Removed the pre-rename `@orpc/react-query` leftover from `@checkstack/frontend-api`; its `createRouterUtils` / `RouterUtils` / `ProcedureUtils` now come from `@orpc/tanstack-query` (the package already in use).
|
|
143
|
+
|
|
144
|
+
Stale in-range runtime deps pulled up to current published versions: `hono` `^4.12.23`, `@tanstack/react-query` (+devtools) `^5.100.14`, `date-fns` `^4.4.0`, `jose` `^6.2.3`, `tar` `^7.5.16`, `semver` `^7.8.1`, `@xyflow/react` `^12.11.0`.
|
|
145
|
+
|
|
146
|
+
### Patch Changes
|
|
147
|
+
|
|
148
|
+
- Updated dependencies [9dcc848]
|
|
149
|
+
- Updated dependencies [9dcc848]
|
|
150
|
+
- Updated dependencies [9dcc848]
|
|
151
|
+
- Updated dependencies [9dcc848]
|
|
152
|
+
- Updated dependencies [9dcc848]
|
|
153
|
+
- @checkstack/common@0.13.0
|
|
154
|
+
- @checkstack/template-engine@0.4.0
|
|
155
|
+
- @checkstack/frontend-api@0.7.0
|
|
156
|
+
|
|
3
157
|
## 1.12.0
|
|
4
158
|
|
|
5
159
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.1",
|
|
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.13.0",
|
|
9
|
+
"@checkstack/frontend-api": "0.7.0",
|
|
10
|
+
"@checkstack/template-engine": "0.4.0",
|
|
10
11
|
"@codingame/monaco-vscode-editor-api": "25.1.2",
|
|
11
12
|
"@codingame/monaco-vscode-languages-service-override": "25.1.2",
|
|
12
13
|
"@codingame/monaco-vscode-standalone-json-language-features": "25.1.2",
|
|
@@ -23,43 +24,47 @@
|
|
|
23
24
|
"ajv-formats": "^3.0.1",
|
|
24
25
|
"class-variance-authority": "^0.7.1",
|
|
25
26
|
"clsx": "^2.1.0",
|
|
26
|
-
"date-fns": "^4.
|
|
27
|
+
"date-fns": "^4.4.0",
|
|
27
28
|
"fast-xml-parser": "^5.8.0",
|
|
28
29
|
"jsonc-parser": "^3.3.1",
|
|
29
|
-
"lucide-react": "
|
|
30
|
+
"lucide-react": "^1.17.0",
|
|
30
31
|
"monaco-languageclient": "10.7.0",
|
|
31
|
-
"react": "^18.
|
|
32
|
+
"react": "^18.3.1",
|
|
32
33
|
"react-day-picker": "^9.13.0",
|
|
33
34
|
"react-dom": "^18.2.0",
|
|
34
35
|
"react-markdown": "^10.1.0",
|
|
35
|
-
"react-router-dom": "^
|
|
36
|
+
"react-router-dom": "^7.16.0",
|
|
36
37
|
"recharts": "^3.6.0",
|
|
38
|
+
"rehype-raw": "^7.0.0",
|
|
39
|
+
"rehype-sanitize": "^6.0.0",
|
|
40
|
+
"remark-gfm": "^4.0.1",
|
|
37
41
|
"tailwind-merge": "^2.2.0",
|
|
38
|
-
"yaml": "^2.9.0"
|
|
42
|
+
"yaml": "^2.9.0",
|
|
43
|
+
"zod": "^4.2.1"
|
|
39
44
|
},
|
|
40
45
|
"devDependencies": {
|
|
41
|
-
"@checkstack/scripts": "0.
|
|
42
|
-
"@checkstack/test-utils-frontend": "0.0
|
|
46
|
+
"@checkstack/scripts": "0.4.0",
|
|
47
|
+
"@checkstack/test-utils-frontend": "0.1.0",
|
|
43
48
|
"@checkstack/tsconfig": "0.0.7",
|
|
44
|
-
"@storybook/addon-a11y": "^10.
|
|
45
|
-
"@storybook/addon-docs": "^10.
|
|
46
|
-
"@storybook/addon-themes": "^10.
|
|
47
|
-
"@storybook/react": "^10.
|
|
48
|
-
"@storybook/react-vite": "^10.
|
|
49
|
+
"@storybook/addon-a11y": "^10.4.1",
|
|
50
|
+
"@storybook/addon-docs": "^10.4.1",
|
|
51
|
+
"@storybook/addon-themes": "^10.4.1",
|
|
52
|
+
"@storybook/react": "^10.4.1",
|
|
53
|
+
"@storybook/react-vite": "^10.4.1",
|
|
49
54
|
"@testing-library/react": "^16.0.0",
|
|
50
55
|
"@types/bun": "^1.3.14",
|
|
51
56
|
"@types/node": "^20.19.27",
|
|
52
57
|
"@types/react": "^18.2.0",
|
|
53
58
|
"@types/react-dom": "^18.2.0",
|
|
54
|
-
"@vitejs/plugin-react": "^6.0.
|
|
59
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
55
60
|
"bun-types": "^1.3.14",
|
|
56
61
|
"autoprefixer": "^10.4.18",
|
|
57
|
-
"postcss": "^8.
|
|
58
|
-
"storybook": "^10.
|
|
62
|
+
"postcss": "^8.5.15",
|
|
63
|
+
"storybook": "^10.4.1",
|
|
59
64
|
"tailwindcss": "^3.4.1",
|
|
60
65
|
"tailwindcss-animate": "^1.0.7",
|
|
61
66
|
"typescript": "^5.0.0",
|
|
62
|
-
"vite": "^8.0.
|
|
67
|
+
"vite": "^8.0.16"
|
|
63
68
|
},
|
|
64
69
|
"scripts": {
|
|
65
70
|
"typecheck": "tsgo -b",
|
|
@@ -2,6 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
3
3
|
import { ChevronDown } from "lucide-react";
|
|
4
4
|
import { cn } from "../utils";
|
|
5
|
+
import { usePerformance } from "./PerformanceProvider";
|
|
5
6
|
|
|
6
7
|
const Accordion = AccordionPrimitive.Root;
|
|
7
8
|
|
|
@@ -40,15 +41,22 @@ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
|
|
40
41
|
const AccordionContent = React.forwardRef<
|
|
41
42
|
React.ElementRef<typeof AccordionPrimitive.Content>,
|
|
42
43
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
|
43
|
-
>(({ className, children, ...props }, ref) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
>(({ className, children, ...props }, ref) => {
|
|
45
|
+
const { isLowPower } = usePerformance();
|
|
46
|
+
return (
|
|
47
|
+
<AccordionPrimitive.Content
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn(
|
|
50
|
+
"overflow-hidden text-sm transition-all",
|
|
51
|
+
!isLowPower &&
|
|
52
|
+
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
|
57
|
+
</AccordionPrimitive.Content>
|
|
58
|
+
);
|
|
59
|
+
});
|
|
52
60
|
|
|
53
61
|
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
|
54
62
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { Card, CardContent, CardHeader } from "./Card";
|
|
11
11
|
import { Button } from "./Button";
|
|
12
12
|
import { Toggle } from "./Toggle";
|
|
13
|
-
import { DynamicIcon, type
|
|
13
|
+
import { DynamicIcon, type IconName } from "./DynamicIcon";
|
|
14
14
|
import { Badge, type BadgeProps } from "./Badge";
|
|
15
15
|
import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
|
|
16
16
|
import { DropdownMenuItem, MenuCloseContext } from "./Menu";
|
|
@@ -23,7 +23,7 @@ import { cn } from "../utils";
|
|
|
23
23
|
*/
|
|
24
24
|
export interface ActionCardMenuItem {
|
|
25
25
|
label: string;
|
|
26
|
-
icon?:
|
|
26
|
+
icon?: IconName;
|
|
27
27
|
onClick: () => void;
|
|
28
28
|
/** `destructive` tints the item red (e.g. Delete). Defaults to neutral. */
|
|
29
29
|
variant?: "default" | "destructive";
|
|
@@ -38,8 +38,8 @@ export interface ActionCardProps {
|
|
|
38
38
|
description?: string;
|
|
39
39
|
/** Plugin/category label rendered as a subdued badge. */
|
|
40
40
|
category?: string;
|
|
41
|
-
/**
|
|
42
|
-
icon?:
|
|
41
|
+
/** Icon (PascalCase) shown to the left of the title - lucide or brand. */
|
|
42
|
+
icon?: IconName;
|
|
43
43
|
/** Toggle for the action's `enabled` flag. Omit to hide the toggle. */
|
|
44
44
|
enabled?: boolean;
|
|
45
45
|
onEnabledChange?: (enabled: boolean) => void;
|
|
@@ -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,9 +2,9 @@
|
|
|
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 { useState } from "react";
|
|
5
|
+
import { useEffect, useState, type ComponentType } from "react";
|
|
6
6
|
import { Maximize2 } from "lucide-react";
|
|
7
|
-
import {
|
|
7
|
+
import type { TypefoxEditorProps } from "./TypefoxEditor";
|
|
8
8
|
import { popoutTitle } from "./popoutTitle";
|
|
9
9
|
import type { CodeEditorProps } from "./types";
|
|
10
10
|
import {
|
|
@@ -13,8 +13,57 @@ import {
|
|
|
13
13
|
DialogHeader,
|
|
14
14
|
DialogTitle,
|
|
15
15
|
} from "../Dialog";
|
|
16
|
+
import { Skeleton } from "../Skeleton";
|
|
17
|
+
import { usePerformance } from "../PerformanceProvider";
|
|
16
18
|
import { cn } from "../../utils";
|
|
17
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
|
+
);
|
|
66
|
+
|
|
18
67
|
/**
|
|
19
68
|
* Code editor with context-aware IntelliSense, template / shell completion, and
|
|
20
69
|
* structural validation. See `CodeEditorProps`.
|
|
@@ -39,11 +88,18 @@ export const CodeEditor = ({
|
|
|
39
88
|
markers,
|
|
40
89
|
acquireTypes,
|
|
41
90
|
acquireResetKey,
|
|
91
|
+
sdkTypes,
|
|
92
|
+
sdkTypesResetKey,
|
|
42
93
|
importablePackages,
|
|
43
94
|
allowPopout = true,
|
|
44
95
|
title,
|
|
96
|
+
deferInit,
|
|
45
97
|
}: CodeEditorProps) => {
|
|
46
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();
|
|
47
103
|
|
|
48
104
|
// CodeEditorProps.minHeight is a CSS length string ("240px"); TypefoxEditor
|
|
49
105
|
// takes a pixel number.
|
|
@@ -67,14 +123,21 @@ export const CodeEditor = ({
|
|
|
67
123
|
markers,
|
|
68
124
|
acquireTypes,
|
|
69
125
|
acquireResetKey,
|
|
126
|
+
sdkTypes,
|
|
127
|
+
sdkTypesResetKey,
|
|
70
128
|
importablePackages,
|
|
129
|
+
deferInit,
|
|
71
130
|
};
|
|
72
131
|
|
|
73
132
|
const dialogTitle = title ?? popoutTitle({ language });
|
|
74
133
|
|
|
75
134
|
return (
|
|
76
135
|
<div className="relative">
|
|
77
|
-
|
|
136
|
+
{TypefoxEditor ? (
|
|
137
|
+
<TypefoxEditor id={editorId} {...sharedEditorProps} />
|
|
138
|
+
) : (
|
|
139
|
+
<EditorLoadingFallback minHeightPx={minHeightPx} />
|
|
140
|
+
)}
|
|
78
141
|
{allowPopout && (
|
|
79
142
|
<button
|
|
80
143
|
type="button"
|
|
@@ -85,7 +148,8 @@ export const CodeEditor = ({
|
|
|
85
148
|
// Sits above the editor in the top-right corner. A faint background
|
|
86
149
|
// keeps the muted icon legible over code; hover emphasises it.
|
|
87
150
|
"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
|
|
151
|
+
"rounded-md bg-background/70 text-muted-foreground",
|
|
152
|
+
!isLowPower && "backdrop-blur-sm",
|
|
89
153
|
"transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
90
154
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
|
91
155
|
)}
|
|
@@ -104,10 +168,10 @@ export const CodeEditor = ({
|
|
|
104
168
|
<DialogHeader>
|
|
105
169
|
<DialogTitle>{dialogTitle}</DialogTitle>
|
|
106
170
|
</DialogHeader>
|
|
107
|
-
{/*
|
|
108
|
-
|
|
171
|
+
{/* The second Monaco instance only mounts while the dialog is open,
|
|
172
|
+
so there's no double-editor cost when closed. Distinct
|
|
109
173
|
`${id}-popout` id => distinct model URI. */}
|
|
110
|
-
{popoutOpen && (
|
|
174
|
+
{popoutOpen && TypefoxEditor && (
|
|
111
175
|
<div className="flex-1 min-h-0">
|
|
112
176
|
<TypefoxEditor
|
|
113
177
|
id={`${editorId}-popout`}
|