@opengeni/react 6.1.0-canary.2 → 6.1.0-canary.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +5 -0
  2. package/dist/{browser-viewer-NTD6UIPR.js → browser-viewer-LGH4IP47.js} +3 -3
  3. package/dist/chunk-AWEBVNVX.js +1398 -0
  4. package/dist/chunk-AWEBVNVX.js.map +1 -0
  5. package/dist/{chunk-NYESIADM.js → chunk-B7TTSUEW.js} +3 -1
  6. package/dist/chunk-B7TTSUEW.js.map +1 -0
  7. package/dist/{chunk-UWVY3OXF.js → chunk-BC7BFCMN.js} +277 -455
  8. package/dist/chunk-BC7BFCMN.js.map +1 -0
  9. package/dist/chunk-BRM6FVZA.js +302 -0
  10. package/dist/chunk-BRM6FVZA.js.map +1 -0
  11. package/dist/{chunk-KZ4QDBGM.js → chunk-GHSSI2BK.js} +925 -1265
  12. package/dist/chunk-GHSSI2BK.js.map +1 -0
  13. package/dist/{chunk-NLTS7JHI.js → chunk-JPHDWKZE.js} +5 -5
  14. package/dist/{chunk-L5VJYZIW.js → chunk-O53CQZ6T.js} +7 -7
  15. package/dist/{chunk-24M4YBCL.js → chunk-PNAVEK2U.js} +4 -4
  16. package/dist/components/preview-loading.d.ts +2 -0
  17. package/dist/composer.js +4 -4
  18. package/dist/connect-setup.d.ts +2 -0
  19. package/dist/connect.js +19 -8
  20. package/dist/connect.js.map +1 -1
  21. package/dist/index.js +27 -27
  22. package/dist/interaction.js +2 -2
  23. package/dist/machines.js +2 -2
  24. package/dist/plugin-discovery.d.ts +2 -0
  25. package/dist/realtime.js +20 -20
  26. package/dist/realtime.js.map +1 -1
  27. package/dist/session-ui.js +7 -7
  28. package/dist/session.js +3 -3
  29. package/package.json +3 -3
  30. package/src/components/markdown.tsx +13 -21
  31. package/src/components/preview-loading.tsx +154 -0
  32. package/src/connect-setup.tsx +17 -5
  33. package/src/hooks/use-composer.ts +6 -0
  34. package/src/plugin-discovery.tsx +10 -6
  35. package/src/realtime/realtime-control.tsx +6 -8
  36. package/styles/compiled.css +48 -8
  37. package/styles/index.css +45 -0
  38. package/dist/chunk-G5JFM6ZE.js +0 -911
  39. package/dist/chunk-G5JFM6ZE.js.map +0 -1
  40. package/dist/chunk-HQV2I5HV.js +0 -124
  41. package/dist/chunk-HQV2I5HV.js.map +0 -1
  42. package/dist/chunk-KZ4QDBGM.js.map +0 -1
  43. package/dist/chunk-NYESIADM.js.map +0 -1
  44. package/dist/chunk-UWVY3OXF.js.map +0 -1
  45. /package/dist/{browser-viewer-NTD6UIPR.js.map → browser-viewer-LGH4IP47.js.map} +0 -0
  46. /package/dist/{chunk-NLTS7JHI.js.map → chunk-JPHDWKZE.js.map} +0 -0
  47. /package/dist/{chunk-L5VJYZIW.js.map → chunk-O53CQZ6T.js.map} +0 -0
  48. /package/dist/{chunk-24M4YBCL.js.map → chunk-PNAVEK2U.js.map} +0 -0
@@ -0,0 +1,154 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ /** Private loading surface: never receives or executes the unfinished fence. */
4
+ export function PreviewLoading() {
5
+ const ref = useRef<HTMLCanvasElement>(null);
6
+ useEffect(() => {
7
+ const canvas = ref.current;
8
+ if (!canvas) return;
9
+ let ctx: CanvasRenderingContext2D | null;
10
+ try {
11
+ ctx = canvas.getContext("2d");
12
+ } catch {
13
+ return; // The CSS grid and status remain when canvas is unavailable.
14
+ }
15
+ if (!ctx) return;
16
+ const context = ctx;
17
+ const motion = window.matchMedia?.("(prefers-reduced-motion: reduce)");
18
+ let frame: number | undefined;
19
+ let time = 2;
20
+ let last: number | undefined;
21
+ let disposed = false;
22
+ // Without visibility observation, keep a static surface rather than doing
23
+ // unbounded work for previews that may be outside the viewport.
24
+ let visible = false;
25
+ const canAnimate = () => !disposed && visible && !document.hidden && motion?.matches === false;
26
+
27
+ function draw() {
28
+ const w = canvas!.clientWidth;
29
+ const h = canvas!.clientHeight;
30
+ if (!w || !h || document.hidden || disposed) return;
31
+ const d = Math.min(window.devicePixelRatio || 1, 2);
32
+ if (canvas!.width !== Math.round(w * d) || canvas!.height !== Math.round(h * d)) {
33
+ canvas!.width = Math.round(w * d);
34
+ canvas!.height = Math.round(h * d);
35
+ }
36
+ const style = getComputedStyle(canvas!);
37
+ const ink = style.getPropertyValue("--_og-preview-ink").trim() || "#bca3ef";
38
+ const glowColor = style.getPropertyValue("--_og-preview-glow").trim() || "#8f6ccc";
39
+ context.setTransform(d, 0, 0, d, 0, 0);
40
+ context.clearRect(0, 0, w, h);
41
+ const cx = w * (0.5 + 0.13 * Math.sin(time * 0.21));
42
+ const cy = h * (0.5 + 0.12 * Math.cos(time * 0.27));
43
+ const glow = context.createRadialGradient(cx, cy, 0, cx, cy, Math.max(w, h) * 0.65);
44
+ glow.addColorStop(0, glowColor);
45
+ glow.addColorStop(1, "transparent");
46
+ context.globalAlpha = 0.09;
47
+ context.fillStyle = glow;
48
+ context.fillRect(0, 0, w, h);
49
+ const rows = Math.ceil(h / 18) + 7;
50
+ const cols = Math.ceil(w / 18) + 7;
51
+ const points = Array.from({ length: rows }, (_row, r) =>
52
+ Array.from({ length: cols }, (_col, c) => {
53
+ const x = (c - 3) * 18;
54
+ const y = (r - 3) * 18;
55
+ const dist = Math.hypot((x - cx) * 0.8, y - cy);
56
+ const wave = Math.sin(dist * 0.032 - time * 0.95);
57
+ return {
58
+ x: x + ((x - cx) / Math.max(dist, 1)) * wave * 4,
59
+ y: y + ((y - cy) / Math.max(dist, 1)) * wave * 4,
60
+ l: Math.pow((wave + 1) / 2, 5),
61
+ };
62
+ }),
63
+ );
64
+ context.strokeStyle = ink;
65
+ context.fillStyle = ink;
66
+ context.lineWidth = 0.65;
67
+ points.forEach((row, r) =>
68
+ row.forEach((p, c) => {
69
+ context.globalAlpha = 0.035 + p.l * 0.055;
70
+ context.beginPath();
71
+ if (c + 1 < cols) {
72
+ context.moveTo(p.x, p.y);
73
+ context.lineTo(row[c + 1]!.x, row[c + 1]!.y);
74
+ }
75
+ if (r + 1 < rows) {
76
+ context.moveTo(p.x, p.y);
77
+ context.lineTo(points[r + 1]![c]!.x, points[r + 1]![c]!.y);
78
+ }
79
+ context.stroke();
80
+ context.globalAlpha = 0.15 + p.l * 0.55;
81
+ context.beginPath();
82
+ context.arc(p.x, p.y, 0.85 + p.l * 0.65, 0, Math.PI * 2);
83
+ context.fill();
84
+ }),
85
+ );
86
+ context.globalAlpha = 1;
87
+ canvas!.dataset.painted = "true";
88
+ }
89
+
90
+ function stop() {
91
+ if (frame !== undefined) window.cancelAnimationFrame(frame);
92
+ frame = undefined;
93
+ last = undefined;
94
+ }
95
+ function tick(stamp: number) {
96
+ frame = undefined;
97
+ if (!canAnimate()) return;
98
+ if (last !== undefined) time += Math.min((stamp - last) / 1000, 0.05);
99
+ last = stamp;
100
+ draw();
101
+ frame = window.requestAnimationFrame(tick);
102
+ }
103
+ function refresh() {
104
+ stop();
105
+ if (disposed || document.hidden || (!visible && intersection)) return;
106
+ draw();
107
+ if (canAnimate() && typeof window.requestAnimationFrame === "function") {
108
+ frame = window.requestAnimationFrame(tick);
109
+ }
110
+ }
111
+ const intersection =
112
+ typeof IntersectionObserver === "function"
113
+ ? new IntersectionObserver(([entry]) => {
114
+ visible = entry?.isIntersecting === true;
115
+ refresh();
116
+ })
117
+ : undefined;
118
+ intersection?.observe(canvas);
119
+ const resize = typeof ResizeObserver === "function" ? new ResizeObserver(refresh) : undefined;
120
+ resize?.observe(canvas);
121
+ // Observe only ancestors, not the document subtree. This also updates the
122
+ // static reduced-motion canvas when a host changes its local theme.
123
+ const theme =
124
+ typeof MutationObserver === "function" ? new MutationObserver(refresh) : undefined;
125
+ for (let ancestor = canvas.parentElement; ancestor; ancestor = ancestor.parentElement) {
126
+ theme?.observe(ancestor, {
127
+ attributes: true,
128
+ attributeFilter: ["class", "style", "data-og-theme"],
129
+ });
130
+ }
131
+ motion?.addEventListener?.("change", refresh);
132
+ document.addEventListener("visibilitychange", refresh);
133
+ window.addEventListener("resize", refresh);
134
+ // Old hosts get one still frame; observed surfaces wait until onscreen.
135
+ if (!intersection) draw();
136
+ return () => {
137
+ disposed = true;
138
+ stop();
139
+ intersection?.disconnect();
140
+ resize?.disconnect();
141
+ theme?.disconnect();
142
+ motion?.removeEventListener?.("change", refresh);
143
+ document.removeEventListener("visibilitychange", refresh);
144
+ window.removeEventListener("resize", refresh);
145
+ };
146
+ }, []);
147
+
148
+ return (
149
+ <div className="og-preview-loading">
150
+ <canvas ref={ref} aria-hidden="true" />
151
+ <span className="og-preview-loading-label">Preparing preview…</span>
152
+ </div>
153
+ );
154
+ }
@@ -35,6 +35,8 @@ export type ConnectSetupProps = {
35
35
  controller: ConnectController;
36
36
  /** Called synchronously in the click handler so a host can open a popup. */
37
37
  onAuthorize: (attempt: ConnectAttempt) => void | Promise<unknown>;
38
+ /** Called synchronously on Continue; hosts can reserve a popup before advancing. */
39
+ onSelectAccount?: (accountId: string) => void | Promise<unknown>;
38
40
  /** Host pagination must retain selections across pages and submit the final set. */
39
41
  onBrowseResources?: (attempt: ConnectAttempt) => void | Promise<unknown>;
40
42
  className?: string;
@@ -61,11 +63,13 @@ function ScopedSetup({
61
63
  controller,
62
64
  onAuthorize,
63
65
  onBrowseResources,
66
+ onSelectAccount,
64
67
  className,
65
68
  authorizeLabel = "Authorize connection",
66
69
  }: ConnectSetupProps) {
67
70
  const view = useConnect(controller);
68
71
  const [localError, setLocalError] = useState(false);
72
+ const [navigating, setNavigating] = useState(false);
69
73
  const invoke = (operation: () => unknown | Promise<unknown>) => {
70
74
  setLocalError(false);
71
75
  try {
@@ -91,7 +95,7 @@ function ScopedSetup({
91
95
  event.preventDefault();
92
96
  const form = event.currentTarget;
93
97
  const data = new FormData(form);
94
- if (view.busy) return;
98
+ if (view.busy || navigating) return;
95
99
  const key = crypto.randomUUID();
96
100
  if (action.type === "credentials") {
97
101
  const values = Object.fromEntries(
@@ -105,7 +109,15 @@ function ScopedSetup({
105
109
  if (
106
110
  action.accounts.some((account) => account.id === accountId && account.status !== "disabled")
107
111
  )
108
- invoke(() => view.advance({ type: "account", accountId }, key));
112
+ invoke(async () => {
113
+ if (!onSelectAccount) return view.advance({ type: "account", accountId }, key);
114
+ setNavigating(true);
115
+ try {
116
+ await onSelectAccount(accountId);
117
+ } finally {
118
+ setNavigating(false);
119
+ }
120
+ });
109
121
  } else if (action.type === "select_resources") {
110
122
  if (action.cursor) return;
111
123
  const resourceIds = data.getAll("resource").map(String);
@@ -129,7 +141,7 @@ function ScopedSetup({
129
141
  <section
130
142
  className={["og-connect-setup", className].filter(Boolean).join(" ")}
131
143
  aria-label="Connection setup"
132
- aria-busy={view.busy}
144
+ aria-busy={view.busy || navigating}
133
145
  >
134
146
  <div className="og-connect-setup-summary">
135
147
  <p className="og-connect-setup-scope">
@@ -154,7 +166,7 @@ function ScopedSetup({
154
166
  )}
155
167
  {!terminal && (
156
168
  <form key={`${attempt.id}:${attempt.revision}`} onSubmit={submit} autoComplete="off">
157
- <fieldset disabled={view.busy}>
169
+ <fieldset disabled={view.busy || navigating}>
158
170
  {["credentials", "select_account", "select_resources", "preview"].includes(
159
171
  action.type,
160
172
  ) ? (
@@ -261,7 +273,7 @@ function ScopedSetup({
261
273
  className="og-connect-setup-primary"
262
274
  onClick={() => invoke(() => onAuthorize(structuredClone(attempt)))}
263
275
  >
264
- {authorizeLabel}
276
+ {navigating ? "Waiting for authorization…" : authorizeLabel}
265
277
  </button>
266
278
  )}
267
279
  {action.type === "wait" && (
@@ -1478,6 +1478,9 @@ export function useComposer(
1478
1478
  ...(wireInput.connectionAuthorities
1479
1479
  ? { connectionAuthorities: wireInput.connectionAuthorities }
1480
1480
  : {}),
1481
+ ...(wireInput.selectedHostMcpDelegations
1482
+ ? { selectedHostMcpDelegations: wireInput.selectedHostMcpDelegations }
1483
+ : {}),
1481
1484
  ...(wireInput.personalResourceAttachment
1482
1485
  ? { personalResourceAttachment: wireInput.personalResourceAttachment }
1483
1486
  : {}),
@@ -1754,6 +1757,9 @@ export function useComposer(
1754
1757
  ...(input.connectionAuthorities
1755
1758
  ? { connectionAuthorities: input.connectionAuthorities }
1756
1759
  : {}),
1760
+ ...(input.selectedHostMcpDelegations
1761
+ ? { selectedHostMcpDelegations: input.selectedHostMcpDelegations }
1762
+ : {}),
1757
1763
  ...(input.personalResourceAttachment
1758
1764
  ? { personalResourceAttachment: input.personalResourceAttachment }
1759
1765
  : {}),
@@ -17,6 +17,8 @@ export type PluginDiscoveryProps = {
17
17
  };
18
18
  workspaceId: string;
19
19
  query: string;
20
+ /** Initially selected registry. Users can still switch registries or browse all. */
21
+ defaultProvider?: "" | "openai" | "anthropic";
20
22
  /** Discovery identities already installed in the current workspace. */
21
23
  installedIds?: ReadonlySet<string>;
22
24
  resultLimit?: number;
@@ -24,7 +26,7 @@ export type PluginDiscoveryProps = {
24
26
  onOpen: (item: PluginDiscoveryItem) => void;
25
27
  };
26
28
  export function PluginDiscovery(props: PluginDiscoveryProps) {
27
- const [provider, setProvider] = useState("");
29
+ const [provider, setProvider] = useState(props.defaultProvider ?? "");
28
30
  return (
29
31
  <section
30
32
  className={`og-plugin-discovery ${props.resultLimit ? "og-catalog-overview" : ""}`}
@@ -34,11 +36,13 @@ export function PluginDiscovery(props: PluginDiscoveryProps) {
34
36
  <h3>{props.resultLimit ? "Plugins" : "Browse plugins"}</h3>
35
37
  {!props.resultLimit ? (
36
38
  <div className="og-plugin-filters" role="group" aria-label="Plugin registry">
37
- {[
38
- { value: "", label: "All" },
39
- { value: "openai", label: "OpenAI plugin registry" },
40
- { value: "anthropic", label: "Anthropic plugin registry" },
41
- ].map((option) => (
39
+ {(
40
+ [
41
+ { value: "", label: "All" },
42
+ { value: "openai", label: "OpenAI plugin registry" },
43
+ { value: "anthropic", label: "Anthropic plugin registry" },
44
+ ] as const
45
+ ).map((option) => (
42
46
  <button
43
47
  key={option.value}
44
48
  type="button"
@@ -1105,16 +1105,14 @@ export function RealtimeVoiceModelPanel(props: {
1105
1105
 
1106
1106
  return (
1107
1107
  <div className="flex min-h-0 flex-1 flex-col" data-testid="realtime-voice-model-panel">
1108
- <div className="flex shrink-0 items-start gap-1 px-2 pt-1 pb-1.5">
1108
+ <div className="flex min-h-10 shrink-0 items-center gap-2 border-b border-og-border px-2 pb-2">
1109
1109
  {props.leading}
1110
- <div className="min-w-0">
1111
- <div className="text-og-menu font-medium text-og-fg">Voice model</div>
1112
- <p className="mt-0.5 text-og-control text-og-fg-subtle">
1113
- Used when you start a voice conversation.
1114
- </p>
1115
- </div>
1110
+ <div className="min-w-0 flex-1 text-og-menu font-medium text-og-fg">Voice model</div>
1116
1111
  </div>
1117
- <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-1.5 pb-1.5">
1112
+ <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-2">
1113
+ <p className="mb-3 text-og-control text-og-fg-muted">
1114
+ Used when you start a voice conversation.
1115
+ </p>
1118
1116
  <RealtimeModelPickerMenu
1119
1117
  models={props.models}
1120
1118
  selectedModel={props.selectedModel}
@@ -608,6 +608,54 @@
608
608
  --_og-session-chrome-ease: var(--og-session-chrome-ease, var(--og-ease-out));
609
609
  --_og-session-chrome-row-hover: var(--og-session-chrome-row-hover, var(--og-color-surface-3));
610
610
  }
611
+ :where(.og-root).og-preview-loading,:where(.og-root) .og-preview-loading {
612
+ --_og-preview-ink: #bca3ef;
613
+ --_og-preview-glow: #8f6ccc;
614
+ position: relative;
615
+ width: 100%;
616
+ overflow: hidden;
617
+ border-radius: 10px;
618
+ background: #17161d;
619
+ container: og-preview / inline-size;
620
+ }
621
+ :where(.og-root):is([data-og-theme="light"], .og-light) .og-preview-loading, :is([data-og-theme="light"], .og-light) :where(.og-root) .og-preview-loading,:where(.og-root) :is([data-og-theme="light"], .og-light) .og-preview-loading {
622
+ --_og-preview-ink: #76629c;
623
+ --_og-preview-glow: #ba8ee0;
624
+ background: #faf9fd;
625
+ }
626
+ :where(.og-root).og-preview-loading canvas,:where(.og-root) .og-preview-loading canvas {
627
+ display: block;
628
+ width: 100%;
629
+ height: 320px;
630
+ background-image: radial-gradient(circle, var(--_og-preview-ink) 1px, transparent 1px);
631
+ @supports (color: color-mix(in lab, red, red)) {
632
+ background-image: radial-gradient(circle, color-mix(in srgb, var(--_og-preview-ink) 30%, transparent) 1px, transparent 1px);
633
+ }
634
+ background-size: 18px 18px;
635
+ }
636
+ :where(.og-root).og-preview-loading canvas[data-painted="true"],:where(.og-root) .og-preview-loading canvas[data-painted="true"] {
637
+ background-image: none;
638
+ }
639
+ :where(.og-root).og-preview-loading-label,:where(.og-root) .og-preview-loading-label {
640
+ position: absolute;
641
+ left: 16px;
642
+ bottom: 14px;
643
+ color: var(--og-color-fg-muted);
644
+ font-family: var(--og-font-sans);
645
+ font-size: var(--og-font-size-xs);
646
+ line-height: var(--og-line-height-xs);
647
+ font-weight: 400;
648
+ }
649
+ @container og-preview (max-width: 500px) {
650
+ :where(.og-root).og-preview-loading canvas,:where(.og-root) .og-preview-loading canvas {
651
+ height: 260px;
652
+ }
653
+ }
654
+ @media (max-width: 500px) {
655
+ :where(.og-root).og-preview-loading canvas,:where(.og-root) .og-preview-loading canvas {
656
+ height: 260px;
657
+ }
658
+ }
611
659
  .og-root :where([hidden]:not([hidden="until-found"])) {
612
660
  display: none !important;
613
661
  }
@@ -1623,9 +1671,6 @@
1623
1671
  :where(.og-root).min-h-14,:where(.og-root) .min-h-14 {
1624
1672
  min-height: calc(var(--spacing) * 14);
1625
1673
  }
1626
- :where(.og-root).min-h-32,:where(.og-root) .min-h-32 {
1627
- min-height: calc(var(--spacing) * 32);
1628
- }
1629
1674
  :where(.og-root).min-h-56,:where(.og-root) .min-h-56 {
1630
1675
  min-height: calc(var(--spacing) * 56);
1631
1676
  }
@@ -5031,11 +5076,6 @@
5031
5076
  }
5032
5077
  }
5033
5078
  }
5034
- :where(.og-root).motion-safe\:animate-spin,:where(.og-root) .motion-safe\:animate-spin {
5035
- @media (prefers-reduced-motion: no-preference) {
5036
- animation: og-spin 1s linear infinite;
5037
- }
5038
- }
5039
5079
  :where(.og-root).motion-reduce\:animate-none,:where(.og-root) .motion-reduce\:animate-none {
5040
5080
  @media (prefers-reduced-motion: reduce) {
5041
5081
  animation: none;
package/styles/index.css CHANGED
@@ -17,6 +17,51 @@
17
17
  @import "./tokens.css";
18
18
  @import "./effective-tokens.css";
19
19
 
20
+ /* Full-surface preview preparation. Private palette preserves the accepted
21
+ ripple study; the steady label uses the host's typography and text tokens. */
22
+ .og-preview-loading {
23
+ --_og-preview-ink: #bca3ef;
24
+ --_og-preview-glow: #8f6ccc;
25
+ position: relative;
26
+ width: 100%;
27
+ overflow: hidden;
28
+ border-radius: 10px;
29
+ background: #17161d;
30
+ container: og-preview / inline-size;
31
+ }
32
+ .og-root:is([data-og-theme="light"], .og-light) .og-preview-loading,
33
+ :is([data-og-theme="light"], .og-light) .og-root .og-preview-loading,
34
+ .og-root :is([data-og-theme="light"], .og-light) .og-preview-loading {
35
+ --_og-preview-ink: #76629c;
36
+ --_og-preview-glow: #ba8ee0;
37
+ background: #faf9fd;
38
+ }
39
+ .og-preview-loading canvas {
40
+ display: block;
41
+ width: 100%;
42
+ height: 320px;
43
+ /* Non-canvas / pre-effect fallback, removed only after a successful paint. */
44
+ background-image: radial-gradient(circle, color-mix(in srgb, var(--_og-preview-ink) 30%, transparent) 1px, transparent 1px);
45
+ background-size: 18px 18px;
46
+ }
47
+ .og-preview-loading canvas[data-painted="true"] { background-image: none; }
48
+ .og-preview-loading-label {
49
+ position: absolute;
50
+ left: 16px;
51
+ bottom: 14px;
52
+ color: var(--og-color-fg-muted);
53
+ font-family: var(--og-font-sans);
54
+ font-size: var(--og-font-size-xs);
55
+ line-height: var(--og-line-height-xs);
56
+ font-weight: 400;
57
+ }
58
+ @container og-preview (max-width: 500px) {
59
+ .og-preview-loading canvas { height: 260px; }
60
+ }
61
+ @media (max-width: 500px) {
62
+ .og-preview-loading canvas { height: 260px; }
63
+ }
64
+
20
65
  /* Standalone embeds do not necessarily load Tailwind Preflight. Keep the
21
66
  control reset inside SDK roots, before utilities, without resetting the host.
22
67
  :where keeps controls at the same specificity as component utilities. */